diff --git a/bacnet-stack/demo/Makefile b/bacnet-stack/demo/Makefile index 502a5b89..83b82dd0 100644 --- a/bacnet-stack/demo/Makefile +++ b/bacnet-stack/demo/Makefile @@ -49,6 +49,10 @@ SUBDIRS = readprop writeprop readfile writefile reinit server dcc \ whohas whois ucov scov timesync epics readpropm \ mstpcap mstpcrc uptransfer \ whoisrouter iamrouter initrouter +ifeq (${BACNET_PORT},linux) +SUBDIRS += router +endif + ifeq (${BACNET_PORT},win32) SUBDIRS += ptransfer endif diff --git a/bacnet-stack/demo/handler/h_get_alarm_sum.c b/bacnet-stack/demo/handler/h_get_alarm_sum.c index 5d670e5b..db3fe68f 100644 --- a/bacnet-stack/demo/handler/h_get_alarm_sum.c +++ b/bacnet-stack/demo/handler/h_get_alarm_sum.c @@ -152,7 +152,7 @@ void handler_get_alarm_summary( pdu_len); #if PRINT_ENABLED if (bytes_sent <= 0) { - fprintf(stderr, "Failed to send PDU (%s)!\n", strerror(errno)); + //fprintf(stderr, "Failed to send PDU (%s)!\n", strerror(errno)); } #else bytes_sent = bytes_sent; diff --git a/bacnet-stack/demo/router/Makefile b/bacnet-stack/demo/router/Makefile new file mode 100644 index 00000000..94cb86df --- /dev/null +++ b/bacnet-stack/demo/router/Makefile @@ -0,0 +1,63 @@ +#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 = router + +TARGET_BIN = ${TARGET}$(TARGET_EXT) + +ifeq (${BACNET_PORT},linux) +#PFLAGS = +# -pthread +TARGET_EXT = +LIBS = -lpthread -lconfig +LFLAGS += $(LIBS) +endif + +#DEFINES = $(BACNET_DEFINES) -DBACDL_MSTP -DBACDL_BIP +BACNET_SOURCE_DIR = ../../src + +SRCS = main.c \ + ${BACNET_PORT_DIR}/rs485.c \ + ${BACNET_PORT_DIR}/timer.c \ + ${BACNET_PORT_DIR}/bip-init.c \ + ${BACNET_PORT_DIR}/dlmstp_linux.c \ + ${BACNET_SOURCE_DIR}/bip.c \ + ${BACNET_SOURCE_DIR}/bvlc.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 \ + mstpmodule.c \ + ipmodule.c \ + portthread.c \ + msgqueue.c \ + network_layer.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 diff --git a/bacnet-stack/demo/router/init.cfg b/bacnet-stack/demo/router/init.cfg new file mode 100644 index 00000000..ed0d9aba --- /dev/null +++ b/bacnet-stack/demo/router/init.cfg @@ -0,0 +1,68 @@ +/* +configuration file that stores values for router ports initialization + +Common arguments: + device_type - "bip" or "mstp" (with quotes) + device - Connection device, for example "eth0" or "/dev/ttyS0" + network - Network number [1..65534]. Do not use network number 65535, it is broadcast number + +bip arguments: + port - bip UDP port, default 47808 + +mstp arguments: + mac - MSTP MAC + max_master - MSTP max master + max_frames - 1 + baud - one from the list: 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400 + parity - one from the list (with quotes): "None", "Even", "Odd" + databits - one from the list: 5, 6, 7, 8 + stopbits - 1 or 2 + + +Example: + ports = + ( + { + device_type = "bip"; + device = "eth0"; + port = 47808; + network = 1; + }, + + { + device_type = "mstp"; + device = "/dev/ttyS0"; + mac = 1; + max_master = 127; + max_frames = 1; + baud = 38400; + parity = "None"; + databits = 8; + stopbits = 1; + network = 2; + } + ); +*/ + +ports = +( + { + device_type = "bip"; + device = "eth0"; + port = 47808; + network = 1; + }, + + { + device_type = "mstp"; + device = "/dev/ttyS0"; + mac = 2; + max_master = 127; + max_frames = 1; + baud = 38400; + parity = "None"; + databits = 8; + stopbits = 1; + network = 2; + } +); diff --git a/bacnet-stack/demo/router/ipmodule.c b/bacnet-stack/demo/router/ipmodule.c new file mode 100644 index 00000000..bc83654c --- /dev/null +++ b/bacnet-stack/demo/router/ipmodule.c @@ -0,0 +1,375 @@ +/* +Copyright (C) 2012 Andriy Sukhynyuk, Vasyl Tkhir, Andriy Ivasiv + + 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 3 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, see . +*/ +#include +#include +#include +#include "ipmodule.h" +#include "bacint.h" + +#ifdef TEST_PACKET + uint8_t test_packet[]={0x81, 0x0a, 0x00, 0x16, // BVLC header + 0x01, 0x24, 0x00, 0x01, 0x01, 0x0b, 0xff, // NPDU + 0x00, 0x03, 0x01, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x02, 0x19, 0x55}; // APDU +#endif + +extern int get_local_address_ioctl( + char *ifname, + struct in_addr *addr, + int request); + +void* dl_ip_thread(void *pArgs) { + MSGBOX_ID msgboxid; + BACMSG msg_storage, *bacmsg = NULL; + MSG_DATA *msg_data; + ROUTER_PORT *port = (ROUTER_PORT*)pArgs; + IP_DATA ip_data; // port specific parameters + BACNET_ADDRESS address = { 0 }; + int status; + uint8_t shutdown = 0; + + // initialize router port + if (!dl_ip_init(port, &ip_data)) { + port->state = INIT_FAILED; + return NULL; + } + + // allocate buffer + ip_data.max_buff = MAX_BIP_MPDU; + ip_data.buff = (uint8_t*)malloc(ip_data.max_buff); + + if (ip_data.buff == NULL) { + port->state = INIT_FAILED; + return NULL; + } + + msgboxid = create_msgbox(); + if (msgboxid == INVALID_MSGBOX_ID) { + PRINT(ERROR, "Error: Failed to create message box"); + port->state = INIT_FAILED; + return NULL; + } + + port->port_id = msgboxid; + port->state = RUNNING; + + while (!shutdown) { + + // check for incoming messages + bacmsg = recv_from_msgbox(port->port_id, &msg_storage); + + if (bacmsg) { + switch(bacmsg->type) { + case DATA: { + msg_data = (MSG_DATA*)bacmsg->data; + memmove(&address.net, &msg_data->dest.net, 2); + memmove(&address.mac_len, &msg_data->dest.len, 1); + memmove(&address.mac[0], &msg_data->dest.adr[0], MAX_MAC_LEN); + + dl_ip_send(&ip_data, &address, msg_data->pdu, msg_data->pdu_len); + + check_data(msg_data); + + break; + } + + case SERVICE: { + switch(bacmsg->subtype) { + case SHUTDOWN: + del_msgbox(port->port_id); + shutdown = 1; + break; + default: + break; + } + break; + } + + default: + break; + } + } else { + status = dl_ip_recv(&ip_data, &msg_data, &address, 1000); + if (status > 0) { + memmove(&msg_data->src.len, &address.mac_len, 1); + memmove(&msg_data->src.adr[0], &address.mac[0], MAX_MAC_LEN); + msg_storage.origin = port->port_id; + msg_storage.type = DATA; + msg_storage.data = msg_data; + + if (!send_to_msgbox(port->main_id, &msg_storage)) { + free_data(msg_data); + } + } + } + } + + // cleanup procedure + dl_ip_cleanup(&ip_data); + port->state = FINISHED; + return NULL; +} + +bool dl_ip_init(ROUTER_PORT *port, + IP_DATA *ip_data) { + struct sockaddr_in sin; + int socket_opt = 0; + int status = 0; // for error checking + + // setup port for later use + ip_data->port = htons(port->params.bip_params.port); + + // get local address + status = get_local_address_ioctl(port->iface, &ip_data->local_addr, SIOCGIFADDR); + if (status < 0) { + return false; + } + // get broadcast address + status = get_local_address_ioctl(port->iface, &ip_data->broadcast_addr, SIOCGIFBRDADDR); + if (status < 0) { + return false; + } + + ip_data->socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (ip_data->socket < 0) + return false; + + // setup socket options + + socket_opt = 1; + status = setsockopt(ip_data->socket, SOL_SOCKET, SO_REUSEADDR, &socket_opt, sizeof(socket_opt)); + if (status < 0) { + close(ip_data->socket); + return false; + } + + status = setsockopt(ip_data->socket, SOL_SOCKET, SO_BROADCAST, &socket_opt, sizeof(socket_opt)); + if (status < 0) { + close(ip_data->socket); + return false; + } + + // bind the socket to the local port number + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = ip_data->port; + + memset(&sin.sin_zero, '\0', sizeof(sin.sin_zero)); + + status = bind(ip_data->socket, (const struct sockaddr *) &sin, sizeof(struct sockaddr)); + if (status < 0) { + close(ip_data->socket); + return false; + } + + // add BIP address to router port structure + memcpy(&port->route_info.mac[0], &ip_data->local_addr.s_addr, 4); + memcpy(&port->route_info.mac[4], &port->params.bip_params.port, 2); + port->route_info.mac_len = 6; + + PRINT(INFO, "Interface: %s\n", port->iface); + PRINT(INFO, "IP Address: %s\n", inet_ntoa(ip_data->local_addr)); + PRINT(INFO, "IP Broadcast Address: %s\n", inet_ntoa(ip_data->broadcast_addr)); + PRINT(INFO, "UDP Port: 0x%04X [%hu]\n", (port->params.bip_params.port), + (port->params.bip_params.port)); + + return true; +} + +int dl_ip_send(IP_DATA *data, + BACNET_ADDRESS *dest, + uint8_t *pdu, + unsigned pdu_len) +{ + struct sockaddr_in bip_dest = { 0 }; + int buff_len = 0; + int bytes_sent = 0; + + if (data->socket < 0) + return -1; + + data->buff[0] = BVLL_TYPE_BACNET_IP; + bip_dest.sin_family = AF_INET; + if (dest->net == BACNET_BROADCAST_NETWORK) { + // broadcast + bip_dest.sin_addr.s_addr = data->broadcast_addr.s_addr; + bip_dest.sin_port = data->port; + data->buff[1] = BVLC_ORIGINAL_BROADCAST_NPDU; + } else if (dest->mac_len == 6) { + memcpy(&bip_dest.sin_addr.s_addr, &dest->mac[0], 4); + memcpy(&bip_dest.sin_port, &dest->mac[4], 2); + data->buff[1] = BVLC_ORIGINAL_UNICAST_NPDU; + } else { + // invalid address + return -1; + } + + buff_len = 2; + buff_len += + encode_unsigned16(&data->buff[buff_len], + (uint16_t) (pdu_len + 4 /*inclusive*/ )); + memcpy(&data->buff[buff_len], pdu, pdu_len); + buff_len += pdu_len; + + // send the packet + bytes_sent = + sendto(data->socket, (char *) data->buff, buff_len, 0, + (struct sockaddr *) &bip_dest, sizeof(struct sockaddr)); + + PRINT(DEBUG, "send to %s\n", inet_ntoa(bip_dest.sin_addr)); + + return bytes_sent; +} + +int dl_ip_recv( + IP_DATA *data, + MSG_DATA **msg_data, + BACNET_ADDRESS *src, + unsigned timeout) +{ + int received_bytes = 0; + uint16_t buff_len = 0; // return value + fd_set read_fds; + struct timeval select_timeout; + struct sockaddr_in sin = { 0 }; + socklen_t sin_len = sizeof(sin); + + // make sure the socket is open + if (data->socket < 0) + return 0; + + if (timeout >= 1000) { + select_timeout.tv_sec = timeout / 1000; + select_timeout.tv_usec = 1000 * (timeout - select_timeout.tv_sec * 1000); + } else { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 1000 * timeout; + } + + FD_ZERO(&read_fds); + FD_SET(data->socket, &read_fds); + +#ifdef TEST_PACKET + received_bytes = sizeof(test_packet); + memmove(data->buff, &test_packet, received_bytes); + sin.sin_addr.s_addr = 0x7E1D40A; sin.sin_port = 0xC0BA; +#else + int ret = select(data->socket + 1, &read_fds, NULL, NULL, &select_timeout); + // see if there is a packet for us + if (ret > 0) + received_bytes = + recvfrom(data->socket, (char *) &data->buff[0], data->max_buff, 0, + (struct sockaddr *) &sin, &sin_len); + else + return 0; +#endif + PRINT(DEBUG, "received from %s\n", inet_ntoa(sin.sin_addr)); + + // check for errors + if (received_bytes <= 0) { + return 0; + } + + // the signature of a BACnet/IP packet + if (data->buff[0] != BVLL_TYPE_BACNET_IP) + return 0; + + switch (data->buff[1]) { + case BVLC_ORIGINAL_UNICAST_NPDU: + case BVLC_ORIGINAL_BROADCAST_NPDU: { + if ((sin.sin_addr.s_addr == data->local_addr.s_addr) && + (sin.sin_port == data->port)) { + buff_len = 0; + + PRINT(DEBUG, "BIP: src is me. Discarded!\n"); + + } else { + src->mac_len = 6; + memcpy(&src->mac[0], &sin.sin_addr.s_addr, 4); + memcpy(&src->mac[4], &sin.sin_port, 2); + + (void) decode_unsigned16(&data->buff[2], &buff_len); + // subtract off the BVLC header + buff_len -= 4; + if (buff_len < data->max_buff) { + // allocate data message stucture + (*msg_data) = (MSG_DATA*)malloc(sizeof(MSG_DATA)); + (*msg_data)->pdu_len = buff_len; + (*msg_data)->pdu = (uint8_t*)malloc((*msg_data)->pdu_len); + // fill up data message structure + memmove(&(*msg_data)->pdu[0], &data->buff[4], (*msg_data)->pdu_len); + memmove(&(*msg_data)->src, src, sizeof(BACNET_ADDRESS)); + } + // ignore packets that are too large + else { + buff_len = 0; + + PRINT(ERROR, "BIP: PDU too large. Discarded!.\n"); + + } + } + } + break; + + case BVLC_FORWARDED_NPDU: { + memcpy(&sin.sin_addr.s_addr, &data->buff[4], 4); + memcpy(&sin.sin_port, &data->buff[8], 2); + if ((sin.sin_addr.s_addr == data->local_addr.s_addr) && + (sin.sin_port == data->port)) { + buff_len = 0; + } else { + src->mac_len = 6; + memcpy(&src->mac[0], &sin.sin_addr.s_addr, 4); + memcpy(&src->mac[4], &sin.sin_port, 2); + + (void) decode_unsigned16(&data->buff[2], &buff_len); + // subtract off the BVLC header + buff_len -= 10; + if (buff_len < data->max_buff) { + // allocate data message stucture + (*msg_data) = (MSG_DATA*)malloc(sizeof(MSG_DATA)); + (*msg_data)->pdu_len = buff_len; + (*msg_data)->pdu = (uint8_t*)malloc((*msg_data)->pdu_len); + // fill up data message structure + memmove(&(*msg_data)->pdu, &data->buff[4+6], (*msg_data)->pdu_len); + memmove(&(*msg_data)->src, src, sizeof(BACNET_ADDRESS)); + } else { + // ignore packets that are too large + buff_len = 0; + } + } + } + break; + default: + + PRINT(ERROR, "BIP: BVLC discarded!\n"); + + break; + } + return buff_len; +} + +void dl_ip_cleanup( + IP_DATA *ip_data) { + // free buffer + if (ip_data->buff) + free(ip_data->buff); + // close socket + if (ip_data->socket > 0) + close(ip_data->socket); + return; +} + diff --git a/bacnet-stack/demo/router/ipmodule.h b/bacnet-stack/demo/router/ipmodule.h new file mode 100644 index 00000000..ab6402fb --- /dev/null +++ b/bacnet-stack/demo/router/ipmodule.h @@ -0,0 +1,61 @@ +/* +Copyright (C) 2012 Andriy Sukhynyuk, Vasyl Tkhir, Andriy Ivasiv + + 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 3 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, see . +*/ +#ifndef UDPMODULE_H +#define UDPMODULE_H + +#include +#include +#include "portthread.h" +#include "bip.h" + +#define MAX_BIP_APDU 1476 +#define MAX_BIP_PDU (MAX_NPDU + MAX_BIP_APDU) +#define MAX_BIP_MPDU (MAX_HEADER + MAX_BIP_PDU) + + typedef struct ip_data { + int socket; + uint16_t port; + struct in_addr local_addr; + struct in_addr broadcast_addr; + uint8_t *buff; + uint16_t max_buff; + } IP_DATA; + + +void* dl_ip_thread( + void *pArgs); + +bool dl_ip_init( + ROUTER_PORT *port, + IP_DATA *data); + +int dl_ip_send( + IP_DATA *data, + BACNET_ADDRESS *dest, + uint8_t *pdu, + unsigned pdu_len); + +int dl_ip_recv( + IP_DATA *data, + MSG_DATA **msg, // on recieve fill up message + BACNET_ADDRESS *src, + unsigned timeout); + +void dl_ip_cleanup( + IP_DATA *data); + +#endif /* end of UDPMODULE_H */ diff --git a/bacnet-stack/demo/router/main.c b/bacnet-stack/demo/router/main.c new file mode 100644 index 00000000..8adc35b4 --- /dev/null +++ b/bacnet-stack/demo/router/main.c @@ -0,0 +1,813 @@ +/* +Copyright (C) 2012 Andriy Sukhynyuk, Vasyl Tkhir, Andriy Ivasiv + + 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 3 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, see . +*/ +#include +#include +#include +#include +#include +#include +#include // for time +#include +#include +#include +#include // read config files +#include // for getopt +#include // used in kbhit() +#include +#include +#include +#include +#include +#include "msgqueue.h" +#include "portthread.h" +#include "network_layer.h" +#include "ipmodule.h" +#include "mstpmodule.h" +#include "bip.h" +#include "dlmstp.h" + +#define KEY_ESC 27 + +ROUTER_PORT *head = NULL; // pointer to list of router ports + +int port_count; + +void print_help(); + +bool read_config( + char *filepath); + +bool parse_cmd( + int argc, + char *argv[]); + +void init_port_threads( + ROUTER_PORT* port_list); + +bool init_router(); + +void cleanup(); + +void print_msg( + BACMSG *msg); + +uint16_t process_msg( + BACMSG *msg, + MSG_DATA *data, + uint8_t **buff); + +uint16_t get_next_free_dnet(); + +int kbhit(); + +inline bool is_network_msg( + BACMSG *msg); + +int main( + int argc, + char *argv[]) +{ + printf("I am router\n"); + + ROUTER_PORT *port; + BACMSG msg_storage, *bacmsg = NULL; + MSG_DATA *msg_data = NULL; + uint8_t *buff = NULL; + int16_t buff_len = 0; + + atexit(cleanup); + + if (!parse_cmd(argc, argv)) + { + printf("parse cmd failed\r\n"); + return -1; + } + + if(!init_router()) + { + printf("init_router failed\r\n"); + return -1; + } + + + send_network_message(NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK, msg_data, &buff, NULL); + + while (true) { + if(kbhit()) { + char ch = getchar(); + if (ch == KEY_ESC) { + PRINT(INFO, "Received shutdown. Exiting...\n"); + break; + } + } + + bacmsg = recv_from_msgbox(head->main_id, &msg_storage); + if (bacmsg) { + switch (bacmsg->type) { + case DATA: + { + MSGBOX_ID msg_src = bacmsg->origin; + + // allocate message structure + msg_data = malloc(sizeof(MSG_DATA)); + if (!msg_data) { + PRINT(ERROR, "Error: Could not allocate memory\n"); + break; + } + + print_msg(bacmsg); + + if (is_network_msg(bacmsg)) { + buff_len = process_network_message(bacmsg, msg_data, &buff); + if (buff_len == 0) { + free_data(bacmsg->data); + break; + } + } else { + buff_len = process_msg(bacmsg, msg_data, &buff); + } + + // if buff_len + // >0 - form new message and send + // =-1 - try to find next router + // other value - discard message + + if (buff_len > 0) { + // form new message + msg_data->pdu = buff; + msg_data->pdu_len = buff_len; + msg_storage.origin = head->main_id; + msg_storage.type = DATA; + msg_storage.data = msg_data; + + print_msg(bacmsg); + + if (is_network_msg(bacmsg)) { + msg_data->ref_count = 1; + send_to_msgbox(msg_src, &msg_storage); + } else if (msg_data->dest.net != BACNET_BROADCAST_NETWORK) { + msg_data->ref_count = 1; + port = find_dnet(msg_data->dest.net, &msg_data->dest); + send_to_msgbox(port->port_id, &msg_storage); + } else { + port = head; + msg_data->ref_count = port_count - 1; + while (port != NULL) { + if (port->port_id == msg_src || port->state == FINISHED) { + port = port->next; + continue; + } + send_to_msgbox(port->port_id, &msg_storage); + port = port->next; + } + } + } else if (buff_len == -1) { + uint16_t net = msg_data->dest.net; // NET to find + PRINT(INFO, "Searching NET...\n"); + send_network_message(NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK, msg_data, &buff, &net); + } else { + // if invalid message send Reject-Message-To-Network + PRINT(ERROR, "Error: Invalid message\n"); + free_data(msg_data); + } + } + break; + case SERVICE: + default: + break; + } + } + } + + return 0; + +} + +void print_help() +{ + printf("Usage: router [init_parameters]\n" + "\ninit_method:\n" + "-c, --config \n\tinitialize router with a configuration file (.cfg) located at \n" + "-D, --device [params]\n\tinitialize a device using an interface specified with\n\t[params]\n" + "\ninit_parameters:\n" + "-n, --network \n\tspecify device network number\n" + "-P, --port \n\tspecify udp port for BIP device\n" + "-m, --mac [max_master] [max_frames]\n\tspecify MSTP port parameters\n" + "-b, --baud \n\tspecify MSTP port baud rate\n" + "-p, --parity \n\tspecify MSTP port parity\n" + "-d, --databits <5|6|7|8>\n\tspecify MSTP port databits\n" + "-s, --stopbits <1|2>\n\tspecify MSTP port stopbits\n"); +} + +bool read_config( + char *filepath) +{ + config_t cfg; + config_setting_t *setting; + ROUTER_PORT *current = head; + int result, fd; + + config_init(&cfg); + + // open configuration file + if (!config_read_file(&cfg, filepath)) { + PRINT(ERROR, "Config file error: %d - %s\n", config_error_line(&cfg), config_error_text(&cfg)); + config_destroy(&cfg); + return false; + } + + // get router "port" count + setting = config_lookup(&cfg, "ports"); + if (setting != NULL) { + int count = config_setting_length(setting); + int i; + + // lookup and initialize router "port" parameters + for (i = 0; i < count; i++) { + const char *dev_type; + const char *iface; + long int param; + const char *str_param; + config_setting_t *port = config_setting_get_elem(setting, i); + + // create new list node to store port information + if (head == NULL) { + head = (ROUTER_PORT*)malloc(sizeof(ROUTER_PORT)); + head->next = NULL; + current = head; + } else { + ROUTER_PORT *tmp = current; + current = current->next; + current = (ROUTER_PORT*)malloc(sizeof(ROUTER_PORT)); + current->next = NULL; + tmp->next = current; + } + + port_count++; + config_setting_lookup_string(port, "device_type", &dev_type); + printf("dev_type = %s\r\n", dev_type); + if (strcmp(dev_type, "bip") == 0) { + current->type = BIP; + + result = config_setting_lookup_string(port, "device", &iface); + if (result) { + current->iface = (char*)malloc((strlen(iface) + 1)*sizeof(char)); + strcpy(current->iface, iface); + + // check if interface is valid + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd) { + struct ifreq ifr; + strncpy(ifr.ifr_name, current->iface, sizeof(ifr.ifr_name)-1); + result = ioctl(fd, SIOCGIFADDR, &ifr); + if (result != -1) { + close(fd); + } else { + PRINT(ERROR, "Error: Invalid interface for BIP device\n"); + return false; + } + } + } else { + current->iface = "eth0"; + } + + result = config_setting_lookup_int(port, "port", (int*)¶m); + if (result) { + current->params.bip_params.port = param; + } else { + current->params.bip_params.port = 0xBAC0; + } + result = config_setting_lookup_int(port, "network", (int*)¶m); + if (result) { + current->route_info.net = param; + } else { + current->route_info.net = get_next_free_dnet(); + } + + } else if (strcmp(dev_type, "mstp") == 0) { + current->type = MSTP; + + result = config_setting_lookup_string(port, "device", &iface); + if (result) { + current->iface = (char*)malloc((strlen(iface) + 1)*sizeof(char)); + strcpy(current->iface, iface); + + // check if interface is valid + fd = open(current->iface, O_NOCTTY | O_NONBLOCK); + if (fd != -1) { + close(fd); + } else { + PRINT(ERROR, "Error: Invalid interface for MSTP device\n"); + return false; + } + } else { + current->iface = "/dev/ttyS0"; + } + result = config_setting_lookup_int(port, "mac", (int*)¶m); + if (result) { + current->route_info.mac[0] = param; + current->route_info.mac_len = 1; + } else { + current->route_info.mac[0] = 127; + current->route_info.mac_len = 1; + } + result = config_setting_lookup_int(port, "max_master", (int*)¶m); + if (result) { + current->params.mstp_params.max_master = param; + } else { + current->params.mstp_params.max_master = 127; + } + result = config_setting_lookup_int(port, "max_frames", (int*)¶m); + if (result) { + current->params.mstp_params.max_frames = param; + } else { + current->params.mstp_params.max_frames = 1; + } + result = config_setting_lookup_int(port, "baud", (int*)¶m); + if (result) { + current->params.mstp_params.baudrate = param; + } else { + current->params.mstp_params.baudrate = 9600; + } + result = config_setting_lookup_string(port, "parity", &str_param); + if (result) { + switch(str_param[0]){ + case 'E': + current->params.mstp_params.parity = PARITY_EVEN; + break; + case 'O': + current->params.mstp_params.parity = PARITY_ODD; + break; + default: + current->params.mstp_params.parity = PARITY_NONE; + break; + } + } else { + current->params.mstp_params.parity = PARITY_NONE; + } + result = config_setting_lookup_int(port, "databits", (int*)¶m); + if (result && param >= 5 && param <= 8) { + current->params.mstp_params.databits = param; + } else { + current->params.mstp_params.databits = 8; + } + result = config_setting_lookup_int(port, "stopbits", (int*)¶m); + if (result && param >= 1 && param <= 2) { + current->params.mstp_params.stopbits = param; + } else { + current->params.mstp_params.stopbits = 1; + } + result = config_setting_lookup_int(port, "network", (int*)¶m); + if (result) { + current->route_info.net = param; + } else { + current->route_info.net = get_next_free_dnet(); + } + + } else { + PRINT(ERROR, "Error: %s unsuported\n", dev_type); + return false; + } + } + } + else { + config_destroy(&cfg); + return false; + } + + config_destroy(&cfg); + printf("cmd file parse success\r\n"); + return true; +} + +bool parse_cmd( + int argc, + char *argv[]) +{ + const char *optString = "hc:D:"; + const char *bipString = "p:n:D:"; + const char *mstpString = "m:b:p:d:s:n:D:"; + const struct option Options[] = { + { "config", required_argument, NULL, 'c' }, + { "device", required_argument, NULL, 'D' }, + { "network", required_argument, NULL, 'n' }, + { "port", required_argument, NULL, 'P' }, + { "mac", required_argument, NULL, 'm' }, + { "baud", required_argument, NULL, 'b' }, + { "parity", required_argument, NULL, 'p' }, + { "databits", required_argument, NULL, 'd' }, + { "stopbits", required_argument, NULL, 's' }, + { "help", no_argument, NULL, 'h' }, + { NULL, no_argument, NULL, 0 }, + }; + + int opt, dev_opt, index, result, fd; + ROUTER_PORT *current = head; + + if (argc < 2) + print_help(); + + // begin checking cmd parameters + opt = getopt_long(argc, argv, optString, Options, &index); + printf("opt = %c\r\n", opt); + while (opt != -1) { + switch(opt) { + case 'h': + print_help(); + return false; + break; + case 'c': + return read_config(optarg); + break; + case 'D': + + // create new list node to store port information + if (head == NULL) { + head = (ROUTER_PORT*)malloc(sizeof(ROUTER_PORT)); + head->next = NULL; + current = head; + } else { + ROUTER_PORT *tmp = current; + current = current->next; + current = (ROUTER_PORT*)malloc(sizeof(ROUTER_PORT)); + current->next = NULL; + tmp->next = current; + } + + port_count++; + if (strcmp(optarg, "bip") == 0) { + current->type = BIP; + + if (optind < argc && argv[optind][0] != '-') { + current->iface = argv[optind]; + } else { + current->iface = "eth0"; + } + + // setup default parameters + current->params.bip_params.port = 0xBAC0; // 47808 + current->route_info.net = get_next_free_dnet(); + + // check if interface is valid + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd) { + struct ifreq ifr; + strncpy(ifr.ifr_name, current->iface, sizeof(ifr.ifr_name)-1); + result = ioctl(fd, SIOCGIFADDR, &ifr); + if (result != -1) { + close(fd); + } else { + PRINT(ERROR, "Error: Invalid interface for BIP device \n"); + return false; + } + } + + dev_opt = getopt_long(argc, argv, bipString, Options, &index); + while (dev_opt != -1 && dev_opt != 'd') { + switch(dev_opt) { + case 'P': + result = atoi(optarg); + if (result) { + current->params.bip_params.port = (uint16_t)result; + } else { + current->params.bip_params.port = 0xBAC0; // 47808 + } + break; + case 'n': + result = atoi(optarg); + if (result) { + current->route_info.net = (uint16_t)result; + } else { + current->route_info.net = port_count; + } + break; + } + dev_opt = getopt_long(argc, argv, bipString, Options, &index); + } + opt = dev_opt; + } else if (strcmp(optarg, "mstp") == 0) { + current->type = MSTP; + + if (optind < argc && argv[optind][0] != '-') { + current->iface = argv[optind]; + } else { + current->iface = "/dev/ttyS0"; + } + + // check if interface is valid + fd = open(current->iface, O_NOCTTY | O_NONBLOCK); + if (fd != -1) { + close(fd); + } else { + PRINT(ERROR, "Error: Invalid interface for MSTP device\n"); + return false; + } + + // setup default parameters + current->route_info.mac[0] = 127; + current->route_info.mac_len = 1; + current->params.mstp_params.max_master = 127; + current->params.mstp_params.max_frames = 1; + current->params.mstp_params.baudrate = 9600; + current->params.mstp_params.parity = PARITY_NONE; + current->params.mstp_params.databits = 8; + current->params.mstp_params.stopbits = 1; + current->route_info.net = get_next_free_dnet(); + + dev_opt = getopt_long(argc, argv, mstpString, Options, &index); + while (dev_opt != -1 && dev_opt != 'D') { + switch(dev_opt) { + case 'm': + result = atoi(optarg); + if (result) { + current->route_info.mac[0] = (uint8_t)result; + } + if (argv[optind][0] != '-') { + current->params.mstp_params.max_master = (uint8_t)atoi(argv[optind]); + if (current->params.mstp_params.max_master < current->route_info.mac[0]) + current->params.mstp_params.max_master = current->route_info.mac[0]; + + if (argv[optind+1][0] != '-') { + current->params.mstp_params.max_frames = (uint8_t)atoi(argv[optind+1]); + } + } + break; + case 'b': + result = atoi(optarg); + if (result) { + current->params.mstp_params.baudrate = (uint32_t)result; + } + break; + case 'p': + switch(optarg[0]){ + case 'E': + current->params.mstp_params.parity = PARITY_EVEN; + break; + case 'O': + current->params.mstp_params.parity = PARITY_ODD; + break; + default: + current->params.mstp_params.parity = PARITY_NONE; + break; + } + break; + case 'd': + result = atoi(optarg); + if (result >= 5 && result <= 8) { + current->params.mstp_params.databits = (uint8_t)result; + } + break; + case 's': + result = atoi(optarg); + if (result >= 1 && result <= 2) { + current->params.mstp_params.stopbits = (uint8_t)result; + } + break; + case 'n': + result = atoi(optarg); + if (result) { + current->route_info.net = (uint16_t)result; + } + break; + } + dev_opt = getopt_long(argc, argv, mstpString, Options, &index); + } + opt = dev_opt; + } else { + PRINT(ERROR, "Error: %s unknown\n", optarg); + return false; + } + break; + } + } + return true; +} + +void init_port_threads( + ROUTER_PORT* port_list) { + ROUTER_PORT *port = port_list; + pthread_t * thread; + + while (port != NULL) { + switch (port->type) { + case BIP: + port->func = &dl_ip_thread; + break; + case MSTP: + port->func = &dl_mstp_thread; + break; + } + + port->state = INIT; + thread = (pthread_t *)malloc(sizeof(pthread_t)); + pthread_create(thread, + NULL, + port->func, + port); + + pthread_detach(*thread); // for proper thread termination + + port = port->next; + } +} + +bool init_router() { + MSGBOX_ID msgboxid; + ROUTER_PORT *port; + + msgboxid = create_msgbox(); + if (msgboxid == INVALID_MSGBOX_ID) + return false; + + port = head; + // add main message box id to all ports + while (port != NULL) { + port->main_id = msgboxid; + port = port->next; + } + + init_port_threads(head); + + // wait for port initialization + port = head; + while (port != NULL) { + if (port->state == RUNNING) { + port = port->next; + continue; + } else if (port->state == INIT_FAILED) { + PRINT(ERROR, "Error: Failed to initialize %s\n", port->iface); + return false; + } else { + PRINT(INFO, "Initializing...\n"); + sleep(1); + continue; + } + } + + return true; +} + +void cleanup() { + ROUTER_PORT *port; + BACMSG msg; + + if (head == NULL) + return; + + msg.origin = head->main_id; + msg.type = SERVICE; + msg.subtype = SHUTDOWN; + + del_msgbox(head->main_id); // close routers message box + + // send shutdown message to all router ports + port = head; + while (port != NULL) { + if (port->state == RUNNING) + send_to_msgbox(port->port_id, &msg); + port = port->next; + } + + port = head; + while (port != NULL) { + if (port->state == FINISHED) { + cleanup_dnets(port->route_info.dnets); + port = port->next; + free(head->iface); + free(head); + head = port; + } + } + + pthread_mutex_destroy(&msg_lock); +} + +void print_msg(BACMSG *msg) { + if (msg->type == DATA) { + int i; + MSG_DATA *data = (MSG_DATA*)msg->data; + + if (data->pdu_len) { + PRINT(DEBUG, "Message PDU: "); + for (i = 0; i < data->pdu_len; i++) + PRINT(DEBUG, "%02X ",data->pdu[i]); + PRINT(DEBUG, "\n"); + } + } +} + +uint16_t process_msg(BACMSG *msg, + MSG_DATA *data, + uint8_t **buff) { + + BACNET_ADDRESS addr; + BACNET_NPDU_DATA npdu_data; + ROUTER_PORT *srcport; + ROUTER_PORT *destport; + uint8_t npdu[MAX_NPDU]; + int16_t buff_len = 0; + int apdu_offset; + int apdu_len; + int npdu_len; + + memmove(data, msg->data, sizeof(MSG_DATA)); + + apdu_offset = npdu_decode(data->pdu, &data->dest, &addr, &npdu_data); + apdu_len = data->pdu_len - apdu_offset; + + srcport = find_snet(msg->origin); + destport = find_dnet(data->dest.net, NULL); + assert(srcport); + + if (srcport && destport) { + data->src.net = srcport->route_info.net; + + // if received from another router save real source address (not other router source address) + if (addr.net > 0 && addr.net < BACNET_BROADCAST_NETWORK && data->src.net != addr.net) + memmove(&data->src, &addr, sizeof(BACNET_ADDRESS)); + + // encode both source and destination for broadcast and router-to-router communication + if (data->dest.net == BACNET_BROADCAST_NETWORK || destport->route_info.net != data->dest.net) { + npdu_len = npdu_encode_pdu(npdu, &data->dest, &data->src, &npdu_data); + } + else { + npdu_len = npdu_encode_pdu(npdu, NULL, &data->src, &npdu_data); + } + + buff_len = npdu_len + data->pdu_len - apdu_offset; + + *buff = (uint8_t*)malloc(buff_len); + memmove(*buff, npdu, npdu_len); // copy newly formed NPDU + memmove(*buff+npdu_len, &data->pdu[apdu_offset], apdu_len); // copy APDU + + } else { + // request net search + return -1; + } + + // delete received message + free_data((MSG_DATA*)msg->data); + + return buff_len; +} + +int kbhit() +{ + static const int STDIN = 0; + static bool initialized = false; + + if (! initialized) { + // use termios to turn off line buffering + struct termios term; + tcgetattr(STDIN, &term); + term.c_lflag &= ~ICANON; + tcsetattr(STDIN, TCSANOW, &term); + setbuf(stdin, NULL); + initialized = true; + } + + int bytesWaiting; + ioctl(STDIN, FIONREAD, &bytesWaiting); + return bytesWaiting; +} + +bool is_network_msg(BACMSG *msg) { + + uint8_t control_byte; // NPDU control byte + MSG_DATA *data = (MSG_DATA*)msg->data; + + control_byte = data->pdu[1]; + + return control_byte & 0x80; // check 7th bit +} + +uint16_t get_next_free_dnet() { + + ROUTER_PORT * port = head; + uint16_t i = 1; + while(port) { + if(port->route_info.net == i) { + port = head; + i++; + continue; + } + + port = port->next; + } + return i; +} + diff --git a/bacnet-stack/demo/router/msgqueue.c b/bacnet-stack/demo/router/msgqueue.c new file mode 100644 index 00000000..3aa738a1 --- /dev/null +++ b/bacnet-stack/demo/router/msgqueue.c @@ -0,0 +1,95 @@ +/* +Copyright (C) 2012 Andriy Sukhynyuk, Vasyl Tkhir, Andriy Ivasiv + + 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 3 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, see . +*/ +#include +#include +#include +#include +#include +#include "msgqueue.h" + +pthread_mutex_t msg_lock = PTHREAD_MUTEX_INITIALIZER; + +MSGBOX_ID create_msgbox() { + MSGBOX_ID msgboxid; + + msgboxid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT); + if (msgboxid == INVALID_MSGBOX_ID) { + return INVALID_MSGBOX_ID; + } + + return msgboxid; +} + +bool send_to_msgbox( + MSGBOX_ID dest, + BACMSG *msg) { + + int err; + + err = msgsnd(dest, msg, sizeof(BACMSG), 0); + if (err) { + return false; + } + return true; +} + +BACMSG* recv_from_msgbox( + MSGBOX_ID src, + BACMSG *msg) { + + int recv_bytes; + + recv_bytes = msgrcv(src, msg, sizeof(BACMSG), 0, IPC_NOWAIT); + if (recv_bytes > 0) { + return msg; + } else { + return NULL; + } +} + +void del_msgbox( + MSGBOX_ID msgboxid) { + + if (msgboxid == INVALID_MSGBOX_ID) + return; + else + msgctl(msgboxid, IPC_RMID, NULL); +} + +void free_data( + MSG_DATA *data) { + + if (data->pdu) { + free(data->pdu); + data->pdu = NULL; + } + if (data) { + free(data); + data = NULL; + } +} + +void check_data( + MSG_DATA *data) { + + // lock and decrement messages reference count + pthread_mutex_lock(&msg_lock); + if (--data->ref_count == 0) { + free_data(data); + } + pthread_mutex_unlock(&msg_lock); +} diff --git a/bacnet-stack/demo/router/msgqueue.h b/bacnet-stack/demo/router/msgqueue.h new file mode 100644 index 00000000..cdf97b80 --- /dev/null +++ b/bacnet-stack/demo/router/msgqueue.h @@ -0,0 +1,85 @@ +/* +Copyright (C) 2012 Andriy Sukhynyuk, Vasyl Tkhir, Andriy Ivasiv + + 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 3 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, see . +*/ +#ifndef MSGQUEUE_H +#define MSGQUEUE_H + +#include +#include +#include +#include +#include +#include "bacdef.h" +#include "npdu.h" + +extern pthread_mutex_t msg_lock; + +#define INVALID_MSGBOX_ID -1 + +typedef int MSGBOX_ID; + +typedef enum { + DATA, + SERVICE +} MSGTYPE; + +typedef enum { + SHUTDOWN, + CHG_IP, + CHG_MAC +} MSGSUBTYPE; + +typedef struct _message { + MSGBOX_ID origin; + MSGTYPE type; + MSGSUBTYPE subtype; + void *data; + // add timestamp +} BACMSG; + +// specific message type data structures +typedef struct _msg_data { + BACNET_ADDRESS dest; + BACNET_ADDRESS src; + uint8_t *pdu; + uint16_t pdu_len; + uint8_t ref_count; +} MSG_DATA; + +MSGBOX_ID create_msgbox(); + +// returns sent byte count +bool send_to_msgbox( + MSGBOX_ID dest, + BACMSG *msg); + +// returns received message +BACMSG* recv_from_msgbox( + MSGBOX_ID src, + BACMSG *msg); + +void del_msgbox( + MSGBOX_ID msgboxid); + +// free message data structure +void free_data( + MSG_DATA *data); + +// check message reference counter and delete data if needed +void check_data( + MSG_DATA *data); + +#endif /* end of MSGQUEUE_H */ diff --git a/bacnet-stack/demo/router/mstpmodule.c b/bacnet-stack/demo/router/mstpmodule.c new file mode 100644 index 00000000..87bce959 --- /dev/null +++ b/bacnet-stack/demo/router/mstpmodule.c @@ -0,0 +1,168 @@ +/* +Copyright (C) 2012 Andriy Sukhynyuk, Vasyl Tkhir, Andriy Ivasiv + + 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 3 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, see . +*/ +#include +#include +#include +#include "mstpmodule.h" +#include "bacint.h" +#include "dlmstp_linux.h" +#include + +#define MSTP_THREAD_PRINT_ENABLED +#ifdef MSTP_THREAD_PRINT_ENABLED +#define mstp_thread_debug(...) fprintf(stderr, __VA_ARGS__) +#else +#define mstp_thread_debug(...) +#endif + +void* dl_mstp_thread(void* pArgs) { + + ROUTER_PORT *port = (ROUTER_PORT*)pArgs; + struct mstp_port_struct_t mstp_port = {(MSTP_RECEIVE_STATE)0}; + volatile SHARED_MSTP_DATA shared_port_data = {0}; + uint16_t pdu_len; + uint8_t shutdown = 0; + + shared_port_data.Treply_timeout = 260; + shared_port_data.MSTP_Packets = 0; + shared_port_data.Tusage_timeout = 50; + shared_port_data.RS485_Handle = -1; + shared_port_data.RS485_Baud = B38400; + shared_port_data.RS485MOD = 0; + + switch(port->params.mstp_params.databits){ + case 5: + shared_port_data.RS485MOD = CS5; + break; + case 6: + shared_port_data.RS485MOD = CS6; + break; + case 7: + shared_port_data.RS485MOD = CS7; + break; + default: + shared_port_data.RS485MOD = CS8; + break; + } + + switch(port->params.mstp_params.parity){ + case PARITY_EVEN: + shared_port_data.RS485MOD |= PARENB; + break; + case PARITY_ODD: + shared_port_data.RS485MOD |= PARENB | PARODD; + break; + default: + break; + } + + if(port->params.mstp_params.stopbits == 2) + shared_port_data.RS485MOD |= CSTOPB; + + mstp_port.UserData = (void*) &shared_port_data; + dlmstp_set_baud_rate(&mstp_port, port->params.mstp_params.baudrate); + dlmstp_set_mac_address(&mstp_port, port->route_info.mac[0]); + dlmstp_set_max_info_frames(&mstp_port, port->params.mstp_params.max_frames); + dlmstp_set_max_master(&mstp_port, port->params.mstp_params.max_master); + if(!dlmstp_init(&mstp_port, port->iface)) + printf("MSTP %s init failed. Stop.\n", port->iface); + + port->port_id = create_msgbox(); + if (port->port_id == INVALID_MSGBOX_ID) { + port->state = INIT_FAILED; + return NULL; + } + + port->state = RUNNING; + + while (!shutdown) { + // message loop + BACMSG msg_storage, * bacmsg; + MSG_DATA * msg_data; + + bacmsg = recv_from_msgbox(port->port_id, &msg_storage); + + if(bacmsg) + { + switch(bacmsg->type) + { + case DATA: + msg_data = (MSG_DATA*) bacmsg->data; + + if(msg_data->dest.net == BACNET_BROADCAST_NETWORK) + { + dlmstp_get_broadcast_address(&(msg_data->dest)); + } + else + { + msg_data->dest.mac[0] = msg_data->dest.adr[0]; + msg_data->dest.mac_len = 1; + } + + dlmstp_send_pdu(&mstp_port, &(msg_data->dest), msg_data->pdu, msg_data->pdu_len); + + check_data(msg_data); + + + break; + case SERVICE: + switch(bacmsg->subtype) { + case SHUTDOWN: + shutdown = 1; + break; + default: + break; + } + break; + default: + continue; + break; + } + } + else + { + pdu_len = dlmstp_receive(&mstp_port, NULL, NULL, 0, 1000); + + if(pdu_len > 0) + { + msg_data = (MSG_DATA*)malloc(sizeof(MSG_DATA)); + memmove(&(msg_data->src), (const void*)&(shared_port_data.Receive_Packet.address), sizeof(shared_port_data.Receive_Packet.address)); + msg_data->src.adr[0] = msg_data->src.mac[0]; + msg_data->src.len = 1; + msg_data->pdu = (uint8_t*)malloc(pdu_len); + memmove(msg_data->pdu, (const void*)&(shared_port_data.Receive_Packet.pdu), pdu_len); + msg_data->pdu_len = pdu_len; + + msg_storage.type = DATA; + msg_storage.subtype = (MSGSUBTYPE)0; + msg_storage.origin = port->port_id; + msg_storage.data = msg_data; + + if(!send_to_msgbox(port->main_id, &msg_storage)) + { + free_data(msg_data); + } + } + } + } + + dlmstp_cleanup(&mstp_port); + port->state = FINISHED; + + return NULL; + +} diff --git a/bacnet-stack/demo/router/mstpmodule.h b/bacnet-stack/demo/router/mstpmodule.h new file mode 100644 index 00000000..44a3ff85 --- /dev/null +++ b/bacnet-stack/demo/router/mstpmodule.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2012 Andriy Sukhynyuk, Vasyl Tkhir, Andriy Ivasiv + + 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 3 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, see . +*/ +#ifndef MSTPMODULE_H +#define MSTPMODULE_H + +#include "portthread.h" + +void* dl_mstp_thread( + void* pArgs); + +#endif /* end of MSTPMODULE_H */ diff --git a/bacnet-stack/demo/router/network_layer.c b/bacnet-stack/demo/router/network_layer.c new file mode 100644 index 00000000..1648fc55 --- /dev/null +++ b/bacnet-stack/demo/router/network_layer.c @@ -0,0 +1,311 @@ +/* +Copyright (C) 2012 Andriy Sukhynyuk, Vasyl Tkhir, Andriy Ivasiv + + 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 3 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, see . +*/ +#include +#include +#include +#include "network_layer.h" +#include "bacint.h" + +uint16_t process_network_message( + BACMSG *msg, + MSG_DATA *data, + uint8_t **buff) { + + BACNET_NPDU_DATA npdu_data; + ROUTER_PORT *srcport; + ROUTER_PORT *destport; + uint16_t net; + uint8_t error_code; + int16_t buff_len = 0; + int apdu_offset; + int apdu_len; + + memmove(data, msg->data, sizeof(MSG_DATA)); + + apdu_offset = npdu_decode(data->pdu, &data->dest, NULL, &npdu_data); + apdu_len = data->pdu_len - apdu_offset; + + srcport = find_snet(msg->origin); + data->src.net = srcport->route_info.net; + + switch (npdu_data.network_message_type) { + + case NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK: + PRINT(INFO, "Recieved Who-Is-Router-To-Network message\n"); + if (apdu_len) { + // if NET specified + decode_unsigned16(&data->pdu[apdu_offset], &net); + if (srcport->route_info.net == net) { + PRINT(INFO, "Message discarded: NET directly connected\n"); + return -2; + } + + destport = find_dnet(net, NULL); // see if NET can be reached + if (destport) { + // if TRUE send reply + PRINT(INFO, "Sending I-Am-Router-To-Network message\n"); + buff_len = create_network_message(NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK, + data, buff, &net); + } else { + data->dest.net = net; // NET to look for + return -1; // else initiate NET search procedure + } + } else { + // if NET is omitted (message sent with -1) + PRINT(INFO, "Sending I-Am-Router-To-Network message\n"); + buff_len = create_network_message(NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK, + data, buff, NULL); + } + + break; + + case NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK: + { + PRINT(INFO, "Recieved I-Am-Router-To-Network message\n"); + int net_count = apdu_len / 2; + int i; + for (i = 0; i < net_count; i++) { + decode_unsigned16(&data->pdu[apdu_offset+2*i], &net); // decode received NET values + add_dnet(&srcport->route_info, net, data->src); // and update routing table + } + break; + } + case NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK: + { + // first octet of the message contains rejection reason + // next two octets contain NET (can be decoded for additional info on error) + error_code = data->pdu[apdu_offset]; + switch (error_code) { + case 0: + PRINT(ERROR, "Error!\n"); + break; + case 1: + PRINT(ERROR, "Error: Network unreachable\n"); + break; + case 2: + PRINT(ERROR, "Error: Network is busy\n"); + break; + case 3: + PRINT(ERROR, "Error: Unknown network message type\n"); + break; + case 4: + PRINT(ERROR, "Error: Message too long\n"); + break; + } + break; + } + case NETWORK_MESSAGE_INIT_RT_TABLE: + PRINT(INFO, "Recieved Initialize-Routing-Table message\n"); + if (data->pdu[apdu_offset] > 0) { + int net_count = data->pdu[apdu_offset]; + while (net_count--) { + int i = 1; + decode_unsigned16(&data->pdu[apdu_offset+i], &net); // decode received NET values + add_dnet(&srcport->route_info, net, data->src); // and update routing table + if (data->pdu[apdu_offset+i+3] > 0) // find next NET value + i = data->pdu[apdu_offset+i+3] + 4; + else + i = i + 4; + } + buff_len = create_network_message(NETWORK_MESSAGE_INIT_RT_TABLE_ACK, data, buff, NULL); + } else + buff_len = create_network_message(NETWORK_MESSAGE_INIT_RT_TABLE_ACK, data, buff, &buff); + break; + + case NETWORK_MESSAGE_INIT_RT_TABLE_ACK: + PRINT(INFO, "Recieved Initialize-Routing-Table-Ack message\n"); + if (data->pdu[apdu_offset] > 0) { + int net_count = data->pdu[apdu_offset]; + while (net_count--) { + int i = 1; + decode_unsigned16(&data->pdu[apdu_offset+i], &net); // decode received NET values + add_dnet(&srcport->route_info, net, data->src); // and update routing table + if (data->pdu[apdu_offset+i+3] > 0) // find next NET value + i = data->pdu[apdu_offset+i+3] + 4; + else + i = i + 4; + } + } + break; + + case NETWORK_MESSAGE_INVALID: + case NETWORK_MESSAGE_I_COULD_BE_ROUTER_TO_NETWORK: + case NETWORK_MESSAGE_ROUTER_BUSY_TO_NETWORK: + case NETWORK_MESSAGE_ROUTER_AVAILABLE_TO_NETWORK: + case NETWORK_MESSAGE_ESTABLISH_CONNECTION_TO_NETWORK: + case NETWORK_MESSAGE_DISCONNECT_CONNECTION_TO_NETWORK: + // hell if I know what to do with these messages + break; + + default: + PRINT(ERROR, "Error: Message unsupported\n"); + break; + } + + return buff_len; +} + +uint16_t create_network_message( + BACNET_NETWORK_MESSAGE_TYPE network_message_type, + MSG_DATA *data, + uint8_t **buff, + void *val) { + + int16_t buff_len; + bool data_expecting_reply = false; + BACNET_NPDU_DATA npdu_data; + + if (network_message_type == NETWORK_MESSAGE_INIT_RT_TABLE) + data_expecting_reply = true; + init_npdu(&npdu_data, network_message_type, data_expecting_reply); + + *buff = (uint8_t*)malloc(128); // resolve different length + + // manual destination setup for Init-RT-Table-Ack message + data->dest.net = BACNET_BROADCAST_NETWORK; + buff_len = npdu_encode_pdu(*buff, &data->dest, NULL, &npdu_data); + + switch (network_message_type) { + + case NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK: + if (val != NULL) { + uint8_t * valptr = (uint8_t*)val; + uint16_t val16 = (valptr[0]) + (valptr[1] << 8); + buff_len += encode_unsigned16(*buff+buff_len, val16); + } + break; + + case NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK: + if (val != NULL) { + uint8_t * valptr = (uint8_t*)val; + uint16_t val16 = (valptr[0]) + (valptr[1] << 8); + buff_len += encode_unsigned16(*buff+buff_len, val16); + } + else { + ROUTER_PORT *port = head; + DNET *dnet; + while (port != NULL) { + if (port->route_info.net != data->src.net) { + buff_len += encode_unsigned16(*buff+buff_len, port->route_info.net); + dnet = port->route_info.dnets; + while (dnet != NULL) { + buff_len += encode_unsigned16(*buff+buff_len, dnet->net); + dnet = dnet->next; + } + port = port->next; + } else { + dnet = port->route_info.dnets; + while (dnet != NULL) { + buff_len += encode_unsigned16(*buff+buff_len, dnet->net); + dnet = dnet->next; + } + port = port->next; + } + } + } + break; + + case NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK: + { + uint8_t * valptr = (uint8_t*)val; + uint16_t val16 = (valptr[0]) + (valptr[1] << 8); + buff_len += encode_unsigned16(*buff + buff_len, val16); + break; + } + case NETWORK_MESSAGE_INIT_RT_TABLE: + case NETWORK_MESSAGE_INIT_RT_TABLE_ACK: + if ((uint8_t*)val) { + (*buff)[buff_len++] = (uint8_t) port_count; + + if (port_count > 0) { + ROUTER_PORT *port = head; + uint8_t portID = 1; + + while (port != NULL) { + buff_len += encode_unsigned16(*buff+buff_len, port->route_info.net); + (*buff)[buff_len++] = portID++; + (*buff)[buff_len++] = 0; + port = port->next; + } + } + } else + (*buff)[buff_len++] = (uint8_t) 0; + break; + + case NETWORK_MESSAGE_INVALID: + case NETWORK_MESSAGE_I_COULD_BE_ROUTER_TO_NETWORK: + case NETWORK_MESSAGE_ROUTER_BUSY_TO_NETWORK: + case NETWORK_MESSAGE_ROUTER_AVAILABLE_TO_NETWORK: + case NETWORK_MESSAGE_ESTABLISH_CONNECTION_TO_NETWORK: + case NETWORK_MESSAGE_DISCONNECT_CONNECTION_TO_NETWORK: + // hell if I know what to do with these messages + break; + } + + return buff_len; +} + +void send_network_message( + BACNET_NETWORK_MESSAGE_TYPE network_message_type, + MSG_DATA *data, + uint8_t **buff, + void *val) { + + BACMSG msg; + ROUTER_PORT *port = head; + int16_t buff_len; + + if (!data) { + data = (MSG_DATA*)malloc(sizeof(MSG_DATA)); + data->dest.net = BACNET_BROADCAST_NETWORK; + } + + buff_len = create_network_message(network_message_type, data, buff, val); + + // form network message + data->pdu = *buff; + data->pdu_len = buff_len; + msg.origin = head->main_id; + msg.type = DATA; + msg.data = data; + + data->ref_count = port_count; + while (port != NULL) { + if (port->state == FINISHED) { + port = port->next; + continue; + } + send_to_msgbox(port->port_id, &msg); + port = port->next; + } +} + +void init_npdu( + BACNET_NPDU_DATA *npdu_data, + BACNET_NETWORK_MESSAGE_TYPE network_message_type, + bool data_expecting_reply) { + + if (npdu_data) { + npdu_data->data_expecting_reply = data_expecting_reply; + npdu_data->protocol_version = BACNET_PROTOCOL_VERSION; + npdu_data->network_layer_message = true; + npdu_data->network_message_type = network_message_type; + npdu_data->vendor_id = 0; + npdu_data->priority = MESSAGE_PRIORITY_NORMAL; + npdu_data->hop_count = HOP_COUNT_DEFAULT; + } +} diff --git a/bacnet-stack/demo/router/network_layer.h b/bacnet-stack/demo/router/network_layer.h new file mode 100644 index 00000000..a8ae428c --- /dev/null +++ b/bacnet-stack/demo/router/network_layer.h @@ -0,0 +1,53 @@ +/* +Copyright (C) 2012 Andriy Sukhynyuk, Vasyl Tkhir, Andriy Ivasiv + + 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 3 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, see . +*/ +#ifndef NETWORK_LAYER_H +#define NETWORK_LAYER_H + +#include +#include +#include +#include +#include +#include "bacenum.h" +#include "bacdef.h" +#include "npdu.h" +#include "net.h" +#include "portthread.h" + +uint16_t process_network_message( + BACMSG *msg, + MSG_DATA *data, + uint8_t **buff); + +uint16_t create_network_message( + BACNET_NETWORK_MESSAGE_TYPE network_message_type, + MSG_DATA *data, + uint8_t **buff, + void *val); + +void send_network_message( + BACNET_NETWORK_MESSAGE_TYPE network_message_type, + MSG_DATA *data, + uint8_t **buff, + void *val); + +void init_npdu( + BACNET_NPDU_DATA *npdu_data, + BACNET_NETWORK_MESSAGE_TYPE network_message_type, + bool data_expecting_reply); + +#endif /* end of NETWORK_LAYER_H */ diff --git a/bacnet-stack/demo/router/portthread.c b/bacnet-stack/demo/router/portthread.c new file mode 100644 index 00000000..690b0b30 --- /dev/null +++ b/bacnet-stack/demo/router/portthread.c @@ -0,0 +1,116 @@ +/* +Copyright (C) 2012 Andriy Sukhynyuk, Vasyl Tkhir, Andriy Ivasiv + + 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 3 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, see . +*/ +#include +#include +#include +#include "portthread.h" + +ROUTER_PORT* find_snet( + MSGBOX_ID id) { + + ROUTER_PORT *port = head; + + while (port != NULL) { + if (port->port_id == id) + return port; + port = port->next; + } + + return NULL; +} + +ROUTER_PORT* find_dnet( + uint16_t net, + BACNET_ADDRESS *addr) { + + ROUTER_PORT *port = head; + DNET *dnet; + + // for broadcast messages no search is needed + if (net == BACNET_BROADCAST_NETWORK) + return port; + + while(port != NULL) { + + // check if DNET is directly connected to the router + if (net == port->route_info.net) + return port; + + // else search router ports DNET list + else if (port->route_info.dnets) { + dnet = port->route_info.dnets; + while (dnet != NULL) { + if (net == dnet->net) { + if (addr) { + memmove(&addr->len, &dnet->mac_len, 1); + memmove(&addr->adr[0], &dnet->mac[0], MAX_MAC_LEN); + } + return port; + } + dnet = dnet->next; + } + } + port = port->next; + } + + return NULL; +} + +void add_dnet( + RT_ENTRY *route_info, + uint16_t net, + BACNET_ADDRESS addr) { + + DNET *dnet = route_info->dnets; + DNET *tmp; + + if (dnet == NULL) { + route_info->dnets = (DNET*)malloc(sizeof(DNET)); + memmove(&route_info->dnets->mac_len, &addr.len, 1); + memmove(&route_info->dnets->mac[0], &addr.adr[0], MAX_MAC_LEN); + route_info->dnets->net = net; + route_info->dnets->state = true; + route_info->dnets->next = NULL; + } else { + + while (dnet != NULL) { + if (dnet->net == net) // make sure NETs are not repeated + return; + tmp = dnet; + dnet = dnet->next; + } + + dnet = (DNET*)malloc(sizeof(DNET)); + memmove(&dnet->mac_len, &addr.len, 1); + memmove(&dnet->mac[0], &addr.adr[0], MAX_MAC_LEN); + dnet->net = net; + dnet->state = true; + dnet->next = NULL; + tmp->next = dnet; + } +} + +void cleanup_dnets( + DNET *dnets) { + + DNET *dnet = dnets; + while(dnet != NULL) { + dnet = dnet->next; + free(dnets); + dnets = dnet; + } +} diff --git a/bacnet-stack/demo/router/portthread.h b/bacnet-stack/demo/router/portthread.h new file mode 100644 index 00000000..769fbaa6 --- /dev/null +++ b/bacnet-stack/demo/router/portthread.h @@ -0,0 +1,125 @@ +/* +Copyright (C) 2012 Andriy Sukhynyuk, Vasyl Tkhir, Andriy Ivasiv + + 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 3 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, see . +*/ +#ifndef PORTTHREAD_H +#define PORTTHREAD_H + +#include +#include +#include +#include "msgqueue.h" +#include "bacdef.h" +#include "npdu.h" + +#define ERROR 1 +#define INFO 2 +#define DEBUG 3 + +#define DEBUG_LEVEL 3 +#ifdef DEBUG_LEVEL +#define PRINT(debug_level, ...) if(debug_level <= DEBUG_LEVEL) fprintf(stderr, __VA_ARGS__) +#else +#define PRINT(...) +#endif + +typedef enum { + BIP = 1, + MSTP = 2 +} DL_TYPE; + +typedef enum { + INIT, + INIT_FAILED, + RUNNING, + FINISHED +} PORT_STATE; + +// router port thread function +typedef void* (*PORT_FUNC)( + void*); + +typedef enum { + PARITY_NONE, + PARITY_EVEN, + PARITY_ODD +}PARITY; + +// port specific parameters +typedef union _port_params { + struct { + uint16_t port; + } bip_params; + struct { + uint32_t baudrate; + PARITY parity; + uint8_t databits; + uint8_t stopbits; + uint8_t max_master; + uint8_t max_frames; + } mstp_params; +} PORT_PARAMS; + +// list node for reacheble networks +typedef struct _dnet { + uint8_t mac[MAX_MAC_LEN]; + uint8_t mac_len; + uint16_t net; + bool state; // enabled or disabled + struct _dnet *next; +} DNET; + +// information for routing table +typedef struct _routing_table_entry { + uint8_t mac[MAX_MAC_LEN]; + uint8_t mac_len; + uint16_t net; + DNET *dnets; +} RT_ENTRY; + +typedef struct _port { + DL_TYPE type; + PORT_STATE state; + MSGBOX_ID main_id; // same for every router port + MSGBOX_ID port_id; // different for every router port + char *iface; + PORT_FUNC func; + RT_ENTRY route_info; + PORT_PARAMS params; + struct _port *next; // pointer to next list node +} ROUTER_PORT; + +extern ROUTER_PORT *head; +extern int port_count; + +// get recieving router port +ROUTER_PORT* find_snet( + MSGBOX_ID id); + +// get sending router port +ROUTER_PORT* find_dnet( + uint16_t net, + BACNET_ADDRESS *addr); + +// add reacheble network for specified router port +void add_dnet( + RT_ENTRY *route_info, + uint16_t net, + BACNET_ADDRESS addr); + +void cleanup_dnets( + DNET *dnets); + +#endif /* end of PORTTHREAD_H */ diff --git a/bacnet-stack/demo/router/readme.txt b/bacnet-stack/demo/router/readme.txt new file mode 100644 index 00000000..32cc667a --- /dev/null +++ b/bacnet-stack/demo/router/readme.txt @@ -0,0 +1,138 @@ +----------------------- +1. About +----------------------- + +The Router connects two or more BACnet/IP and BACnet MS/TP networks. +Number of netwoks is limited only by available hardware communication devices (or ports for Ethernet). + +----------------------- +2. License +----------------------- + +Copyright (C) 2012 Andriy Sukhynyuk, Vasyl Tkhir, Andriy Ivasiv + +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 3 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, see . + + +----------------------- +3. Build +----------------------- + +1. Download, build and install libconfig C/C++ Configuration File Library from http://www.hyperrealm.com/libconfig +2. Set variable "BACNET_PORT" in library root diredory Makefile to linux +3. Run "make clean all" from library root directory + +----------------------- +4. Router configuration +----------------------- + +4.1. Configuration file format. + +//single line comment + +/* + multiline comment +*/ + +ports = +( + //route_1 + { + device_type = ""; + //route specific arguments, see below + }, + + //route_2 + { + device_type = ""; + //route specific arguments, see below + }, + + //..... + + //route_n + { + device_type = ""; + //route specific arguments, see below + } +); + +Note: - arguments are separeted with ';' + - routes are separeted with ',' + - no ',' after the last route + +4.2. Configuration file arguments. + +Common arguments: + device_type - Describes a type of route, may be "bip" (Etherent) or "mstp" (Serial port). Use quotes. + device - Connection device, for example "eth0" or "/dev/ttyS0"; default values: for BIP:"eth0", for MSTP: "/dev/ttyS0". Use quotes. + network - Network number [1..65534]. Do not use network number 65535, it is broadcast number; default begins from 1 to routes count. + +bip arguments: + port - bip UDP port; default port is 47808 (0xBAC0). + +mstp arguments: + mac - MSTP MAC; default value is 127. + max_master - MSTP max master; default value is 127. + max_frames - 1. Segmentation does not supported. + baud - one from the list: 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400; default baud is 9600 + parity - one from the list (with quotes): "None", "Even", "Odd"; default parity "None". Use quotes. + databits - one from the list: 5, 6, 7, 8; default 8. + stopbits - 1 or 2; default 1. + +4.3. Example of configuration file. + + ports = + ( + { + device_type = "bip"; + device = "eth0"; + port = 47808; + network = 1; + }, + { + device_type = "bip"; + device = "eth1"; + port = 47808; + network = 2; + }, + { + device_type = "bip"; + device = "eth1"; + port = 47809; + network = 3; + }, + { + device_type = "mstp"; + device = "/dev/ttyS0"; + mac = 1; + max_master = 127; + max_frames = 1; + baud = 38400; + parity = "None"; + databits = 8; + stopbits = 1; + network = 4; + } + ); + + +----------------------- +5. Start +----------------------- + +1. Copy configuration file in the router executable directory +2. Start the router with "router -c init.cfg" command in terminal + + diff --git a/bacnet-stack/include/mstp.h b/bacnet-stack/include/mstp.h index 85ea3dc7..f5520816 100644 --- a/bacnet-stack/include/mstp.h +++ b/bacnet-stack/include/mstp.h @@ -111,10 +111,10 @@ struct mstp_port_struct_t { so that you can be atomic on 8 bit microcontrollers */ uint32_t( *SilenceTimer) ( - void); + void * pArg); void ( *SilenceTimerReset) ( - void); + void * pArg); /* A timer used to measure and generate Reply Postponed frames. It is */ /* incremented by a timer process and is cleared by the Master Node State */ @@ -163,6 +163,10 @@ struct mstp_port_struct_t { hold contiguous memory. */ uint8_t *OutputBuffer; uint16_t OutputBufferSize; + + /*Platform-specific port data*/ + void * UserData; + }; #ifdef __cplusplus diff --git a/bacnet-stack/ports/linux/bip-init.c b/bacnet-stack/ports/linux/bip-init.c index d9a5588c..508d6703 100644 --- a/bacnet-stack/ports/linux/bip-init.c +++ b/bacnet-stack/ports/linux/bip-init.c @@ -78,7 +78,7 @@ static int get_local_ifr_ioctl( return rv; } -static int get_local_address_ioctl( +int get_local_address_ioctl( char *ifname, struct in_addr *addr, int request) diff --git a/bacnet-stack/ports/linux/dlmstp.c b/bacnet-stack/ports/linux/dlmstp.c index 475db2a3..fc191087 100644 --- a/bacnet-stack/ports/linux/dlmstp.c +++ b/bacnet-stack/ports/linux/dlmstp.c @@ -100,7 +100,7 @@ static uint8_t Tusage_timeout = 60; static struct timeval start; static uint32_t Timer_Silence( - void) + void * pArg) { struct timeval now, tmp_diff; int32_t res; @@ -113,7 +113,7 @@ static uint32_t Timer_Silence( } static void Timer_Silence_Reset( - void) + void * pArg) { gettimeofday(&start, NULL); } @@ -221,7 +221,7 @@ static void *dlmstp_master_fsm_task( if (MSTP_Port.ReceivedValidFrame || MSTP_Port.ReceivedInvalidFrame) { run_master = true; } else { - silence = MSTP_Port.SilenceTimer(); + silence = MSTP_Port.SilenceTimer(NULL); switch (MSTP_Port.master_state) { case MSTP_MASTER_STATE_IDLE: if (silence >= Tno_token) diff --git a/bacnet-stack/ports/linux/dlmstp_linux.c b/bacnet-stack/ports/linux/dlmstp_linux.c new file mode 100644 index 00000000..a79e26a0 --- /dev/null +++ b/bacnet-stack/ports/linux/dlmstp_linux.c @@ -0,0 +1,978 @@ +/************************************************************************** +* +* Copyright (C) 2008 Steve Karg +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include "bacdef.h" +#include "bacaddr.h" +#include "mstp.h" +//#include "dlmstp.h" +#include "dlmstp_linux.h" +#include "rs485.h" +#include "npdu.h" +#include "bits.h" +/* OS Specific include */ +#include "net.h" +#include "ringbuf.h" + +/** @file linux/dlmstp.c Provides Linux-specific DataLink functions for MS/TP. */ + +#define BACNET_PDU_CONTROL_BYTE_OFFSET 1 +#define BACNET_DATA_EXPECTING_REPLY_BIT 2 +#define BACNET_DATA_EXPECTING_REPLY(control) ( (control & (1 << BACNET_DATA_EXPECTING_REPLY_BIT) ) > 0 ) + +#define INCREMENT_AND_LIMIT_UINT16(x) {if (x < 0xFFFF) x++;} +uint32_t Timer_Silence( + void * poPort) +{ + struct timeval now, tmp_diff; + SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t *) poPort; + if(!mstp_port) + { + return -1; + } + poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return -1; + } + + int32_t res; + + gettimeofday(&now, NULL); + timersub(&poSharedData->start, &now, &tmp_diff); + res = ((tmp_diff.tv_sec) * 1000 + (tmp_diff.tv_usec) / 1000); + + return (res >= 0 ? res : -res); +} + +void Timer_Silence_Reset( + void * poPort) +{ + SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t *) poPort; + if(!mstp_port) + { + return; + } + poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return; + } + + gettimeofday(&poSharedData->start, NULL); +} + +void get_abstime( + struct timespec *abstime, + unsigned long milliseconds) +{ + struct timeval now, offset, result; + + gettimeofday(&now, NULL); + offset.tv_sec = 0; + offset.tv_usec = milliseconds * 1000; + timeradd(&now, &offset, &result); + abstime->tv_sec = result.tv_sec; + abstime->tv_nsec = result.tv_usec * 1000; +} + +void dlmstp_cleanup( + void * poPort) +{ + SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t *) poPort; + if(!mstp_port) + { + return; + } + poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return; + } + + /* restore the old port settings */ + tcsetattr(poSharedData->RS485_Handle, TCSANOW, &poSharedData->RS485_oldtio); + close(poSharedData->RS485_Handle); + + pthread_cond_destroy(&poSharedData->Received_Frame_Flag); + pthread_cond_destroy(&poSharedData->Receive_Packet_Flag); + pthread_cond_destroy(&poSharedData->Master_Done_Flag); + pthread_mutex_destroy(&poSharedData->Received_Frame_Mutex); + pthread_mutex_destroy(&poSharedData->Receive_Packet_Mutex); + pthread_mutex_destroy(&poSharedData->Master_Done_Mutex); +} + +/* returns number of bytes sent on success, zero on failure */ +int dlmstp_send_pdu( + void * poPort, + BACNET_ADDRESS * dest, /* destination address */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len) +{ /* number of bytes of data */ + int bytes_sent = 0; + struct mstp_pdu_packet *pkt; + unsigned i = 0; + SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t *) poPort; + if(!mstp_port) + { + return 0; + } + poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return 0; + } + + pkt = (struct mstp_pdu_packet *) Ringbuf_Alloc(&poSharedData->PDU_Queue); + if (pkt) { + pkt->data_expecting_reply = BACNET_DATA_EXPECTING_REPLY(pdu[BACNET_PDU_CONTROL_BYTE_OFFSET]); + for (i = 0; i < pdu_len; i++) { + pkt->buffer[i] = pdu[i]; + } + pkt->length = pdu_len; + pkt->destination_mac = dest->mac[0]; + bytes_sent = pdu_len; + } + + return bytes_sent; +} + +uint16_t dlmstp_receive( + void * poPort, + 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; + int rv = 0; + SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t *) poPort; + if(!mstp_port) + { + return 0; + } + poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return 0; + } + (void) max_pdu; + /* see if there is a packet available, and a place + to put the reply (if necessary) and process it */ + get_abstime(&abstime, timeout); + rv = pthread_cond_timedwait(&poSharedData->Receive_Packet_Flag, &poSharedData->Receive_Packet_Mutex, + &abstime); + if (rv == 0) { + if (poSharedData->Receive_Packet.ready) { + if (poSharedData->Receive_Packet.pdu_len) { + poSharedData->MSTP_Packets++; + if (src) { + memmove(src, &poSharedData->Receive_Packet.address, + sizeof(poSharedData->Receive_Packet.address)); + } + if (pdu) { + memmove(pdu, &poSharedData->Receive_Packet.pdu, + sizeof(poSharedData->Receive_Packet.pdu)); + } + pdu_len = poSharedData->Receive_Packet.pdu_len; + } + poSharedData->Receive_Packet.ready = false; + } + } + + return pdu_len; +} + +void *dlmstp_receive_fsm_task( + void *pArg) +{ + bool received_frame; + SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t*)pArg; + if(!mstp_port) + { + return NULL; + } + + poSharedData = (SHARED_MSTP_DATA *) ((struct mstp_port_struct_t*)pArg)->UserData; + if(!poSharedData) + { + return NULL; + } + + 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((volatile struct mstp_port_struct_t*)pArg); + received_frame = mstp_port->ReceivedValidFrame || + mstp_port->ReceivedInvalidFrame; + if (received_frame) { + pthread_cond_signal(&poSharedData->Received_Frame_Flag); + break; + } + } while (mstp_port->DataAvailable); + } + } + + return NULL; +} + +void *dlmstp_master_fsm_task( + void *pArg) +{ + uint32_t silence = 0; + bool run_master = false; + SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t*)pArg; + if(!mstp_port) + { + return NULL; + } + + poSharedData = (SHARED_MSTP_DATA *) ((struct mstp_port_struct_t*)pArg)->UserData; + if(!poSharedData) + { + return NULL; + } + + for (;;) { + 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(NULL); + 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 >= poSharedData->Treply_timeout) + run_master = true; + break; + case MSTP_MASTER_STATE_POLL_FOR_MASTER: + if (silence >= poSharedData->Tusage_timeout) + run_master = true; + break; + default: + run_master = true; + break; + } + } + if (run_master) { + if (mstp_port->This_Station <= DEFAULT_MAX_MASTER) { + while (MSTP_Master_Node_FSM(mstp_port)) { + /* do nothing while immediate transitioning */ + } + } else if (mstp_port->This_Station < 255) { + MSTP_Slave_Node_FSM(mstp_port); + } + } + } + + 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( + volatile struct mstp_port_struct_t *mstp_port) +{ + uint16_t pdu_len = 0; + SHARED_MSTP_DATA * poSharedData = (SHARED_MSTP_DATA*)mstp_port->UserData; + + if(!poSharedData) + { + return 0; + } + + if (!poSharedData->Receive_Packet.ready) { + /* bounds check - maybe this should send an abort? */ + pdu_len = mstp_port->DataLength; + if (pdu_len > sizeof(poSharedData->Receive_Packet.pdu)) + pdu_len = sizeof(poSharedData->Receive_Packet.pdu); + memmove((void *) &poSharedData->Receive_Packet.pdu[0], + (void *) &mstp_port->InputBuffer[0], pdu_len); + dlmstp_fill_bacnet_address(&poSharedData->Receive_Packet.address, + mstp_port->SourceAddress); + poSharedData->Receive_Packet.pdu_len = mstp_port->DataLength; + poSharedData->Receive_Packet.ready = true; + pthread_cond_signal(&poSharedData->Receive_Packet_Flag); + } + + return pdu_len; +} + +/* 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 */ + uint16_t pdu_len = 0; + uint8_t frame_type = 0; + struct mstp_pdu_packet *pkt; + SHARED_MSTP_DATA * poSharedData = (SHARED_MSTP_DATA*)mstp_port->UserData; + + if(!poSharedData) + { + return 0; + } + + (void) timeout; + if (Ringbuf_Empty(&poSharedData->PDU_Queue)) { + return 0; + } + pkt = (struct mstp_pdu_packet *) Ringbuf_Pop_Front(&poSharedData->PDU_Queue); + 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, frame_type, pkt->destination_mac, + mstp_port->This_Station, (uint8_t *) & pkt->buffer[0], pkt->length); + + return pdu_len; +} + +bool dlmstp_compare_data_expecting_reply( + uint8_t * request_pdu, + uint16_t request_pdu_len, + uint8_t src_address, + uint8_t * reply_pdu, + uint16_t reply_pdu_len, + uint8_t dest_address) +{ + uint16_t offset; + /* One way to check the message is to compare NPDU + src, dest, along with the APDU type, invoke id. + Seems a bit overkill */ + struct DER_compare_t { + BACNET_NPDU_DATA npdu_data; + BACNET_ADDRESS address; + uint8_t pdu_type; + uint8_t invoke_id; + uint8_t service_choice; + }; + struct DER_compare_t request; + struct DER_compare_t reply; + + /* unused parameters */ + request_pdu_len = request_pdu_len; + reply_pdu_len = reply_pdu_len; + /* decode the request data */ + request.address.mac[0] = src_address; + request.address.mac_len = 1; + offset = + npdu_decode(&request_pdu[0], NULL, &request.address, + &request.npdu_data); + if (request.npdu_data.network_layer_message) { +#if PRINT_ENABLED + fprintf(stderr, + "DLMSTP: DER Compare failed: " "Request is Network message.\n"); +#endif + return false; + } + request.pdu_type = request_pdu[offset] & 0xF0; + if (request.pdu_type != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) { +#if PRINT_ENABLED + fprintf(stderr, + "DLMSTP: DER Compare failed: " "Not Confirmed Request.\n"); +#endif + return false; + } + request.invoke_id = request_pdu[offset + 2]; + /* segmented message? */ + if (request_pdu[offset] & BIT3) { + request.service_choice = request_pdu[offset + 5]; + } else { + request.service_choice = request_pdu[offset + 3]; + } + /* decode the reply data */ + reply.address.mac[0] = dest_address; + reply.address.mac_len = 1; + offset = + npdu_decode(&reply_pdu[0], &reply.address, NULL, &reply.npdu_data); + if (reply.npdu_data.network_layer_message) { +#if PRINT_ENABLED + fprintf(stderr, + "DLMSTP: DER Compare failed: " "Reply is Network message.\n"); +#endif + return false; + } + /* reply could be a lot of things: + confirmed, simple ack, abort, reject, error */ + reply.pdu_type = reply_pdu[offset] & 0xF0; + switch (reply.pdu_type) { + case PDU_TYPE_CONFIRMED_SERVICE_REQUEST: + reply.invoke_id = reply_pdu[offset + 2]; + /* segmented message? */ + if (reply_pdu[offset] & BIT3) { + reply.service_choice = reply_pdu[offset + 5]; + } else { + reply.service_choice = reply_pdu[offset + 3]; + } + break; + case PDU_TYPE_SIMPLE_ACK: + reply.invoke_id = reply_pdu[offset + 1]; + reply.service_choice = reply_pdu[offset + 2]; + break; + case PDU_TYPE_COMPLEX_ACK: + reply.invoke_id = reply_pdu[offset + 1]; + /* segmented message? */ + if (reply_pdu[offset] & BIT3) { + reply.service_choice = reply_pdu[offset + 4]; + } else { + reply.service_choice = reply_pdu[offset + 2]; + } + break; + case PDU_TYPE_ERROR: + reply.invoke_id = reply_pdu[offset + 1]; + reply.service_choice = reply_pdu[offset + 2]; + break; + case PDU_TYPE_REJECT: + case PDU_TYPE_ABORT: + reply.invoke_id = reply_pdu[offset + 1]; + break; + default: + return false; + } + /* these don't have service choice included */ + if ((reply.pdu_type == PDU_TYPE_REJECT) || + (reply.pdu_type == PDU_TYPE_ABORT)) { + if (request.invoke_id != reply.invoke_id) { +#if PRINT_ENABLED + fprintf(stderr, + "DLMSTP: DER Compare failed: " "Invoke ID mismatch.\n"); +#endif + return false; + } + } else { + if (request.invoke_id != reply.invoke_id) { +#if PRINT_ENABLED + fprintf(stderr, + "DLMSTP: DER Compare failed: " "Invoke ID mismatch.\n"); +#endif + return false; + } + if (request.service_choice != reply.service_choice) { +#if PRINT_ENABLED + fprintf(stderr, + "DLMSTP: DER Compare failed: " "Service choice mismatch.\n"); +#endif + return false; + } + } + if (request.npdu_data.protocol_version != reply.npdu_data.protocol_version) { +#if PRINT_ENABLED + fprintf(stderr, + "DLMSTP: DER Compare failed: " + "NPDU Protocol Version mismatch.\n"); +#endif + return false; + } + if (request.npdu_data.priority != reply.npdu_data.priority) { +#if PRINT_ENABLED + fprintf(stderr, + "DLMSTP: DER Compare failed: " "NPDU Priority mismatch.\n"); +#endif + return false; + } + if (!bacnet_address_same(&request.address, &reply.address)) { +#if PRINT_ENABLED + fprintf(stderr, + "DLMSTP: DER Compare failed: " "BACnet Address mismatch.\n"); +#endif + return false; + } + + return true; +} + +/* Get the reply to a DATA_EXPECTING_REPLY frame, or nothing */ +uint16_t MSTP_Get_Reply( + volatile struct mstp_port_struct_t * mstp_port, + unsigned timeout) +{ /* milliseconds to wait for a packet */ + uint16_t pdu_len = 0; /* return value */ + bool matched = false; + uint8_t frame_type = 0; + struct mstp_pdu_packet *pkt; + SHARED_MSTP_DATA * poSharedData = (SHARED_MSTP_DATA*)mstp_port->UserData; + + if(!poSharedData) + { + return 0; + } + + if (Ringbuf_Empty(&poSharedData->PDU_Queue)) { + return 0; + } + pkt = (struct mstp_pdu_packet *) Ringbuf_Get_Front(&poSharedData->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, + (uint8_t *) & pkt->buffer[0], pkt->length, pkt->destination_mac); + if (!matched) { + return 0; + } + pkt = (struct mstp_pdu_packet *) Ringbuf_Pop_Front(&poSharedData->PDU_Queue); + 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, frame_type, pkt->destination_mac, + mstp_port->This_Station, (uint8_t *) & pkt->buffer[0], pkt->length); + + return pdu_len; +} + +void dlmstp_set_mac_address( + void * poPort, + uint8_t mac_address) +{ +// SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t *) poPort; + if(!mstp_port) + { + return; + } +/* + poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return; + } +*/ + /* 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(mstp_port, mac_address); + } + + return; +} + +uint8_t dlmstp_mac_address( + void * poPort) +{ +// SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t *) poPort; + if(!mstp_port) + { + return 0; + } +/* poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return 0; + } +*/ + + 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. */ +void dlmstp_set_max_info_frames( + void * poPort, + uint8_t max_info_frames) +{ +// SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t *) poPort; + if(!mstp_port) + { + return; + } +/* + poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return; + } +*/ + 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; +} + +uint8_t dlmstp_max_info_frames( + void * poPort) +{ +// SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t *) poPort; + if(!mstp_port) + { + return 0; + } +/* + poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return 0; + } +*/ + 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. */ +void dlmstp_set_max_master( + void * poPort, + uint8_t max_master) +{ +// SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t *) poPort; + if(!mstp_port) + { + return; + } +/* + poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return; + } +*/ + 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); */ + } + } + + return; +} + +uint8_t dlmstp_max_master( + void * poPort) +{ +// SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t *) poPort; + if(!mstp_port) + { + return 0; + } +/* + poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return 0; + } +*/ + return mstp_port->Nmax_master; +} + +/* RS485 Baud Rate 9600, 19200, 38400, 57600, 115200 */ +void dlmstp_set_baud_rate( + void * poPort, + uint32_t baud) +{ + SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t *) poPort; + if(!mstp_port) + { + return; + } + poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return; + } + + switch (baud) { + case 9600: + poSharedData->RS485_Baud = B9600; + break; + case 19200: + poSharedData->RS485_Baud = B19200; + break; + case 38400: + poSharedData->RS485_Baud = B38400; + break; + case 57600: + poSharedData->RS485_Baud = B57600; + break; + case 115200: + poSharedData->RS485_Baud = B115200; + break; + default: + break; + } +} + +uint32_t dlmstp_baud_rate( + void * poPort) +{ + SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t *) poPort; + if(!mstp_port) + { + return false; + } + poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return false; + } + + switch (poSharedData->RS485_Baud) { + case B19200: + return 19200; + case B38400: + return 38400; + case B57600: + return 57600; + case B115200: + return 115200; + default: + case B9600: + return 9600; + } +} + +void dlmstp_get_my_address( + void * poPort, + BACNET_ADDRESS * my_address) +{ + int i = 0; /* counter */ + SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t *) poPort; + if(!mstp_port) + { + return; + } + poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return; + } + my_address->mac_len = 1; + my_address->mac[0] = mstp_port->This_Station; + my_address->net = 0; /* local only, no routing */ + my_address->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + my_address->adr[i] = 0; + } + + return; +} + +void dlmstp_get_broadcast_address( + BACNET_ADDRESS * dest) +{ /* destination address */ + int i = 0; /* counter */ + + if (dest) { + dest->mac_len = 1; + dest->mac[0] = MSTP_BROADCAST_ADDRESS; + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; /* always zero when DNET is broadcast */ + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } + } + + return; +} + +bool dlmstp_init( + void * poPort, + char *ifname) +{ + unsigned long hThread = 0; + int rv = 0; + SHARED_MSTP_DATA * poSharedData; + struct mstp_port_struct_t * mstp_port = (struct mstp_port_struct_t*)poPort; + if(!mstp_port) + { + return false; + } + + poSharedData = (SHARED_MSTP_DATA *) ((struct mstp_port_struct_t*)mstp_port)->UserData; + if(!poSharedData) + { + return false; + } + + poSharedData->RS485_Port_Name = ifname; + /* initialize PDU queue */ + Ringbuf_Init(&poSharedData->PDU_Queue, (uint8_t *) & poSharedData->PDU_Buffer, + sizeof(struct mstp_pdu_packet), MSTP_PDU_PACKET_COUNT); + /* initialize packet queue */ + poSharedData->Receive_Packet.ready = false; + poSharedData->Receive_Packet.pdu_len = 0; + rv = pthread_cond_init(&poSharedData->Receive_Packet_Flag, NULL); + if (rv != 0) { + fprintf(stderr, + "MS/TP Interface: %s\n cannot allocate PThread Condition.\n", + ifname); + exit(1); + } + rv = pthread_mutex_init(&poSharedData->Receive_Packet_Mutex, NULL); + if (rv != 0) { + fprintf(stderr, + "MS/TP Interface: %s\n cannot allocate PThread Mutex.\n", ifname); + exit(1); + } + + struct termios newtio; + printf("RS485: Initializing %s", poSharedData->RS485_Port_Name); + /* + Open device for reading and writing. + Blocking mode - more CPU effecient + */ + poSharedData->RS485_Handle = open(poSharedData->RS485_Port_Name, O_RDWR | O_NOCTTY | O_NONBLOCK/*| O_NDELAY */ ); + if (poSharedData->RS485_Handle < 0) { + perror(poSharedData->RS485_Port_Name); + exit(-1); + } +#if 0 + /* non blocking for the read */ + fcntl(poSharedData->RS485_Handle, F_SETFL, FNDELAY); +#else + /* efficient blocking for the read */ + fcntl(poSharedData->RS485_Handle, F_SETFL, 0); +#endif + /* save current serial port settings */ + tcgetattr(poSharedData->RS485_Handle, &poSharedData->RS485_oldtio); + /* clear struct for new port settings */ + bzero(&newtio, sizeof(newtio)); + /* + BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed. + CRTSCTS : output hardware flow control (only used if the cable has + all necessary lines. See sect. 7 of Serial-HOWTO) + CLOCAL : local connection, no modem contol + CREAD : enable receiving characters + */ + newtio.c_cflag = poSharedData->RS485_Baud | poSharedData->RS485MOD | CLOCAL | CREAD; + /* Raw input */ + newtio.c_iflag = 0; + /* Raw output */ + newtio.c_oflag = 0; + /* no processing */ + newtio.c_lflag = 0; + /* activate the settings for the port after flushing I/O */ + tcsetattr(poSharedData->RS485_Handle, TCSAFLUSH, &newtio); + /* flush any data waiting */ + usleep(200000); + tcflush(poSharedData->RS485_Handle, TCIOFLUSH); + /* ringbuffer */ + FIFO_Init(&poSharedData->Rx_FIFO, poSharedData->Rx_Buffer, sizeof(poSharedData->Rx_Buffer)); + printf("=success!\n"); + mstp_port->InputBuffer = &poSharedData->RxBuffer[0]; + mstp_port->InputBufferSize = sizeof(poSharedData->RxBuffer); + mstp_port->OutputBuffer = &poSharedData->TxBuffer[0]; + mstp_port->OutputBufferSize = sizeof(poSharedData->TxBuffer); + gettimeofday(&poSharedData->start, NULL); + mstp_port->SilenceTimer = Timer_Silence; + mstp_port->SilenceTimerReset = Timer_Silence_Reset; + MSTP_Init(mstp_port); +#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", mstp_port->Nmax_info_frames); +#endif + + rv = pthread_create(&hThread, NULL, dlmstp_master_fsm_task, mstp_port); + if (rv != 0) { + fprintf(stderr, "Failed to start Master Node FSM task\n"); + } + + return true; +} diff --git a/bacnet-stack/ports/linux/dlmstp_linux.h b/bacnet-stack/ports/linux/dlmstp_linux.h new file mode 100644 index 00000000..4b6f2682 --- /dev/null +++ b/bacnet-stack/ports/linux/dlmstp_linux.h @@ -0,0 +1,210 @@ +/************************************************************************** +* +* Copyright (C) 2012 Steve Karg +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*********************************************************************/ +#ifndef DLMSTP_LINUX_H +#define DLMSTP_LINUX_H + +#include "mstp.h" +//#include "dlmstp.h" +#include "bits/pthreadtypes.h" + +#include +#include +#include +#include "bacdef.h" +#include "npdu.h" +#include +#include "fifo.h" +#include "ringbuf.h" +/* defines specific to MS/TP */ +/* preamble+type+dest+src+len+crc8+crc16 */ +#define MAX_HEADER (2+1+1+1+2+1+2) +#define MAX_MPDU (MAX_HEADER+MAX_PDU) + +/* count must be a power of 2 for ringbuf library */ +#ifndef MSTP_PDU_PACKET_COUNT +#define MSTP_PDU_PACKET_COUNT 8 +#endif + +typedef struct dlmstp_packet { + bool ready; /* true if ready to be sent or received */ + BACNET_ADDRESS address; /* source address */ + uint8_t frame_type; /* type of message */ + uint16_t pdu_len; /* packet length */ + uint8_t pdu[MAX_MPDU]; /* packet */ +} DLMSTP_PACKET; + +/* 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[MAX_MPDU]; +}; + +typedef struct shared_mstp_data +{ + /* Number of MS/TP Packets Rx/Tx */ + uint16_t MSTP_Packets; + + /* packet queues */ + DLMSTP_PACKET Receive_Packet; + DLMSTP_PACKET Transmit_Packet; + /* + RT_COND Receive_Packet_Flag; + RT_MUTEX Receive_Packet_Mutex; + */ + pthread_cond_t Receive_Packet_Flag; + pthread_mutex_t Receive_Packet_Mutex; + /* mechanism to wait for a frame in state machine */ + /* + RT_COND Received_Frame_Flag; + RT_MUTEX Received_Frame_Mutex; + */ + pthread_cond_t Received_Frame_Flag; + pthread_mutex_t Received_Frame_Mutex; + pthread_cond_t Master_Done_Flag; + pthread_mutex_t Master_Done_Mutex; + /* buffers needed by mstp port struct */ + uint8_t TxBuffer[MAX_MPDU]; + uint8_t RxBuffer[MAX_MPDU]; + /* 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.) */ + uint16_t Treply_timeout; + /* The minimum 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 100 milliseconds.) */ + uint8_t Tusage_timeout; + /* Timer that indicates line silence - and functions */ + uint16_t SilenceTime; + + /* handle returned from open() */ + int RS485_Handle; + /* baudrate settings are defined in , which is + included by */ + unsigned int RS485_Baud; + /* serial port name, /dev/ttyS0, + /dev/ttyUSB0 for USB->RS485 from B&B Electronics USOPTL4 */ + char *RS485_Port_Name; + /* serial I/O settings */ + struct termios RS485_oldtio; + /* some terminal I/O have RS-485 specific functionality */ + tcflag_t RS485MOD; + /* Ring buffer for incoming bytes, in order to speed up the receiving. */ + FIFO_BUFFER Rx_FIFO; + /* buffer size needs to be a power of 2 */ + uint8_t Rx_Buffer[4096]; + struct timeval start; + + RING_BUFFER PDU_Queue; + + struct mstp_pdu_packet PDU_Buffer[MSTP_PDU_PACKET_COUNT]; + +}SHARED_MSTP_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + bool dlmstp_init( + void * poShared, + char *ifname); + void dlmstp_reset( + void * poShared); + void dlmstp_cleanup( + void * poShared); + + /* returns number of bytes sent on success, negative on failure */ + int dlmstp_send_pdu( + void * poShared, + BACNET_ADDRESS * dest, /* destination address */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len); /* number of bytes of data */ + + /* returns the number of octets in the PDU, or zero on failure */ + uint16_t dlmstp_receive( + void * poShared, + 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 */ + + /* 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. */ + void dlmstp_set_max_info_frames( + void * poShared, + uint8_t max_info_frames); + uint8_t dlmstp_max_info_frames( + void * poShared); + + /* 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. */ + void dlmstp_set_max_master( + void * poShared, + uint8_t max_master); + uint8_t dlmstp_max_master( + void * poShared); + + /* MAC address 0-127 */ + void dlmstp_set_mac_address( + void * poShared, + uint8_t my_address); + uint8_t dlmstp_mac_address( + void * poShared); + + void dlmstp_get_my_address( + void * poShared, + BACNET_ADDRESS * my_address); + void dlmstp_get_broadcast_address( + BACNET_ADDRESS * dest); /* destination address */ + + /* RS485 Baud Rate 9600, 19200, 38400, 57600, 115200 */ + void dlmstp_set_baud_rate( + void * poShared, + uint32_t baud); + uint32_t dlmstp_baud_rate( + void * poShared); + + void dlmstp_fill_bacnet_address( + BACNET_ADDRESS * src, + uint8_t mstp_address); + + bool dlmstp_sole_master( + void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /*DLMSTP_LINUX_H*/ diff --git a/bacnet-stack/ports/linux/rs485.c b/bacnet-stack/ports/linux/rs485.c index 6a78434b..0331f3d1 100644 --- a/bacnet-stack/ports/linux/rs485.c +++ b/bacnet-stack/ports/linux/rs485.c @@ -63,6 +63,8 @@ #include #include +#include "dlmstp_linux.h" + /* Posix serial programming reference: http://www.easysw.com/~mike/serial/serial.html */ @@ -195,6 +197,87 @@ uint32_t RS485_Get_Baud_Rate( return baud; } +/**************************************************************************** +* DESCRIPTION: Returns the baud rate that we are currently running at +* RETURN: none +* ALGORITHM: none +* NOTES: none +*****************************************************************************/ +uint32_t RS485_Get_Port_Baud_Rate( + volatile struct mstp_port_struct_t *mstp_port) +{ + uint32_t baud = 0; + SHARED_MSTP_DATA * poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + return 0; + } + switch (poSharedData->RS485_Baud) { + case B0: + baud = 0; + break; + case B50: + baud = 50; + break; + case B75: + baud = 75; + break; + case B110: + baud = 110; + break; + case B134: + baud = 134; + break; + case B150: + baud = 150; + break; + case B200: + baud = 200; + break; + case B300: + baud = 300; + break; + case B600: + baud = 600; + break; + case B1200: + baud = 1200; + break; + case B1800: + baud = 1800; + break; + case B2400: + baud = 2400; + break; + case B4800: + baud = 4800; + break; + case B9600: + baud = 9600; + break; + case B19200: + baud = 19200; + break; + case B38400: + baud = 38400; + break; + case B57600: + baud = 57600; + break; + case B115200: + baud = 115200; + break; + case B230400: + baud = 230400; + break; + default: + baud = 9600; + break; + } + + return baud; +} + /**************************************************************************** * DESCRIPTION: Sets the baud rate for the chip USART * RETURN: none @@ -286,35 +369,66 @@ void RS485_Send_Frame( volatile struct mstp_port_struct_t *mstp_port, /* port specific data */ uint8_t * buffer, /* frame to send (up to 501 bytes of data) */ uint16_t nbytes) -{ /* number of bytes of data (up to 501) */ +{ /* number of bytes of data (up to 501) */ uint32_t turnaround_time = Tturnaround * 1000; - uint32_t baud = RS485_Get_Baud_Rate(); + uint32_t baud; ssize_t written = 0; int greska; - - /* sleeping for turnaround time is necessary to give other devices - time to change from sending to receiving state. */ - usleep(turnaround_time / baud); - /* - On success, the number of bytes written are returned (zero indicates - nothing was written). On error, -1 is returned, and errno is set - appropriately. If count is zero and the file descriptor refers to a - regular file, 0 will be returned without causing any other effect. For - a special file, the results are not portable. - */ - written = write(RS485_Handle, buffer, nbytes); - greska = errno; - if (written <= 0) { - printf("write error: %s\n", strerror(greska)); - } else { - /* wait until all output has been transmitted. */ - tcdrain(RS485_Handle); - } - /* tcdrain(RS485_Handle); */ - /* per MSTP spec, sort of */ - if (mstp_port) { - mstp_port->SilenceTimerReset(); - } + SHARED_MSTP_DATA * poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + baud = RS485_Get_Baud_Rate(); + /* sleeping for turnaround time is necessary to give other devices + time to change from sending to receiving state. */ + usleep(turnaround_time / baud); + /* + On success, the number of bytes written are returned (zero indicates + nothing was written). On error, -1 is returned, and errno is set + appropriately. If count is zero and the file descriptor refers to a + regular file, 0 will be returned without causing any other effect. For + a special file, the results are not portable. + */ + written = write(RS485_Handle, buffer, nbytes); + greska = errno; + if (written <= 0) { + printf("write error: %s\n", strerror(greska)); + } else { + /* wait until all output has been transmitted. */ + tcdrain(RS485_Handle); + } + /* tcdrain(RS485_Handle); */ + /* per MSTP spec, sort of */ + if (mstp_port) { + mstp_port->SilenceTimerReset((void*)mstp_port); + } + } + else + { + baud = RS485_Get_Port_Baud_Rate(mstp_port); + /* sleeping for turnaround time is necessary to give other devices + time to change from sending to receiving state. */ + usleep(turnaround_time / baud); + /* + On success, the number of bytes written are returned (zero indicates + nothing was written). On error, -1 is returned, and errno is set + appropriately. If count is zero and the file descriptor refers to a + regular file, 0 will be returned without causing any other effect. For + a special file, the results are not portable. + */ + written = write(poSharedData->RS485_Handle, buffer, nbytes); + greska = errno; + if (written <= 0) { + printf("write error: %s\n", strerror(greska)); + } else { + /* wait until all output has been transmitted. */ + tcdrain(poSharedData->RS485_Handle); + } + /* tcdrain(RS485_Handle); */ + /* per MSTP spec, sort of */ + if (mstp_port) { + mstp_port->SilenceTimerReset((void*)mstp_port); + } + } return; } @@ -333,37 +447,75 @@ void RS485_Check_UART_Data( uint8_t buf[2048]; int n; - if (mstp_port->ReceiveError == true) { - /* do nothing but wait for state machine to clear the error */ - /* burning time, so wait a longer time */ - waiter.tv_sec = 0; - waiter.tv_usec = 5000; - } else if (mstp_port->DataAvailable == false) { - /* wait for state machine to read from the DataRegister */ - if (FIFO_Count(&Rx_FIFO) > 0) { - /* data is available */ - mstp_port->DataRegister = FIFO_Get(&Rx_FIFO); - mstp_port->DataAvailable = true; - /* FIFO is giving data - don't wait very long */ - waiter.tv_sec = 0; - waiter.tv_usec = 10; - } else { - /* FIFO is empty - wait a longer time */ - waiter.tv_sec = 0; - waiter.tv_usec = 5000; - } - } - /* grab bytes and stuff them into the FIFO every time */ - FD_ZERO(&input); - FD_SET(RS485_Handle, &input); - n = select(RS485_Handle + 1, &input, NULL, NULL, &waiter); - if (n < 0) { - return; - } - if (FD_ISSET(RS485_Handle, &input)) { - n = read(RS485_Handle, buf, sizeof(buf)); - FIFO_Add(&Rx_FIFO, &buf[0], n); - } + SHARED_MSTP_DATA * poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData; + if(!poSharedData) + { + if (mstp_port->ReceiveError == true) { + /* do nothing but wait for state machine to clear the error */ + /* burning time, so wait a longer time */ + waiter.tv_sec = 0; + waiter.tv_usec = 5000; + } else if (mstp_port->DataAvailable == false) { + /* wait for state machine to read from the DataRegister */ + if (FIFO_Count(&Rx_FIFO) > 0) { + /* data is available */ + mstp_port->DataRegister = FIFO_Get(&Rx_FIFO); + mstp_port->DataAvailable = true; + /* FIFO is giving data - don't wait very long */ + waiter.tv_sec = 0; + waiter.tv_usec = 10; + } else { + /* FIFO is empty - wait a longer time */ + waiter.tv_sec = 0; + waiter.tv_usec = 5000; + } + } + /* grab bytes and stuff them into the FIFO every time */ + FD_ZERO(&input); + FD_SET(RS485_Handle, &input); + n = select(RS485_Handle + 1, &input, NULL, NULL, &waiter); + if (n < 0) { + return; + } + if (FD_ISSET(RS485_Handle, &input)) { + n = read(RS485_Handle, buf, sizeof(buf)); + FIFO_Add(&Rx_FIFO, &buf[0], n); + } + } + else + { + if (mstp_port->ReceiveError == true) { + /* do nothing but wait for state machine to clear the error */ + /* burning time, so wait a longer time */ + waiter.tv_sec = 0; + waiter.tv_usec = 5000; + } else if (mstp_port->DataAvailable == false) { + /* wait for state machine to read from the DataRegister */ + if (FIFO_Count(&poSharedData->Rx_FIFO) > 0) { + /* data is available */ + mstp_port->DataRegister = FIFO_Get(&poSharedData->Rx_FIFO); + mstp_port->DataAvailable = true; + /* FIFO is giving data - don't wait very long */ + waiter.tv_sec = 0; + waiter.tv_usec = 10; + } else { + /* FIFO is empty - wait a longer time */ + waiter.tv_sec = 0; + waiter.tv_usec = 5000; + } + } + /* grab bytes and stuff them into the FIFO every time */ + FD_ZERO(&input); + FD_SET(poSharedData->RS485_Handle, &input); + n = select(poSharedData->RS485_Handle + 1, &input, NULL, NULL, &waiter); + if (n < 0) { + return; + } + if (FD_ISSET(poSharedData->RS485_Handle, &input)) { + n = read(poSharedData->RS485_Handle, buf, sizeof(buf)); + FIFO_Add(&poSharedData->Rx_FIFO, &buf[0], n); + } + } } void RS485_Cleanup( diff --git a/bacnet-stack/src/mstp.c b/bacnet-stack/src/mstp.c index a26b6489..e8dc6a64 100644 --- a/bacnet-stack/src/mstp.c +++ b/bacnet-stack/src/mstp.c @@ -256,14 +256,14 @@ void MSTP_Receive_Frame_FSM( mstptext_receive_state(mstp_port->receive_state), mstp_port->DataRegister, mstp_port->HeaderCRC, mstp_port->Index, mstp_port->EventCount, mstp_port->DataLength, - mstp_port->SilenceTimer()); + mstp_port->SilenceTimer((void*)mstp_port)); switch (mstp_port->receive_state) { /* In the IDLE state, the node waits for the beginning of a frame. */ case MSTP_RECEIVE_STATE_IDLE: /* EatAnError */ if (mstp_port->ReceiveError == true) { mstp_port->ReceiveError = false; - mstp_port->SilenceTimerReset(); + mstp_port->SilenceTimerReset((void*)mstp_port); INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); /* wait for the start of a frame. */ } else if (mstp_port->DataAvailable == true) { @@ -279,14 +279,14 @@ void MSTP_Receive_Frame_FSM( /* wait for the start of a frame. */ } mstp_port->DataAvailable = false; - mstp_port->SilenceTimerReset(); + mstp_port->SilenceTimerReset((void*)mstp_port); INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); } break; /* In the PREAMBLE state, the node waits for the second octet of the preamble. */ case MSTP_RECEIVE_STATE_PREAMBLE: /* Timeout */ - if (mstp_port->SilenceTimer() > Tframe_abort) { + if (mstp_port->SilenceTimer((void*)mstp_port) > Tframe_abort) { /* a correct preamble has not been received */ /* wait for the start of a frame. */ mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; @@ -294,7 +294,7 @@ void MSTP_Receive_Frame_FSM( /* Error */ else if (mstp_port->ReceiveError == true) { mstp_port->ReceiveError = false; - mstp_port->SilenceTimerReset(); + mstp_port->SilenceTimerReset((void*)mstp_port); INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); /* wait for the start of a frame. */ mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; @@ -317,25 +317,25 @@ void MSTP_Receive_Frame_FSM( mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; } mstp_port->DataAvailable = false; - mstp_port->SilenceTimerReset(); + mstp_port->SilenceTimerReset((void*)mstp_port); INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); } break; /* In the HEADER state, the node waits for the fixed message header. */ case MSTP_RECEIVE_STATE_HEADER: /* Timeout */ - if (mstp_port->SilenceTimer() > Tframe_abort) { + if (mstp_port->SilenceTimer((void*)mstp_port) > Tframe_abort) { /* indicate that an error has occurred during the reception of a frame */ mstp_port->ReceivedInvalidFrame = true; /* wait for the start of a frame. */ mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; printf_receive_error("MSTP: Rx Header: SilenceTimer %u > %d\n", - (unsigned) mstp_port->SilenceTimer(), Tframe_abort); + (unsigned) mstp_port->SilenceTimer((void*)mstp_port), Tframe_abort); } /* Error */ else if (mstp_port->ReceiveError == true) { mstp_port->ReceiveError = false; - mstp_port->SilenceTimerReset(); + mstp_port->SilenceTimerReset((void*)mstp_port); INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); /* indicate that an error has occurred during the reception of a frame */ mstp_port->ReceivedInvalidFrame = true; @@ -460,7 +460,7 @@ void MSTP_Receive_Frame_FSM( /* wait for the start of a frame. */ mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; } - mstp_port->SilenceTimerReset(); + mstp_port->SilenceTimerReset((void*)mstp_port); INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); mstp_port->DataAvailable = false; } @@ -469,19 +469,19 @@ void MSTP_Receive_Frame_FSM( case MSTP_RECEIVE_STATE_DATA: case MSTP_RECEIVE_STATE_SKIP_DATA: /* Timeout */ - if (mstp_port->SilenceTimer() > Tframe_abort) { + if (mstp_port->SilenceTimer((void*)mstp_port) > Tframe_abort) { /* indicate that an error has occurred during the reception of a frame */ mstp_port->ReceivedInvalidFrame = true; printf_receive_error ("MSTP: Rx Data: SilenceTimer %ums > %dms\n", - (unsigned) mstp_port->SilenceTimer(), Tframe_abort); + (unsigned) mstp_port->SilenceTimer((void*)mstp_port), Tframe_abort); /* wait for the start of the next frame. */ mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; } /* Error */ else if (mstp_port->ReceiveError == true) { mstp_port->ReceiveError = false; - mstp_port->SilenceTimerReset(); + mstp_port->SilenceTimerReset((void*)mstp_port); /* indicate that an error has occurred during the reception of a frame */ mstp_port->ReceivedInvalidFrame = true; printf_receive_error("MSTP: Rx Data: ReceiveError\n"); @@ -538,7 +538,7 @@ void MSTP_Receive_Frame_FSM( mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; } mstp_port->DataAvailable = false; - mstp_port->SilenceTimerReset(); + mstp_port->SilenceTimerReset((void*)mstp_port); } break; default: @@ -582,7 +582,7 @@ bool MSTP_Master_Node_FSM( mstp_port->This_Station, next_this_station, mstp_port->Next_Station, next_next_station, mstp_port->Poll_Station, next_poll_station, mstp_port->EventCount, - mstp_port->TokenCount, mstp_port->SilenceTimer(), + mstp_port->TokenCount, mstp_port->SilenceTimer((void*)mstp_port), mstptext_master_state(mstp_port->master_state)); } switch (mstp_port->master_state) { @@ -601,7 +601,7 @@ bool MSTP_Master_Node_FSM( case MSTP_MASTER_STATE_IDLE: /* In the IDLE state, the node waits for a frame. */ /* LostToken */ - if (mstp_port->SilenceTimer() >= Tno_token) { + if (mstp_port->SilenceTimer((void*)mstp_port) >= Tno_token) { /* assume that the token has been lost */ mstp_port->EventCount = 0; /* Addendum 135-2004d-8 */ mstp_port->master_state = MSTP_MASTER_STATE_NO_TOKEN; @@ -620,7 +620,7 @@ bool MSTP_Master_Node_FSM( "Src=%02X Dest=%02X DataLen=%u " "FC=%u ST=%u Type=%s\n", mstp_port->SourceAddress, mstp_port->DestinationAddress, mstp_port->DataLength, mstp_port->FrameCount, - mstp_port->SilenceTimer(), + mstp_port->SilenceTimer((void*)mstp_port), mstptext_frame_type((unsigned) mstp_port->FrameType)); /* destined for me! */ if ((mstp_port->DestinationAddress == mstp_port->This_Station) @@ -731,7 +731,7 @@ bool MSTP_Master_Node_FSM( case MSTP_MASTER_STATE_WAIT_FOR_REPLY: /* In the WAIT_FOR_REPLY state, the node waits for */ /* a reply from another node. */ - if (mstp_port->SilenceTimer() >= Treply_timeout) { + if (mstp_port->SilenceTimer((void*)mstp_port) >= Treply_timeout) { /* ReplyTimeout */ /* assume that the request has failed */ mstp_port->FrameCount = mstp_port->Nmax_info_frames; @@ -878,7 +878,7 @@ bool MSTP_Master_Node_FSM( case MSTP_MASTER_STATE_PASS_TOKEN: /* The PASS_TOKEN state listens for a successor to begin using */ /* the token that this node has just attempted to pass. */ - if (mstp_port->SilenceTimer() <= Tusage_timeout) { + if (mstp_port->SilenceTimer((void*)mstp_port) <= Tusage_timeout) { if (mstp_port->EventCount > Nmin_octets) { /* SawTokenUser */ /* Assume that a frame has been sent by the new token user. */ @@ -922,7 +922,7 @@ bool MSTP_Master_Node_FSM( /* for that period of time. The timeout is continued to determine */ /* whether or not this node may create a token. */ my_timeout = Tno_token + (Tslot * mstp_port->This_Station); - if (mstp_port->SilenceTimer() < my_timeout) { + if (mstp_port->SilenceTimer((void*)mstp_port) < my_timeout) { if (mstp_port->EventCount > Nmin_octets) { /* SawFrame */ /* Some other node exists at a lower address. */ @@ -935,8 +935,8 @@ bool MSTP_Master_Node_FSM( Tno_token + (Tslot * (mstp_port->This_Station + 1)); mm_timeout = Tno_token + (Tslot * (mstp_port->Nmax_master + 1)); - if ((mstp_port->SilenceTimer() < ns_timeout) || - (mstp_port->SilenceTimer() > mm_timeout)) { + if ((mstp_port->SilenceTimer((void*)mstp_port) < ns_timeout) || + (mstp_port->SilenceTimer((void*)mstp_port) > mm_timeout)) { /* GenerateToken */ /* Assume that this node is the lowest numerical address */ /* on the network and is empowered to create a token. */ @@ -1000,7 +1000,7 @@ bool MSTP_Master_Node_FSM( transition_now = true; } mstp_port->ReceivedValidFrame = false; - } else if ((mstp_port->SilenceTimer() > Tusage_timeout) || + } else if ((mstp_port->SilenceTimer((void*)mstp_port) > Tusage_timeout) || (mstp_port->ReceivedInvalidFrame == true)) { if (mstp_port->SoleMaster == true) { /* SoleMaster */ @@ -1069,7 +1069,7 @@ bool MSTP_Master_Node_FSM( mstp_port->master_state = MSTP_MASTER_STATE_IDLE; /* clear our flag we were holding for comparison */ mstp_port->ReceivedValidFrame = false; - } else if (mstp_port->SilenceTimer() > Treply_delay) { + } else if (mstp_port->SilenceTimer((void*)mstp_port) > Treply_delay) { /* DeferredReply */ /* If no reply will be available from the higher layers */ /* within Treply_delay after the reception of the */ @@ -1126,7 +1126,7 @@ void MSTP_Slave_Node_FSM( (uint16_t) length); /* clear our flag we were holding for comparison */ mstp_port->ReceivedValidFrame = false; - } else if (mstp_port->SilenceTimer() > Treply_delay) { + } else if (mstp_port->SilenceTimer((void*)mstp_port) > Treply_delay) { /* If no reply will be available from the higher layers within Treply_delay after the reception of the final octet of the requesting frame (the mechanism used to determine @@ -1198,7 +1198,7 @@ void MSTP_Init( mstp_port->ReceivedValidFrame = false; mstp_port->ReceivedValidFrameNotForUs = false; mstp_port->RetryCount = 0; - mstp_port->SilenceTimerReset(); + mstp_port->SilenceTimerReset((void*)mstp_port); mstp_port->SoleMaster = false; mstp_port->SourceAddress = 0; mstp_port->TokenCount = 0;