BACnet router added.

This commit is contained in:
vasyl-tkhir
2012-09-27 14:36:11 +00:00
parent ad8378ef62
commit 2f73bcae44
23 changed files with 3934 additions and 90 deletions
+4
View File
@@ -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
+1 -1
View File
@@ -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;
+63
View File
@@ -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
+68
View File
@@ -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;
}
);
+375
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}
+61
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef UDPMODULE_H
#define UDPMODULE_H
#include <stdint.h>
#include <stdbool.h>
#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 */
+813
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h> // for time
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <libconfig.h> // read config files
#include <unistd.h> // for getopt
#include <termios.h> // used in kbhit()
#include <getopt.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <pthread.h>
#include <termios.h>
#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_method> [init_parameters]\n"
"\ninit_method:\n"
"-c, --config <filepath>\n\tinitialize router with a configuration file (.cfg) located at <filepath>\n"
"-D, --device <dev_type> <iface> [params]\n\tinitialize a <dev_type> device using an <iface> interface specified with\n\t[params]\n"
"\ninit_parameters:\n"
"-n, --network <net>\n\tspecify device network number\n"
"-P, --port <port>\n\tspecify udp port for BIP device\n"
"-m, --mac <mac_address> [max_master] [max_frames]\n\tspecify MSTP port parameters\n"
"-b, --baud <baud>\n\tspecify MSTP port baud rate\n"
"-p, --parity <None|Even|Odd>\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*)&param);
if (result) {
current->params.bip_params.port = param;
} else {
current->params.bip_params.port = 0xBAC0;
}
result = config_setting_lookup_int(port, "network", (int*)&param);
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*)&param);
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*)&param);
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*)&param);
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*)&param);
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*)&param);
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*)&param);
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*)&param);
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;
}
+95
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#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);
}
+85
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef MSGQUEUE_H
#define MSGQUEUE_H
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#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 */
+168
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mstpmodule.h"
#include "bacint.h"
#include "dlmstp_linux.h"
#include <termios.h>
#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;
}
+25
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef MSTPMODULE_H
#define MSTPMODULE_H
#include "portthread.h"
void* dl_mstp_thread(
void* pArgs);
#endif /* end of MSTPMODULE_H */
+311
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}
}
+53
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef NETWORK_LAYER_H
#define NETWORK_LAYER_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#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 */
+116
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}
}
+125
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef PORTTHREAD_H
#define PORTTHREAD_H
#include <stdint.h>
#include <stdbool.h>
#include <pthread.h>
#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 */
+138
View File
@@ -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 <http://www.gnu.org/licenses/>.
-----------------------
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 = "<value>";
//route specific arguments, see below
},
//route_2
{
device_type = "<value>";
//route specific arguments, see below
},
//.....
//route_n
{
device_type = "<value>";
//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