adjust root folder
This commit is contained in:
@@ -0,0 +1,387 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "bacdef.h"
|
||||
#include "npdu.h"
|
||||
#include "arcnet.h"
|
||||
#include "net.h"
|
||||
|
||||
/** @file linux/arcnet.c Provides Linux-specific functions for Arcnet. */
|
||||
|
||||
/* my local device data - MAC address */
|
||||
uint8_t ARCNET_MAC_Address = 0;
|
||||
/* ARCNET file handle */
|
||||
static int ARCNET_Sock_FD = -1;
|
||||
/* ARCNET socket address (has the interface name) */
|
||||
static struct sockaddr ARCNET_Socket_Address;
|
||||
/* Broadcast address */
|
||||
#define ARCNET_BROADCAST 0
|
||||
|
||||
/*
|
||||
Hints:
|
||||
|
||||
When using a PCI20-485D ARCNET card from Contemporary Controls,
|
||||
you might need to know about the following settings:
|
||||
|
||||
Assuming a 20MHz clock on the COM20020 chip:
|
||||
|
||||
clockp Clock Prescaler DataRate
|
||||
------ --------------- --------
|
||||
0 8 2.5 Mbps
|
||||
1 16 1.25 Mbps
|
||||
2 32 625 Kbps
|
||||
3 64 312.5 Kbps
|
||||
4 128 156.25Kbps
|
||||
|
||||
1. Install the arcnet driver and arcnet raw mode driver:
|
||||
# modprobe com20020_pci clockp=4
|
||||
# modprobe arc_rawmode
|
||||
|
||||
2. Use ifconfig to bring up the interface
|
||||
# ifconfig arc0 up
|
||||
|
||||
3. The hardware address (MAC address) is set using the dipswitch
|
||||
on the back of the card. 0 is broadcast, so don't use 0.
|
||||
|
||||
4. The backplane mode on the PCI20-485D card is done in hardware,
|
||||
so the driver does not need to do backplane mode. If you
|
||||
use another type of PCI20 card, you could pass in backplane=1 or
|
||||
backplane=0 as an option to the modprobe of com20020_pci.
|
||||
|
||||
*/
|
||||
|
||||
bool arcnet_valid(
|
||||
void)
|
||||
{
|
||||
return (ARCNET_Sock_FD >= 0);
|
||||
}
|
||||
|
||||
void arcnet_cleanup(
|
||||
void)
|
||||
{
|
||||
if (arcnet_valid())
|
||||
close(ARCNET_Sock_FD);
|
||||
ARCNET_Sock_FD = -1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int arcnet_bind(
|
||||
char *interface_name)
|
||||
{
|
||||
int sock_fd = -1; /* return value */
|
||||
struct ifreq ifr;
|
||||
int rv; /* return value - error value from df or ioctl call */
|
||||
int uid = 0;
|
||||
|
||||
/* check to see if we are being run as root */
|
||||
uid = getuid();
|
||||
if (uid != 0) {
|
||||
fprintf(stderr,
|
||||
"arcnet: Unable to open an af_packet socket. "
|
||||
"Try running with root priveleges.\n");
|
||||
return sock_fd;
|
||||
}
|
||||
fprintf(stderr, "arcnet: opening \"%s\"\n", interface_name);
|
||||
/* note: on some systems you may have to add or enable in */
|
||||
/* modules.conf (or in modutils/alias on Debian with update-modules) */
|
||||
/* alias net-pf-17 af_packet */
|
||||
/* Then follow it by: # modprobe af_packet */
|
||||
if ((sock_fd = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ALL))) < 0) {
|
||||
/* Error occured */
|
||||
fprintf(stderr, "arcnet: Error opening socket: %s\n", strerror(errno));
|
||||
fprintf(stderr,
|
||||
"You might need to add the following to modules.conf\n"
|
||||
"(or in /etc/modutils/alias on Debian with update-modules):\n"
|
||||
"alias net-pf-17 af_packet\n"
|
||||
"Also, add af_packet to /etc/modules.\n" "Then follow it by:\n"
|
||||
"# modprobe af_packet\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (ARCNET_Sock_FD >= 0) {
|
||||
/* Bind the socket to an interface name so we only get packets from it */
|
||||
ARCNET_Socket_Address.sa_family = ARPHRD_ARCNET;
|
||||
/*ARCNET_Socket_Address.sa_family = PF_INET; */
|
||||
/* Clear the memory before copying */
|
||||
memset(ARCNET_Socket_Address.sa_data, '\0',
|
||||
sizeof(ARCNET_Socket_Address.sa_data));
|
||||
/* Strcpy the interface name into the address */
|
||||
strncpy(ARCNET_Socket_Address.sa_data, interface_name,
|
||||
sizeof(ARCNET_Socket_Address.sa_data) - 1);
|
||||
fprintf(stderr, "arcnet: binding \"%s\"\n",
|
||||
ARCNET_Socket_Address.sa_data);
|
||||
if (bind(sock_fd, &ARCNET_Socket_Address,
|
||||
sizeof(ARCNET_Socket_Address)) != 0) {
|
||||
/* Bind problem, close socket and return */
|
||||
fprintf(stderr, "arcnet: Unable to bind socket : %s\n",
|
||||
strerror(errno));
|
||||
fprintf(stderr,
|
||||
"You might need to add the following to modules.conf\n"
|
||||
"(or in /etc/modutils/alias on Debian with update-modules):\n"
|
||||
"alias net-pf-17 af_packet\n"
|
||||
"Also, add af_packet to /etc/modules.\n" "Then follow it by:\n"
|
||||
"# modprobe af_packet\n");
|
||||
/* Close the socket */
|
||||
close(sock_fd);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
strncpy(ifr.ifr_name, interface_name, sizeof(ifr.ifr_name));
|
||||
rv = ioctl(sock_fd, SIOCGIFHWADDR, &ifr);
|
||||
if (rv != -1) /* worked okay */
|
||||
ARCNET_MAC_Address = ifr.ifr_hwaddr.sa_data[0];
|
||||
/* copy this info into the local copy since bind wiped it out */
|
||||
ARCNET_Socket_Address.sa_family = ARPHRD_ARCNET;
|
||||
/*ARCNET_Socket_Address.sa_family = PF_INET; */
|
||||
/* Clear the memory before copying */
|
||||
memset(ARCNET_Socket_Address.sa_data, '\0',
|
||||
sizeof(ARCNET_Socket_Address.sa_data));
|
||||
/* Strcpy the interface name into the address */
|
||||
strncpy(ARCNET_Socket_Address.sa_data, interface_name,
|
||||
sizeof(ARCNET_Socket_Address.sa_data) - 1);
|
||||
fprintf(stderr, "arcnet: MAC=%02Xh iface=\"%s\"\n", ARCNET_MAC_Address,
|
||||
ARCNET_Socket_Address.sa_data);
|
||||
|
||||
atexit(arcnet_cleanup);
|
||||
|
||||
return sock_fd;
|
||||
}
|
||||
|
||||
bool arcnet_init(
|
||||
char *interface_name)
|
||||
{
|
||||
if (interface_name)
|
||||
ARCNET_Sock_FD = arcnet_bind(interface_name);
|
||||
else
|
||||
ARCNET_Sock_FD = arcnet_bind("arc0");
|
||||
|
||||
return arcnet_valid();
|
||||
}
|
||||
|
||||
/* function to send a PDU out the socket */
|
||||
/* returns number of bytes sent on success, negative on failure */
|
||||
int arcnet_send_pdu(
|
||||
BACNET_ADDRESS * dest, /* destination address */
|
||||
BACNET_NPDU_DATA * npdu_data, /* network information */
|
||||
uint8_t * pdu, /* any data to be sent - may be null */
|
||||
unsigned pdu_len)
|
||||
{ /* number of bytes of data */
|
||||
BACNET_ADDRESS src = { 0 }; /* source address */
|
||||
int bytes = 0;
|
||||
uint8_t mtu[512] = { 0 };
|
||||
int mtu_len = 0;
|
||||
struct archdr *pkt = (struct archdr *) mtu;
|
||||
|
||||
(void) npdu_data;
|
||||
src.mac[0] = ARCNET_MAC_Address;
|
||||
src.mac_len = 1;
|
||||
|
||||
/* don't waste time if the socket is not valid */
|
||||
if (ARCNET_Sock_FD < 0) {
|
||||
fprintf(stderr, "arcnet: socket is invalid!\n");
|
||||
return -1;
|
||||
}
|
||||
/* load destination MAC address */
|
||||
if (dest->mac_len == 1)
|
||||
pkt->hard.dest = dest->mac[0];
|
||||
else {
|
||||
fprintf(stderr, "arcnet: invalid destination MAC address!\n");
|
||||
return -2;
|
||||
}
|
||||
if (src.mac_len == 1)
|
||||
pkt->hard.source = src.mac[0];
|
||||
else {
|
||||
fprintf(stderr, "arcnet: invalid source MAC address!\n");
|
||||
return -3;
|
||||
}
|
||||
/* Logical PDU portion */
|
||||
pkt->soft.raw[0] = 0xCD; /* SC for BACnet */
|
||||
pkt->soft.raw[1] = 0x82; /* DSAP for BACnet */
|
||||
pkt->soft.raw[2] = 0x82; /* SSAP for BACnet */
|
||||
pkt->soft.raw[3] = 0x03; /* LLC Control byte in header */
|
||||
/* packet length */
|
||||
mtu_len = ARC_HDR_SIZE + 4 /*SC,DSAP,SSAP,LLC */ + pdu_len;
|
||||
if (mtu_len > 512) {
|
||||
fprintf(stderr, "arcnet: PDU is too big to send!\n");
|
||||
return -4;
|
||||
}
|
||||
memcpy(&pkt->soft.raw[4], pdu, pdu_len);
|
||||
/* Send the packet */
|
||||
bytes =
|
||||
sendto(ARCNET_Sock_FD, &mtu, mtu_len, 0,
|
||||
(struct sockaddr *) &ARCNET_Socket_Address,
|
||||
sizeof(ARCNET_Socket_Address));
|
||||
/* did it get sent? */
|
||||
if (bytes < 0)
|
||||
fprintf(stderr, "arcnet: Error sending packet: %s\n", strerror(errno));
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/* receives an framed packet */
|
||||
/* returns the number of octets in the PDU, or zero on failure */
|
||||
uint16_t arcnet_receive(
|
||||
BACNET_ADDRESS * src, /* source address */
|
||||
uint8_t * pdu, /* PDU data */
|
||||
uint16_t max_pdu, /* amount of space available in the PDU */
|
||||
unsigned timeout)
|
||||
{ /* milliseconds to wait for a packet */
|
||||
int received_bytes;
|
||||
uint8_t buf[512] = { 0 }; /* data */
|
||||
uint16_t pdu_len = 0; /* return value */
|
||||
fd_set read_fds;
|
||||
int max;
|
||||
struct timeval select_timeout;
|
||||
struct archdr *pkt = (struct archdr *) buf;
|
||||
|
||||
/* Make sure the socket is open */
|
||||
if (ARCNET_Sock_FD <= 0)
|
||||
return 0;
|
||||
|
||||
/* we could just use a non-blocking socket, but that consumes all
|
||||
the CPU time. We can use a timeout; it is only supported as
|
||||
a select. */
|
||||
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(ARCNET_Sock_FD, &read_fds);
|
||||
max = ARCNET_Sock_FD;
|
||||
|
||||
if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0)
|
||||
received_bytes = read(ARCNET_Sock_FD, &buf[0], sizeof(buf));
|
||||
else
|
||||
return 0;
|
||||
|
||||
/* See if there is a problem */
|
||||
if (received_bytes < 0) {
|
||||
/* EAGAIN Non-blocking I/O has been selected */
|
||||
/* using O_NONBLOCK and no data */
|
||||
/* was immediately available for reading. */
|
||||
if (errno != EAGAIN)
|
||||
fprintf(stderr, "ethernet: Read error in receiving packet: %s\n",
|
||||
strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (received_bytes == 0)
|
||||
return 0;
|
||||
|
||||
/* printf("arcnet: received %u bytes (offset=%02Xh %02Xh) "
|
||||
"from %02Xh (proto==%02Xh)\n",
|
||||
received_bytes, pkt->offset[0], pkt->offset[1],
|
||||
pkt->hard.source, pkt->soft.raw[0]);
|
||||
*/
|
||||
|
||||
if (pkt->hard.source == ARCNET_MAC_Address) {
|
||||
fprintf(stderr, "arcnet: self sent packet?\n");
|
||||
return 0;
|
||||
}
|
||||
if (pkt->soft.raw[0] != 0xCD) {
|
||||
/* fprintf(stderr,"arcnet: Non-BACnet packet.\n"); */
|
||||
return 0;
|
||||
}
|
||||
if ((pkt->hard.dest != ARCNET_MAC_Address) &&
|
||||
(pkt->hard.dest != ARCNET_BROADCAST)) {
|
||||
fprintf(stderr, "arcnet: This packet is not for us.\n");
|
||||
return 0;
|
||||
}
|
||||
if ((pkt->soft.raw[1] != 0x82) || /* DSAP */
|
||||
(pkt->soft.raw[2] != 0x82) || /* LSAP */
|
||||
(pkt->soft.raw[3] != 0x03)) { /* LLC Control */
|
||||
fprintf(stderr, "arcnet: BACnet packet has invalid LLC.\n");
|
||||
return 0;
|
||||
}
|
||||
/* It must be addressed to us or be a Broadcast */
|
||||
if ((pkt->hard.dest != ARCNET_MAC_Address) &&
|
||||
(pkt->hard.dest != ARCNET_BROADCAST)) {
|
||||
fprintf(stderr, "arcnet: This packet is not for us.\n");
|
||||
return 0;
|
||||
}
|
||||
/* copy the source address */
|
||||
src->mac_len = 1;
|
||||
src->mac[0] = pkt->hard.source;
|
||||
/* compute the PDU length */
|
||||
pdu_len = received_bytes - ARC_HDR_SIZE;
|
||||
pdu_len -= 4 /* SC, DSAP, SSAP, LLC Control */ ;
|
||||
/* copy the buffer into the PDU */
|
||||
if (pdu_len < max_pdu)
|
||||
memmove(&pdu[0], &pkt->soft.raw[4], pdu_len);
|
||||
/* silently ignore packets that are too large */
|
||||
else
|
||||
pdu_len = 0;
|
||||
|
||||
return pdu_len;
|
||||
}
|
||||
|
||||
void arcnet_get_my_address(
|
||||
BACNET_ADDRESS * my_address)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
my_address->mac_len = 1;
|
||||
my_address->mac[0] = ARCNET_MAC_Address;
|
||||
my_address->net = 0; /* DNET=0 is local only, no routing */
|
||||
my_address->len = 0;
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
my_address->adr[i] = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void arcnet_get_broadcast_address(
|
||||
BACNET_ADDRESS * dest)
|
||||
{ /* destination address */
|
||||
int i = 0; /* counter */
|
||||
|
||||
if (dest) {
|
||||
dest->mac[0] = ARCNET_BROADCAST;
|
||||
dest->mac_len = 1;
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
-- bacnet_BIPV6.lua
|
||||
--
|
||||
-- Dissector for BACnet/IPv6 [135-2016 Annex X]
|
||||
--
|
||||
-- Copyright 2011, Siemens Building Technolgies
|
||||
-- Maintainer Philippe Goetz <philippe.goetz@siemens.com>
|
||||
-- Updated by Steve Karg <skarg@users.sourceforge.net>
|
||||
--
|
||||
-- This program is free software; you can redistribute it and/or
|
||||
-- modify it under the terms of the GNU General Public License
|
||||
-- as published by the Free Software Foundation; either version 2
|
||||
-- of the License, or (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program; if not, write to the Free Software
|
||||
-- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
|
||||
-- Add the following to the end of init.lua in the Wireshark install directory:
|
||||
-- dofile("bacnet_ipv6.lua")
|
||||
|
||||
|
||||
-- [in] tree,pf,buffer,offset,len
|
||||
-- [out] offset, value, ti, bf
|
||||
function uint_dissector(tree,pf,buffer,offset,len)
|
||||
if (offset + len) <= buffer:len() then
|
||||
local bf = buffer(offset,len)
|
||||
local ti = nil
|
||||
if tree ~= nil then
|
||||
ti = tree:add(pf,bf)
|
||||
end
|
||||
return offset + len, bf:uint(), ti, bf
|
||||
end
|
||||
local ti = tree:add(pf,buffer(offset))
|
||||
ti:add_expert_info(PI_MALFORMED, PI_ERROR, "Data shortage")
|
||||
error("Data shortage")
|
||||
end
|
||||
|
||||
function bytes_dissector(tree,pf,buffer,offset,len)
|
||||
if (offset + len) <= buffer:len() then
|
||||
local bf = buffer(offset,len)
|
||||
local ti = nil
|
||||
if tree ~= nil then
|
||||
ti = tree:add(pf,bf)
|
||||
end
|
||||
return offset + len, bf:bytes(), ti, bf
|
||||
end
|
||||
local ti = tree:add(pf,buffer(offset))
|
||||
ti:add_expert_info(PI_MALFORMED, PI_ERROR, "Data shortage")
|
||||
error("Data shortage")
|
||||
end
|
||||
|
||||
p_BACnetBIPV6 = Proto("lua_BACnetBIPV6", "B/IPv6 BACnet Virtual Link Control");
|
||||
p_BACnet_Port = Proto("BACnet-Port", "BACnet UDP Port Handoff")
|
||||
p_dissector_bacnet = Dissector.get("bacnet")
|
||||
p_dissector_bipv4 = Dissector.get("bvlc")
|
||||
|
||||
do
|
||||
p_BACnetBIPV6.init = function()
|
||||
debug("p_BACnetBIPV6.init")
|
||||
end
|
||||
|
||||
local t_BACnetBIPV6_Types = {
|
||||
[0x82]="B/IPv6 (Annex X)"
|
||||
}
|
||||
local t_BACnetBIPV6_Functions = {
|
||||
-- Annex X, PPR3 Draft 22
|
||||
[0x00]="BVLC-Result",
|
||||
[0x01]="Original-Unicast-NPDU",
|
||||
[0x02]="Original-Broadcast-NPDU",
|
||||
[0x03]="Address-Resolution",
|
||||
[0x04]="Forwarded-Address-Resolution",
|
||||
[0x05]="Address-Resolution-Ack",
|
||||
[0x06]="Virtual-Address-Resolution",
|
||||
[0x07]="Virtual-Address-Resolution-Ack",
|
||||
[0x08]="Forwarded-NPDU",
|
||||
[0x09]="Register-Foreign-Device",
|
||||
[0x0A]="Delete-Foreign-Device",
|
||||
[0x0B]="Secure-BVLL",
|
||||
[0x0C]="Distribute-Broadcast-To-Network"
|
||||
}
|
||||
|
||||
local pf_BIPV6Data = ProtoField.bytes ("BIPV6.data", "B/IPV6 Data")
|
||||
|
||||
p_BACnetBIPV6.fields = {
|
||||
pf_BIPV6Data
|
||||
}
|
||||
|
||||
local t_BACnetBIPV6_dissectors = {}
|
||||
|
||||
-- local dt_BIPV6_npdu = DissectorTable.new("lua_BACnetBIPV6.npdu","BIPV6 NPDU",ftypes.UINT8,base.HEX)
|
||||
-- BIPV6 header fields
|
||||
local pf_BIPV6 = {}
|
||||
pf_BIPV6.Type = ProtoField.uint8 ("BIPV6.type","Type",base.HEX,t_BACnetBIPV6_Types)
|
||||
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.Type)
|
||||
pf_BIPV6.Function = ProtoField.uint8 ("BIPV6.function","Function",base.HEX,t_BACnetBIPV6_Functions,0,"BIPV6 Function")
|
||||
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.Function)
|
||||
pf_BIPV6.Length = ProtoField.uint16("BIPV6.length","BIPV6-Length",base.DEC,nil,0,"Length of BIPV6")
|
||||
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.Length)
|
||||
pf_BIPV6.ResultCode = ProtoField.uint16("BIPV6.resultCode","Result-Code",base.DEC,nil,0,"Result Code")
|
||||
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.ResultCode)
|
||||
pf_BIPV6.SourceVirtualAddress = ProtoField.uint24 ("BIPV6.sourceVirtualAddress","Source-Virtual-Address")
|
||||
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.SourceVirtualAddress)
|
||||
pf_BIPV6.DestinationVirtualAddress = ProtoField.uint24 ("BIPV6.destinationVirtualAddress","Destination-Virtual-Address")
|
||||
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.DestinationVirtualAddress)
|
||||
pf_BIPV6.OriginalSourceEffectiveAddress = ProtoField.bytes ("BIPV6.originalSourceEffectiveAddress","Original-Source-B/IPv6-Address")
|
||||
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.OriginalSourceEffectiveAddress)
|
||||
pf_BIPV6.OriginalSourceEffectiveAddress_ip = ProtoField.ipv6 ("BIPV6.originalSourceEffectiveAddress.ip","Original-Source-B/IPv6-Address (ip)")
|
||||
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.OriginalSourceEffectiveAddress_ip)
|
||||
pf_BIPV6.OriginalSourceEffectiveAddress_port = ProtoField.uint16 ("BIPV6.originalSourceEffectiveAddress.port","Original-Source-B/IPv6-Address (port)")
|
||||
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.OriginalSourceEffectiveAddress_port)
|
||||
pf_BIPV6.OriginalSourceVirtualAddress = ProtoField.uint24 ("BIPV6.originalSourceVirtualAddress","Original-Source-Virtual-Address")
|
||||
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.OriginalSourceVirtualAddress)
|
||||
pf_BIPV6.DestinationEffectiveAddress = ProtoField.bytes ("BIPV6.destinationEffectiveAddress","Original-Source-B/IPv6-Address")
|
||||
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.DestinationEffectiveAddress)
|
||||
pf_BIPV6.DestinationEffectiveAddress_ip = ProtoField.ipv6 ("BIPV6.destinationEffectiveAddress.ip","Original-Source-B/IPv6-Address (ip)")
|
||||
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.DestinationEffectiveAddress_ip)
|
||||
pf_BIPV6.DestinationEffectiveAddress_port = ProtoField.uint16 ("BIPV6.destinationEffectiveAddress.port","Original-Source-B/IPv6-Address (port)")
|
||||
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.DestinationEffectiveAddress_port)
|
||||
pf_BIPV6.TimeToLive = ProtoField.uint16 ("BIPV6.timeToLive","Time-To-Live")
|
||||
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.TimeToLive)
|
||||
|
||||
function p_BACnetBIPV6.dissector(buffer,pkt,tree)
|
||||
|
||||
pkt.cols["protocol"] = "B/IPv6"
|
||||
pkt.cols["info"] = "BACnet Building Automation and Control Network"
|
||||
local offset = 0
|
||||
local v_BIPV6 = {}
|
||||
local ti_BIPV6 = {}
|
||||
ti_BIPV6.ti = tree:add(p_BACnetBIPV6,buffer(0))
|
||||
offset, v_BIPV6.Type, ti_BIPV6.Type = uint_dissector(ti_BIPV6.ti, pf_BIPV6.Type, buffer, offset, 1)
|
||||
--if v_BIPV6.Type == 0x81 then
|
||||
-- p_dissector_bipv4:call(buffer, pkt, tree)
|
||||
-- elseif v_BIPV6 ~= 0x82 then
|
||||
-- ti_BIPV6.Type:add_expert_info(PI_MALFORMED, PI_WARN, "Unknown BVLC Type")
|
||||
-- return
|
||||
--end
|
||||
if v_BIPV6.Type ~= 0x82 then
|
||||
ti_BIPV6.Type:add_expert_info(PI_MALFORMED, PI_WARN, "Unknown BVLC Type")
|
||||
return
|
||||
end
|
||||
offset, v_BIPV6.Function, ti_BIPV6.Function = uint_dissector(ti_BIPV6.ti, pf_BIPV6.Function, buffer, offset, 1)
|
||||
offset, v_BIPV6.Length, ti_BIPV6.Length = uint_dissector(ti_BIPV6.ti, pf_BIPV6.Length, buffer, offset, 2)
|
||||
local BIPV6Dissector = t_BACnetBIPV6_dissectors[v_BIPV6.Function]
|
||||
local npduData = nil
|
||||
if BIPV6Dissector ~= nil then
|
||||
pkt.cols["info"] = t_BACnetBIPV6_Functions[v_BIPV6.Function]
|
||||
offset, npduData = BIPV6Dissector(buffer,pkt,ti_BIPV6.ti,offset,v_BIPV6,tree)
|
||||
end
|
||||
if npduData ~= nil then
|
||||
if p_BACnetNPDU ~= nil then
|
||||
ti_BIPV6.ti:set_len(offset)
|
||||
p_BACnetNPDU.dissector:call(npduData:tvb(), pkt, tree)
|
||||
offset = offset + npduData:len()
|
||||
else
|
||||
p_dissector_bacnet:call(npduData:tvb(), pkt, tree)
|
||||
end
|
||||
end
|
||||
-- CLB don't show the raw data
|
||||
-- if offset ~= buffer:len() then
|
||||
-- ti_BIPV6.ti:add(pf_BIPV6Data,buffer(offset))
|
||||
-- end
|
||||
ti_BIPV6 = nil
|
||||
collectgarbage("collect")
|
||||
end
|
||||
|
||||
-- [0x00] BVLC-Result
|
||||
t_BACnetBIPV6_dissectors[0x00] = function(buffer,pkt,tree,offset,npdu)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.ResultCode,buffer,offset,2)
|
||||
return offset
|
||||
end
|
||||
|
||||
-- [0x01] Original-Unicast-NPDU
|
||||
t_BACnetBIPV6_dissectors[0x01] = function(buffer,pkt,tree,offset,npdu)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.DestinationVirtualAddress,buffer,offset,3)
|
||||
return offset, buffer(offset)
|
||||
end
|
||||
|
||||
-- [0x02] Original-Broadcast-NPDU
|
||||
t_BACnetBIPV6_dissectors[0x02] = function(buffer,pkt,tree,offset,npdu)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
|
||||
return offset, buffer(offset)
|
||||
end
|
||||
|
||||
-- [0x03] Address-Resolution
|
||||
t_BACnetBIPV6_dissectors[0x03] = function(buffer,pkt,tree,offset,npdu)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.DestinationVirtualAddress,buffer,offset,3)
|
||||
return offset
|
||||
end
|
||||
|
||||
-- [0x04] Forwarded-Address-Resolution
|
||||
t_BACnetBIPV6_dissectors[0x04] = function(buffer,pkt,tree,offset,npdu)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.OriginalSourceVirtualAddress,buffer,offset,3)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.DestinationVirtualAddress,buffer,offset,3)
|
||||
local subtree = tree:add(pf_BIPV6.OriginalSourceEffectiveAddress,buffer(offset,18))
|
||||
offset = bytes_dissector(subtree,pf_BIPV6.OriginalSourceEffectiveAddress_ip,buffer,offset,16)
|
||||
offset = uint_dissector(subtree,pf_BIPV6.OriginalSourceEffectiveAddress_port,buffer,offset,2)
|
||||
return offset
|
||||
end
|
||||
|
||||
-- [0x05] Address-Resolution-Ack
|
||||
t_BACnetBIPV6_dissectors[0x05] = function(buffer,pkt,tree,offset,npdu)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.DestinationVirtualAddress,buffer,offset,3)
|
||||
return offset
|
||||
end
|
||||
|
||||
-- [0x06] Virtual-Address-Resolution
|
||||
t_BACnetBIPV6_dissectors[0x06] = function(buffer,pkt,tree,offset,npdu)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
|
||||
return offset
|
||||
end
|
||||
|
||||
-- [0x07] Virtual-Address-Resolution-Ack
|
||||
t_BACnetBIPV6_dissectors[0x07] = function(buffer,pkt,tree,offset,npdu)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
|
||||
return offset
|
||||
end
|
||||
|
||||
-- [0x08] Forwarded-NPDU
|
||||
t_BACnetBIPV6_dissectors[0x08] = function(buffer,pkt,tree,offset,npdu)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.OriginalSourceVirtualAddress,buffer,offset,3)
|
||||
local subtree = tree:add(pf_BIPV6.OriginalSourceEffectiveAddress,buffer(offset,18))
|
||||
offset = bytes_dissector(subtree,pf_BIPV6.OriginalSourceEffectiveAddress_ip,buffer,offset,16)
|
||||
offset = uint_dissector(subtree,pf_BIPV6.OriginalSourceEffectiveAddress_port,buffer,offset,2)
|
||||
return offset, buffer(offset)
|
||||
end
|
||||
|
||||
-- [0x09] Register-Foreign-Device
|
||||
t_BACnetBIPV6_dissectors[0x09] = function(buffer,pkt,tree,offset,npdu)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
|
||||
offset = uint_dissector(tree,pf_BIPV6.TimeToLive,buffer,offset,2)
|
||||
return offset
|
||||
end
|
||||
|
||||
-- [0x0A] Delete-Foreign-Device
|
||||
t_BACnetBIPV6_dissectors[0x0A] = function(buffer,pkt,tree,offset,npdu)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
|
||||
-- FDT Entry decoding
|
||||
return offset
|
||||
end
|
||||
|
||||
-- [0x0B] Secure-BVLL
|
||||
t_BACnetBIPV6_dissectors[0x0B] = function(buffer,pkt,tree,offset,npdu)
|
||||
return offset
|
||||
end
|
||||
|
||||
-- [0x0C] Distribute-Broadcast-To-Network
|
||||
t_BACnetBIPV6_dissectors[0x0C] = function(buffer,pkt,tree,offset,npdu)
|
||||
offset = bytes_dissector(tree,pf_BIPV6.OriginalSourceVirtualAddress,buffer,offset,3)
|
||||
local subtree = tree:add(pf_BIPV6.OriginalSourceEffectiveAddress,buffer(offset,18))
|
||||
offset = bytes_dissector(subtree,pf_BIPV6.OriginalSourceEffectiveAddress_ip,buffer,offset,16)
|
||||
offset = uint_dissector(subtree,pf_BIPV6.OriginalSourceEffectiveAddress_port,buffer,offset,2)
|
||||
return offset, buffer(offset)
|
||||
end
|
||||
|
||||
function p_BACnet_Port.dissector(tvb, pinfo, tree)
|
||||
local BIP_Type = tvb(0,1):uint()
|
||||
if BIP_Type == 0x81 then
|
||||
p_dissector_bipv4:call(tvb, pinfo, tree)
|
||||
elseif BIP_Type == 0x82 then
|
||||
p_BACnetBIPV6.dissector(tvb, pinfo, tree)
|
||||
end
|
||||
end
|
||||
|
||||
-- load the udp.port table
|
||||
local dt_udp_port = DissectorTable.get("udp.port")
|
||||
-- p_dissector_bvlc = dt_udp_port:get_dissector(47808)
|
||||
-- dt_udp_port:remove(47808, p_dissector_bvlc)
|
||||
-- dt_udp_port:add(47808, p_BACnetBIPV6)
|
||||
dt_udp_port:add(47808, p_BACnet_Port)
|
||||
|
||||
end
|
||||
@@ -0,0 +1,303 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2015 Nikola Jelic <nikola.jelic@euroicc.com>
|
||||
*
|
||||
* 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 "bacsec.h"
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
|
||||
#define TMP_BUF_LEN 2048
|
||||
|
||||
/* structures for keys - we use existing structures */
|
||||
|
||||
BACNET_KEY_ENTRY master_key;
|
||||
|
||||
BACNET_UPDATE_DISTRIBUTION_KEY distribution_key;
|
||||
|
||||
BACNET_UPDATE_KEY_SET key_sets;
|
||||
|
||||
static bool rand_set = false;
|
||||
static HMAC_CTX hmac_ctx;
|
||||
static EVP_CIPHER_CTX evp_ctx;
|
||||
static uint8_t tmp_buf[TMP_BUF_LEN];
|
||||
|
||||
static uint16_t next_mult_of_16(uint16_t arg)
|
||||
{
|
||||
if ((arg & 0xF) == 0)
|
||||
return arg;
|
||||
else
|
||||
return ((arg >> 4) + 1) << 4;
|
||||
}
|
||||
|
||||
/* signing/verification and encryption/decryption - port specific */
|
||||
int key_sign_msg(BACNET_KEY_ENTRY * key,
|
||||
uint8_t * msg,
|
||||
uint32_t msg_len,
|
||||
uint8_t * signature)
|
||||
{
|
||||
uint8_t full_signature[32]; /* longest case */
|
||||
HMAC_CTX_init(&hmac_ctx);
|
||||
switch (key_algorithm(key->key_identifier)) {
|
||||
case KIA_AES_MD5:
|
||||
HMAC_Init_ex(&hmac_ctx, &key->key[16], 16, EVP_md5(), NULL);
|
||||
break;
|
||||
case KIA_AES_SHA256:
|
||||
HMAC_Init_ex(&hmac_ctx, &key->key[16], 32, EVP_sha256(), NULL);
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
HMAC_Update(&hmac_ctx, msg, msg_len);
|
||||
HMAC_Final(&hmac_ctx, full_signature, NULL); /* we ignore the signature size */
|
||||
HMAC_CTX_cleanup(&hmac_ctx);
|
||||
memcpy(signature, full_signature, SIGNATURE_LEN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool key_verify_sign_msg(BACNET_KEY_ENTRY * key,
|
||||
uint8_t * msg,
|
||||
uint32_t msg_len,
|
||||
uint8_t * signature)
|
||||
{
|
||||
uint8_t full_signature[32]; /* longest case */
|
||||
HMAC_CTX_init(&hmac_ctx);
|
||||
switch (key_algorithm(key->key_identifier)) {
|
||||
case KIA_AES_MD5:
|
||||
HMAC_Init_ex(&hmac_ctx, &key->key[16], 16, EVP_md5(), NULL);
|
||||
break;
|
||||
case KIA_AES_SHA256:
|
||||
HMAC_Init_ex(&hmac_ctx, &key->key[16], 32, EVP_sha256(), NULL);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
HMAC_Update(&hmac_ctx, msg, msg_len);
|
||||
HMAC_Final(&hmac_ctx, full_signature, NULL); /* we ignore the signature size */
|
||||
HMAC_CTX_cleanup(&hmac_ctx);
|
||||
return (memcmp(signature, full_signature,
|
||||
SIGNATURE_LEN) == 0 ? true : false);
|
||||
}
|
||||
|
||||
int key_encrypt_msg(BACNET_KEY_ENTRY * key,
|
||||
uint8_t * msg,
|
||||
uint32_t msg_len,
|
||||
uint8_t * signature)
|
||||
{
|
||||
int outlen, outlen2;
|
||||
switch (key_algorithm(key->key_identifier)) {
|
||||
case KIA_AES_MD5:
|
||||
case KIA_AES_SHA256:
|
||||
EVP_EncryptInit_ex(&evp_ctx, EVP_aes_128_cbc(), NULL, key->key,
|
||||
signature);
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
EVP_EncryptUpdate(&evp_ctx, tmp_buf, &outlen, msg, msg_len);
|
||||
EVP_EncryptFinal(&evp_ctx, &msg[outlen], &outlen2);
|
||||
EVP_CIPHER_CTX_cleanup(&evp_ctx);
|
||||
if (outlen2 != 0)
|
||||
return -1;
|
||||
memcpy(msg, tmp_buf, msg_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool key_decrypt_msg(BACNET_KEY_ENTRY * key,
|
||||
uint8_t * msg,
|
||||
uint32_t msg_len,
|
||||
uint8_t * signature)
|
||||
{
|
||||
int outlen, outlen2;
|
||||
switch (key_algorithm(key->key_identifier)) {
|
||||
case KIA_AES_MD5:
|
||||
case KIA_AES_SHA256:
|
||||
EVP_DecryptInit_ex(&evp_ctx, EVP_aes_128_cbc(), NULL, key->key,
|
||||
signature);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (EVP_DecryptUpdate(&evp_ctx, tmp_buf, &outlen, msg, msg_len) == 0)
|
||||
return false;
|
||||
if (EVP_DecryptFinal(&evp_ctx, &msg[outlen], &outlen2) == 0)
|
||||
return false;
|
||||
EVP_CIPHER_CTX_cleanup(&evp_ctx);
|
||||
if (outlen2 != 0)
|
||||
return false;
|
||||
memcpy(msg, tmp_buf, msg_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
void key_set_padding(BACNET_KEY_ENTRY * key,
|
||||
int enc_len,
|
||||
uint16_t * padding_len,
|
||||
uint8_t * padding)
|
||||
{
|
||||
/* in the future, we should check for the block size, but for now it is always 16 */
|
||||
int i;
|
||||
uint16_t padlen = next_mult_of_16(enc_len + 2);
|
||||
(void) key;
|
||||
(void) padding_len;
|
||||
if (!rand_set) {
|
||||
srand(time(NULL));
|
||||
rand_set = true;
|
||||
}
|
||||
if (padlen > 2)
|
||||
for (i = 0; i < padlen - 2; i++)
|
||||
padding[i] = rand();
|
||||
}
|
||||
|
||||
BACNET_SECURITY_RESPONSE_CODE bacnet_master_key_set(BACNET_SET_MASTER_KEY *
|
||||
key)
|
||||
{
|
||||
memcpy(&master_key, &key->key, sizeof(BACNET_KEY_ENTRY));
|
||||
|
||||
return SEC_RESP_SUCCESS;
|
||||
}
|
||||
|
||||
BACNET_SECURITY_RESPONSE_CODE
|
||||
bacnet_distribution_key_update(BACNET_UPDATE_DISTRIBUTION_KEY * key)
|
||||
{
|
||||
memcpy(&distribution_key, key, sizeof(BACNET_KEY_ENTRY));
|
||||
|
||||
return SEC_RESP_SUCCESS;
|
||||
}
|
||||
|
||||
BACNET_SECURITY_RESPONSE_CODE bacnet_key_set_update(BACNET_UPDATE_KEY_SET *
|
||||
update_key_sets)
|
||||
{
|
||||
int i, j, k, l;
|
||||
bool found;
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (update_key_sets->set_rae[i]) {
|
||||
found = false;
|
||||
/* try with a valid set */
|
||||
for (j = 0; j < 2; j++)
|
||||
if ((key_sets.set_key_revision[j] ==
|
||||
update_key_sets->set_key_revision[i]) &&
|
||||
key_sets.set_rae[j]) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
/* try with an empty set */
|
||||
if (!found) {
|
||||
for (j = 0; j < 2; j++)
|
||||
if (!key_sets.set_rae[j]) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* failure */
|
||||
if (!found)
|
||||
return -SEC_RESP_UNKNOWN_KEY_REVISION;
|
||||
/* just in case we're writing over an empty set */
|
||||
key_sets.set_key_revision[j] =
|
||||
update_key_sets->set_key_revision[i];
|
||||
/* update revision activation and expiration time */
|
||||
key_sets.set_activation_time[j] =
|
||||
update_key_sets->set_activation_time[i];
|
||||
key_sets.set_expiration_time[j] =
|
||||
update_key_sets->set_expiration_time[i];
|
||||
/* should we clear the key set? */
|
||||
if (update_key_sets->set_clr[i]) {
|
||||
key_sets.set_key_count[j] = 0;
|
||||
}
|
||||
for (k = 0; k < update_key_sets->set_key_count[i]; k++) {
|
||||
found = false;
|
||||
for (l = 0; l < key_sets.set_key_count[j]; l++)
|
||||
if (update_key_sets->set_keys[i][k].key_identifier ==
|
||||
key_sets.set_keys[j][l].key_identifier) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
if (!update_key_sets->remove) { /* add key */
|
||||
/* check for available space */
|
||||
if (key_sets.set_key_count[j] == MAX_UPDATE_KEY_COUNT)
|
||||
return -SEC_RESP_TOO_MANY_KEYS;
|
||||
memcpy(&key_sets.set_keys[j][key_sets.
|
||||
set_key_count[j]],
|
||||
&update_key_sets->set_keys[i][k],
|
||||
sizeof(BACNET_KEY_ENTRY));
|
||||
key_sets.set_key_count[j]++;
|
||||
} /* else do nothing, successfuly */
|
||||
} else {
|
||||
if (!update_key_sets->remove) { /* update key */
|
||||
memcpy(&key_sets.set_keys[j][l],
|
||||
&update_key_sets->set_keys[i][k],
|
||||
sizeof(BACNET_KEY_ENTRY));
|
||||
} else { /* remove key */
|
||||
memmove(&key_sets.set_keys[j][l],
|
||||
&key_sets.set_keys[j][l + 1],
|
||||
sizeof(BACNET_KEY_ENTRY) *
|
||||
(key_sets.set_key_count[j] - l));
|
||||
key_sets.set_key_count[j]--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return SEC_RESP_SUCCESS;
|
||||
}
|
||||
|
||||
BACNET_SECURITY_RESPONSE_CODE bacnet_find_key(uint8_t revision,
|
||||
BACNET_KEY_ENTRY * key)
|
||||
{
|
||||
int i, j;
|
||||
unsigned int current_time = time(NULL);
|
||||
switch (key_number(key->key_identifier)) {
|
||||
case KIKN_DEVICE_MASTER:
|
||||
if (revision != 0)
|
||||
return -SEC_RESP_UNKNOWN_KEY_REVISION;
|
||||
else
|
||||
memcpy(key, &master_key, sizeof(BACNET_KEY_ENTRY));
|
||||
break;
|
||||
case KIKN_DISTRIBUTION:
|
||||
if (revision != distribution_key.key_revision)
|
||||
return -SEC_RESP_UNKNOWN_KEY_REVISION;
|
||||
else
|
||||
memcpy(key, &distribution_key.key, sizeof(BACNET_KEY_ENTRY));
|
||||
break;
|
||||
default: /* all other keys must be in a key set */
|
||||
for (i = 0; i < 2; i++) {
|
||||
if ((revision == key_sets.set_key_revision[i]) &&
|
||||
(key_sets.set_activation_time[i] <= current_time) &&
|
||||
(current_time <= key_sets.set_expiration_time[i])) {
|
||||
for (j = 0; j < key_sets.set_key_count[i]; j++)
|
||||
if (key->key_identifier ==
|
||||
key_sets.set_keys[i][j].key_identifier) {
|
||||
memcpy(key,
|
||||
&key_sets.set_keys[i][j].key_identifier,
|
||||
sizeof(BACNET_KEY_ENTRY));
|
||||
return SEC_RESP_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -SEC_RESP_UNKNOWN_KEY_REVISION;
|
||||
}
|
||||
return SEC_RESP_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
|
||||
#include <stdint.h> /* for standard integer types uint8_t etc. */
|
||||
#include <stdbool.h> /* for the standard bool type. */
|
||||
#include "bacdcode.h"
|
||||
#include "bip.h"
|
||||
#include "net.h"
|
||||
|
||||
/** @file linux/bip-init.c Initializes BACnet/IP interface (Linux). */
|
||||
|
||||
bool BIP_Debug = false;
|
||||
|
||||
/* gets an IP address by name, where name can be a
|
||||
string that is an IP address in dotted form, or
|
||||
a name that is a domain name
|
||||
returns 0 if not found, or
|
||||
an IP address in network byte order */
|
||||
long bip_getaddrbyname(
|
||||
const char *host_name)
|
||||
{
|
||||
struct hostent *host_ent;
|
||||
|
||||
if ((host_ent = gethostbyname(host_name)) == NULL)
|
||||
return 0;
|
||||
|
||||
return *(long *) host_ent->h_addr;
|
||||
}
|
||||
|
||||
static int get_local_ifr_ioctl(
|
||||
char *ifname,
|
||||
struct ifreq *ifr,
|
||||
int request)
|
||||
{
|
||||
int fd;
|
||||
int rv; /* return value */
|
||||
|
||||
strncpy(ifr->ifr_name, ifname, sizeof(ifr->ifr_name));
|
||||
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (fd < 0) {
|
||||
rv = fd;
|
||||
} else {
|
||||
rv = ioctl(fd, request, ifr);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* forward prototype required for compilers */
|
||||
int get_local_address_ioctl(
|
||||
char *ifname,
|
||||
struct in_addr *addr,
|
||||
int request);
|
||||
|
||||
int get_local_address_ioctl(
|
||||
char *ifname,
|
||||
struct in_addr *addr,
|
||||
int request)
|
||||
{
|
||||
struct ifreq ifr = { {{0}} };
|
||||
struct sockaddr_in *tcpip_address;
|
||||
int rv; /* return value */
|
||||
|
||||
rv = get_local_ifr_ioctl(ifname, &ifr, request);
|
||||
if (rv >= 0) {
|
||||
tcpip_address = (struct sockaddr_in *) &ifr.ifr_addr;
|
||||
memcpy(addr, &tcpip_address->sin_addr, sizeof(struct in_addr));
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** Gets the local IP address and local broadcast address from the system,
|
||||
* and saves it into the BACnet/IP data structures.
|
||||
*
|
||||
* @param ifname [in] The named interface to use for the network layer.
|
||||
* Eg, for Linux, ifname is eth0, ath0, arc0, and others.
|
||||
*/
|
||||
void bip_set_interface(
|
||||
char *ifname)
|
||||
{
|
||||
struct in_addr local_address;
|
||||
struct in_addr broadcast_address;
|
||||
struct in_addr netmask;
|
||||
int rv = 0;
|
||||
|
||||
/* setup local address */
|
||||
rv = get_local_address_ioctl(ifname, &local_address, SIOCGIFADDR);
|
||||
if (rv < 0) {
|
||||
local_address.s_addr = 0;
|
||||
}
|
||||
bip_set_addr(local_address.s_addr);
|
||||
if (BIP_Debug) {
|
||||
fprintf(stderr, "Interface: %s\n", ifname);
|
||||
fprintf(stderr, "IP Address: %s\n", inet_ntoa(local_address));
|
||||
}
|
||||
/* setup local broadcast address */
|
||||
rv = get_local_address_ioctl(ifname, &netmask, SIOCGIFNETMASK);
|
||||
|
||||
if (rv < 0) {
|
||||
broadcast_address.s_addr = ~0;
|
||||
}
|
||||
else {
|
||||
broadcast_address = local_address;
|
||||
broadcast_address.s_addr |= (~netmask.s_addr);
|
||||
}
|
||||
bip_set_broadcast_addr(broadcast_address.s_addr);
|
||||
if (BIP_Debug) {
|
||||
fprintf(stderr, "IP Broadcast Address: %s\n",
|
||||
inet_ntoa(broadcast_address));
|
||||
fprintf(stderr, "UDP Port: 0x%04X [%hu]\n", ntohs(bip_get_port()),
|
||||
ntohs(bip_get_port()));
|
||||
}
|
||||
}
|
||||
|
||||
/** Initialize the BACnet/IP services at the given interface.
|
||||
* @ingroup DLBIP
|
||||
* -# Gets the local IP address and local broadcast address from the system,
|
||||
* and saves it into the BACnet/IP data structures.
|
||||
* -# Opens a UDP socket
|
||||
* -# Configures the socket for sending and receiving
|
||||
* -# Configures the socket so it can send broadcasts
|
||||
* -# Binds the socket to the local IP address at the specified port for
|
||||
* BACnet/IP (by default, 0xBAC0 = 47808).
|
||||
*
|
||||
* @note For Linux, ifname is eth0, ath0, arc0, and others.
|
||||
*
|
||||
* @param ifname [in] The named interface to use for the network layer.
|
||||
* If NULL, the "eth0" interface is assigned.
|
||||
* @return True if the socket is successfully opened for BACnet/IP,
|
||||
* else False if the socket functions fail.
|
||||
*/
|
||||
bool bip_init(
|
||||
char *ifname)
|
||||
{
|
||||
int status = 0; /* return from socket lib calls */
|
||||
struct sockaddr_in sin;
|
||||
int sockopt = 0;
|
||||
int sock_fd = -1;
|
||||
|
||||
if (ifname)
|
||||
bip_set_interface(ifname);
|
||||
else
|
||||
bip_set_interface("eth0");
|
||||
/* assumes that the driver has already been initialized */
|
||||
sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
bip_set_socket(sock_fd);
|
||||
if (sock_fd < 0)
|
||||
return false;
|
||||
/* Allow us to use the same socket for sending and receiving */
|
||||
/* This makes sure that the src port is correct when sending */
|
||||
sockopt = 1;
|
||||
status =
|
||||
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &sockopt,
|
||||
sizeof(sockopt));
|
||||
if (status < 0) {
|
||||
close(sock_fd);
|
||||
bip_set_socket(-1);
|
||||
return status;
|
||||
}
|
||||
/* allow us to send a broadcast */
|
||||
status =
|
||||
setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, &sockopt,
|
||||
sizeof(sockopt));
|
||||
if (status < 0) {
|
||||
close(sock_fd);
|
||||
bip_set_socket(-1);
|
||||
return false;
|
||||
}
|
||||
/* bind the socket to the local port number and IP address */
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
sin.sin_port = bip_get_port();
|
||||
memset(&(sin.sin_zero), '\0', sizeof(sin.sin_zero));
|
||||
status =
|
||||
bind(sock_fd, (const struct sockaddr *) &sin, sizeof(struct sockaddr));
|
||||
if (status < 0) {
|
||||
close(sock_fd);
|
||||
bip_set_socket(-1);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Cleanup and close out the BACnet/IP services by closing the socket.
|
||||
* @ingroup DLBIP
|
||||
*/
|
||||
void bip_cleanup(
|
||||
void)
|
||||
{
|
||||
int sock_fd = 0;
|
||||
|
||||
if (bip_valid()) {
|
||||
sock_fd = bip_socket();
|
||||
close(sock_fd);
|
||||
}
|
||||
bip_set_socket(-1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** Get the netmask of the BACnet/IP's interface via an ioctl() call.
|
||||
* @param netmask [out] The netmask, in host order.
|
||||
* @return 0 on success, else the error from the ioctl() call.
|
||||
*/
|
||||
int bip_get_local_netmask(
|
||||
struct in_addr *netmask)
|
||||
{
|
||||
int rv;
|
||||
char *ifname = getenv("BACNET_IFACE"); /* will probably be null */
|
||||
if (ifname == NULL)
|
||||
ifname = "eth0";
|
||||
rv = get_local_address_ioctl(ifname, netmask, SIOCGIFNETMASK);
|
||||
return rv;
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2016 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h> /* for standard integer types uint8_t etc. */
|
||||
#include <stdbool.h> /* for the standard bool type. */
|
||||
#include "bacdcode.h"
|
||||
#include "config.h"
|
||||
#include "bip6.h"
|
||||
#include "debug.h"
|
||||
#include "device.h"
|
||||
#include "net.h"
|
||||
#include <ifaddrs.h>
|
||||
|
||||
static void debug_print_ipv6(const char *str, const struct in6_addr * addr) {
|
||||
debug_printf( "BIP6: %s %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x\n",
|
||||
str,
|
||||
(int)addr->s6_addr[0], (int)addr->s6_addr[1],
|
||||
(int)addr->s6_addr[2], (int)addr->s6_addr[3],
|
||||
(int)addr->s6_addr[4], (int)addr->s6_addr[5],
|
||||
(int)addr->s6_addr[6], (int)addr->s6_addr[7],
|
||||
(int)addr->s6_addr[8], (int)addr->s6_addr[9],
|
||||
(int)addr->s6_addr[10], (int)addr->s6_addr[11],
|
||||
(int)addr->s6_addr[12], (int)addr->s6_addr[13],
|
||||
(int)addr->s6_addr[14], (int)addr->s6_addr[15]);
|
||||
}
|
||||
|
||||
/** @file linux/bip6.c Initializes BACnet/IPv6 interface (Linux). */
|
||||
|
||||
/* unix socket */
|
||||
static int BIP6_Socket = -1;
|
||||
/* local address - filled by init functions */
|
||||
static BACNET_IP6_ADDRESS BIP6_Addr;
|
||||
static BACNET_IP6_ADDRESS BIP6_Broadcast_Addr;
|
||||
|
||||
/**
|
||||
* Set the interface name. On Linux, ifname is the /dev/ name of the interface.
|
||||
*
|
||||
* @param ifname - C string for name or text address
|
||||
*/
|
||||
void bip6_set_interface(
|
||||
char *ifname)
|
||||
{
|
||||
struct ifaddrs *ifa, *ifa_tmp;
|
||||
struct sockaddr_in6 *sin;
|
||||
bool found = false;
|
||||
|
||||
if (getifaddrs(&ifa) == -1) {
|
||||
perror("BIP6: getifaddrs failed");
|
||||
exit(1);
|
||||
}
|
||||
ifa_tmp = ifa;
|
||||
debug_printf("BIP6: seeking interface: %s\n", ifname);
|
||||
while (ifa_tmp) {
|
||||
if ((ifa_tmp->ifa_addr) &&
|
||||
(ifa_tmp->ifa_addr->sa_family == AF_INET6)) {
|
||||
debug_printf("BIP6: found interface: %s\n", ifa_tmp->ifa_name);
|
||||
}
|
||||
if ((ifa_tmp->ifa_addr) &&
|
||||
(ifa_tmp->ifa_addr->sa_family == AF_INET6) &&
|
||||
(strcasecmp(ifa_tmp->ifa_name,ifname) == 0)) {
|
||||
sin = (struct sockaddr_in6*) ifa_tmp->ifa_addr;
|
||||
bvlc6_address_set(&BIP6_Addr,
|
||||
ntohs(sin->sin6_addr.s6_addr16[0]),
|
||||
ntohs(sin->sin6_addr.s6_addr16[1]),
|
||||
ntohs(sin->sin6_addr.s6_addr16[2]),
|
||||
ntohs(sin->sin6_addr.s6_addr16[3]),
|
||||
ntohs(sin->sin6_addr.s6_addr16[4]),
|
||||
ntohs(sin->sin6_addr.s6_addr16[5]),
|
||||
ntohs(sin->sin6_addr.s6_addr16[6]),
|
||||
ntohs(sin->sin6_addr.s6_addr16[7]));
|
||||
debug_print_ipv6(ifname, (&sin->sin6_addr));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
ifa_tmp = ifa_tmp->ifa_next;
|
||||
}
|
||||
if (!found) {
|
||||
debug_printf("BIP6: unable to set interface: %s\n", ifname);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the BACnet IPv6 UDP port number
|
||||
*
|
||||
* @param port - IPv6 UDP port number
|
||||
*/
|
||||
void bip6_set_port(
|
||||
uint16_t port)
|
||||
{
|
||||
BIP6_Addr.port = port;
|
||||
BIP6_Broadcast_Addr.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the BACnet IPv6 UDP port number
|
||||
*
|
||||
* @return IPv6 UDP port number
|
||||
*/
|
||||
uint16_t bip6_get_port(
|
||||
void)
|
||||
{
|
||||
return BIP6_Addr.port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the BACnet broadcast address for my interface.
|
||||
* Used as dest address in messages sent as BROADCAST
|
||||
*
|
||||
* @param addr - IPv6 source address
|
||||
*/
|
||||
void bip6_get_broadcast_address(
|
||||
BACNET_ADDRESS * addr)
|
||||
{
|
||||
if (addr) {
|
||||
addr->net = BACNET_BROADCAST_NETWORK;
|
||||
addr->mac_len = 0;
|
||||
addr->len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IPv6 address for my interface. Used as src address in messages sent.
|
||||
*
|
||||
* @param addr - IPv6 source address
|
||||
*/
|
||||
void bip6_get_my_address(
|
||||
BACNET_ADDRESS * addr)
|
||||
{
|
||||
uint32_t device_id = 0;
|
||||
|
||||
if (addr) {
|
||||
device_id = Device_Object_Instance_Number();
|
||||
bvlc6_vmac_address_set(addr, device_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the BACnet/IP address
|
||||
*
|
||||
* @param addr - network IPv6 address
|
||||
*/
|
||||
bool bip6_set_addr(
|
||||
BACNET_IP6_ADDRESS *addr)
|
||||
{
|
||||
return bvlc6_address_copy(&BIP6_Addr, addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the BACnet/IP address
|
||||
*
|
||||
* @return BACnet/IP address
|
||||
*/
|
||||
bool bip6_get_addr(
|
||||
BACNET_IP6_ADDRESS *addr)
|
||||
{
|
||||
return bvlc6_address_copy(addr, &BIP6_Addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the BACnet/IP address
|
||||
*
|
||||
* @param addr - network IPv6 address
|
||||
*/
|
||||
bool bip6_set_broadcast_addr(
|
||||
BACNET_IP6_ADDRESS *addr)
|
||||
{
|
||||
return bvlc6_address_copy(&BIP6_Broadcast_Addr, addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the BACnet/IP address
|
||||
*
|
||||
* @return BACnet/IP address
|
||||
*/
|
||||
bool bip6_get_broadcast_addr(
|
||||
BACNET_IP6_ADDRESS *addr)
|
||||
{
|
||||
return bvlc6_address_copy(addr, &BIP6_Broadcast_Addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* The send function for BACnet/IPv6 driver layer
|
||||
*
|
||||
* @param dest - Points to a BACNET_IP6_ADDRESS structure containing the
|
||||
* destination address.
|
||||
* @param mtu - the bytes of data to send
|
||||
* @param mtu_len - the number of bytes of data to send
|
||||
*
|
||||
* @return Upon successful completion, returns the number of bytes sent.
|
||||
* Otherwise, -1 shall be returned and errno set to indicate the error.
|
||||
*/
|
||||
int bip6_send_mpdu(
|
||||
BACNET_IP6_ADDRESS *dest,
|
||||
uint8_t * mtu,
|
||||
uint16_t mtu_len)
|
||||
{
|
||||
struct sockaddr_in6 bvlc_dest = { 0 };
|
||||
uint16_t addr16[8];
|
||||
|
||||
/* assumes that the driver has already been initialized */
|
||||
if (BIP6_Socket < 0) {
|
||||
return 0;
|
||||
}
|
||||
/* load destination IP address */
|
||||
bvlc_dest.sin6_family = AF_INET6;
|
||||
bvlc6_address_get(dest, &addr16[0], &addr16[1], &addr16[2], &addr16[3],
|
||||
&addr16[4], &addr16[5], &addr16[6], &addr16[7]);
|
||||
bvlc_dest.sin6_addr.s6_addr16[0] = htons(addr16[0]);
|
||||
bvlc_dest.sin6_addr.s6_addr16[1] = htons(addr16[1]);
|
||||
bvlc_dest.sin6_addr.s6_addr16[2] = htons(addr16[2]);
|
||||
bvlc_dest.sin6_addr.s6_addr16[3] = htons(addr16[3]);
|
||||
bvlc_dest.sin6_addr.s6_addr16[4] = htons(addr16[4]);
|
||||
bvlc_dest.sin6_addr.s6_addr16[5] = htons(addr16[5]);
|
||||
bvlc_dest.sin6_addr.s6_addr16[6] = htons(addr16[6]);
|
||||
bvlc_dest.sin6_addr.s6_addr16[7] = htons(addr16[7]);
|
||||
bvlc_dest.sin6_port = htons(dest->port);
|
||||
debug_print_ipv6("Sending MPDU->", &bvlc_dest.sin6_addr);
|
||||
/* Send the packet */
|
||||
return sendto(BIP6_Socket, (char *) mtu, mtu_len, 0,
|
||||
(struct sockaddr *) &bvlc_dest, sizeof(bvlc_dest));
|
||||
}
|
||||
|
||||
/**
|
||||
* BACnet/IP Datalink Receive handler.
|
||||
*
|
||||
* @param src - returns the source address
|
||||
* @param npdu - returns the NPDU buffer
|
||||
* @param max_npdu -maximum size of the NPDU buffer
|
||||
* @param timeout - number of milliseconds to wait for a packet
|
||||
*
|
||||
* @return Number of bytes received, or 0 if none or timeout.
|
||||
*/
|
||||
uint16_t bip6_receive(
|
||||
BACNET_ADDRESS * src,
|
||||
uint8_t * npdu,
|
||||
uint16_t max_npdu,
|
||||
unsigned timeout)
|
||||
{
|
||||
uint16_t npdu_len = 0; /* return value */
|
||||
fd_set read_fds;
|
||||
int max = 0;
|
||||
struct timeval select_timeout;
|
||||
struct sockaddr_in6 sin = { 0 };
|
||||
BACNET_IP6_ADDRESS addr = {{ 0 }};
|
||||
socklen_t sin_len = sizeof(sin);
|
||||
int received_bytes = 0;
|
||||
int offset = 0;
|
||||
uint16_t i = 0;
|
||||
|
||||
/* Make sure the socket is open */
|
||||
if (BIP6_Socket < 0) {
|
||||
return 0;
|
||||
}
|
||||
/* we could just use a non-blocking socket, but that consumes all
|
||||
the CPU time. We can use a timeout; it is only supported as
|
||||
a select. */
|
||||
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(BIP6_Socket, &read_fds);
|
||||
max = BIP6_Socket;
|
||||
/* see if there is a packet for us */
|
||||
if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) {
|
||||
received_bytes =
|
||||
recvfrom(BIP6_Socket, (char *) &npdu[0], max_npdu, 0,
|
||||
(struct sockaddr *) &sin, &sin_len);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
/* See if there is a problem */
|
||||
if (received_bytes < 0) {
|
||||
return 0;
|
||||
}
|
||||
/* no problem, just no bytes */
|
||||
if (received_bytes == 0) {
|
||||
return 0;
|
||||
}
|
||||
/* the signature of a BACnet/IPv6 packet */
|
||||
if (npdu[0] != BVLL_TYPE_BACNET_IP6) {
|
||||
return 0;
|
||||
}
|
||||
/* pass the packet into the BBMD handler */
|
||||
debug_print_ipv6("Received MPDU->", &sin.sin6_addr);
|
||||
bvlc6_address_set(&addr,
|
||||
ntohs(sin.sin6_addr.s6_addr16[0]),
|
||||
ntohs(sin.sin6_addr.s6_addr16[1]),
|
||||
ntohs(sin.sin6_addr.s6_addr16[2]),
|
||||
ntohs(sin.sin6_addr.s6_addr16[3]),
|
||||
ntohs(sin.sin6_addr.s6_addr16[4]),
|
||||
ntohs(sin.sin6_addr.s6_addr16[5]),
|
||||
ntohs(sin.sin6_addr.s6_addr16[6]),
|
||||
ntohs(sin.sin6_addr.s6_addr16[7]));
|
||||
addr.port = ntohs(sin.sin6_port);
|
||||
offset = bvlc6_handler(&addr, src, npdu, received_bytes);
|
||||
if (offset > 0) {
|
||||
npdu_len = received_bytes - offset;
|
||||
if (npdu_len <= max_npdu) {
|
||||
/* shift the buffer to return a valid NPDU */
|
||||
for (i = 0; i < npdu_len; i++) {
|
||||
npdu[i] = npdu[offset + i];
|
||||
}
|
||||
} else {
|
||||
npdu_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return npdu_len;
|
||||
}
|
||||
|
||||
/** Cleanup and close out the BACnet/IP services by closing the socket.
|
||||
* @ingroup DLBIP6
|
||||
*/
|
||||
void bip6_cleanup(
|
||||
void)
|
||||
{
|
||||
if (BIP6_Socket != -1) {
|
||||
close(BIP6_Socket);
|
||||
}
|
||||
BIP6_Socket = -1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** Initialize the BACnet/IP services at the given interface.
|
||||
* @ingroup DLBIP6
|
||||
* -# Gets the local IP address and local broadcast address from the system,
|
||||
* and saves it into the BACnet/IPv6 data structures.
|
||||
* -# Opens a UDP socket
|
||||
* -# Configures the socket for sending and receiving
|
||||
* -# Configures the socket so it can send multicasts
|
||||
* -# Binds the socket to the local IP address at the specified port for
|
||||
* BACnet/IPv6 (by default, 0xBAC0 = 47808).
|
||||
*
|
||||
* @note For Linux, ifname is eth0, ath0, arc0, and others.
|
||||
*
|
||||
* @param ifname [in] The named interface to use for the network layer.
|
||||
* If NULL, the "eth0" interface is assigned.
|
||||
* @return True if the socket is successfully opened for BACnet/IP,
|
||||
* else False if the socket functions fail.
|
||||
*/
|
||||
bool bip6_init(
|
||||
char *ifname)
|
||||
{
|
||||
int status = 0; /* return from socket lib calls */
|
||||
struct sockaddr_in6 server = {0};
|
||||
struct in6_addr broadcast_address;
|
||||
struct ipv6_mreq join_request;
|
||||
int sockopt = 0;
|
||||
|
||||
if (ifname) {
|
||||
bip6_set_interface(ifname);
|
||||
} else {
|
||||
bip6_set_interface("eth0");
|
||||
}
|
||||
if (BIP6_Addr.port == 0) {
|
||||
bip6_set_port(0xBAC0);
|
||||
}
|
||||
debug_printf("BIP6: IPv6 UDP port: 0x%04X\n", htons(BIP6_Addr.port));
|
||||
if (BIP6_Broadcast_Addr.address[0] == 0) {
|
||||
bvlc6_address_set(&BIP6_Broadcast_Addr,
|
||||
BIP6_MULTICAST_SITE_LOCAL, 0, 0, 0, 0, 0, 0,
|
||||
BIP6_MULTICAST_GROUP_ID);
|
||||
}
|
||||
/* assumes that the driver has already been initialized */
|
||||
BIP6_Socket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (BIP6_Socket < 0)
|
||||
return false;
|
||||
/* Allow us to use the same socket for sending and receiving */
|
||||
/* This makes sure that the src port is correct when sending */
|
||||
sockopt = 1;
|
||||
status =
|
||||
setsockopt(BIP6_Socket, SOL_SOCKET, SO_REUSEADDR, &sockopt,
|
||||
sizeof(sockopt));
|
||||
if (status < 0) {
|
||||
close(BIP6_Socket);
|
||||
BIP6_Socket = -1;
|
||||
return status;
|
||||
}
|
||||
/* allow us to send a broadcast */
|
||||
status =
|
||||
setsockopt(BIP6_Socket, SOL_SOCKET, SO_BROADCAST, &sockopt,
|
||||
sizeof(sockopt));
|
||||
if (status < 0) {
|
||||
close(BIP6_Socket);
|
||||
BIP6_Socket = -1;
|
||||
return false;
|
||||
}
|
||||
/* subscribe to a multicast address */
|
||||
memcpy(&broadcast_address.s6_addr[0], &BIP6_Broadcast_Addr.address[0], IP6_ADDRESS_MAX);
|
||||
memcpy(&join_request.ipv6mr_multiaddr, &broadcast_address, sizeof(struct in6_addr));
|
||||
/* Let system choose the interface */
|
||||
join_request.ipv6mr_interface = 0;
|
||||
status = setsockopt(BIP6_Socket, IPPROTO_IPV6, IPV6_JOIN_GROUP,
|
||||
&join_request, sizeof(join_request));
|
||||
if (status < 0) {
|
||||
perror("BIP: setsockopt(IPV6_JOIN_GROUP)");
|
||||
}
|
||||
|
||||
/* bind the socket to the local port number and IP address */
|
||||
server.sin6_family = AF_INET6;
|
||||
server.sin6_addr = in6addr_any;
|
||||
server.sin6_port = htons(BIP6_Addr.port);
|
||||
status = bind(BIP6_Socket, (const void*)&server, sizeof(server));
|
||||
if (status < 0) {
|
||||
perror("BIP: bind");
|
||||
close(BIP6_Socket);
|
||||
BIP6_Socket = -1;
|
||||
return false;
|
||||
}
|
||||
bvlc6_init();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,821 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2008 Steve Karg <skarg@users.sourceforge.net>
|
||||
* Updated by Nikola Jelic 2011 <nikola.jelic@euroicc.com>
|
||||
*
|
||||
* 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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/time.h>
|
||||
#include "bacdef.h"
|
||||
#include "bacaddr.h"
|
||||
#include "mstp.h"
|
||||
#include "dlmstp.h"
|
||||
#include "rs485.h"
|
||||
#include "npdu.h"
|
||||
#include "bits.h"
|
||||
#include "ringbuf.h"
|
||||
#include "debug.h"
|
||||
/* OS Specific include */
|
||||
#include "net.h"
|
||||
|
||||
/** @file linux/dlmstp.c Provides Linux-specific DataLink functions for MS/TP. */
|
||||
|
||||
/* Number of MS/TP Packets Rx/Tx */
|
||||
uint16_t MSTP_Packets = 0;
|
||||
|
||||
/* packet queues */
|
||||
static DLMSTP_PACKET Receive_Packet;
|
||||
/* mechanism to wait for a packet */
|
||||
/*
|
||||
static RT_COND Receive_Packet_Flag;
|
||||
static RT_MUTEX Receive_Packet_Mutex;
|
||||
*/
|
||||
static pthread_cond_t Receive_Packet_Flag;
|
||||
static pthread_mutex_t Receive_Packet_Mutex;
|
||||
/* mechanism to wait for a frame in state machine */
|
||||
/*
|
||||
static RT_COND Received_Frame_Flag;
|
||||
static RT_MUTEX Received_Frame_Mutex;
|
||||
*/
|
||||
|
||||
static pthread_cond_t Received_Frame_Flag;
|
||||
static pthread_mutex_t Received_Frame_Mutex;
|
||||
static pthread_cond_t Master_Done_Flag;
|
||||
static pthread_mutex_t Master_Done_Mutex;
|
||||
|
||||
/*RT_TASK Receive_Task, Fsm_Task;*/
|
||||
/* local MS/TP port data - shared with RS-485 */
|
||||
static volatile struct mstp_port_struct_t MSTP_Port;
|
||||
/* buffers needed by mstp port struct */
|
||||
static uint8_t TxBuffer[MAX_MPDU];
|
||||
static uint8_t RxBuffer[MAX_MPDU];
|
||||
/* 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];
|
||||
};
|
||||
/* count must be a power of 2 for ringbuf library */
|
||||
#ifndef MSTP_PDU_PACKET_COUNT
|
||||
#define MSTP_PDU_PACKET_COUNT 8
|
||||
#endif
|
||||
static struct mstp_pdu_packet PDU_Buffer[MSTP_PDU_PACKET_COUNT];
|
||||
static RING_BUFFER PDU_Queue;
|
||||
/* The minimum time without a DataAvailable or ReceiveError event */
|
||||
/* that a node must wait for a station to begin replying to a */
|
||||
/* confirmed request: 255 milliseconds. (Implementations may use */
|
||||
/* larger values for this timeout, not to exceed 300 milliseconds.) */
|
||||
static uint16_t Treply_timeout = 300;
|
||||
/* The 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.) */
|
||||
static uint8_t Tusage_timeout = 100;
|
||||
/* Timer that indicates line silence - and functions */
|
||||
|
||||
static struct timespec start;
|
||||
|
||||
/**
|
||||
* Calculate the time difference between two timespec values.
|
||||
*
|
||||
* @param l - The minued (time from which we subtract).
|
||||
* @param r - The subtrahend (time that is being subtracted).
|
||||
*
|
||||
* @returns True if the difference is negative, otherwise 0.
|
||||
*/
|
||||
static int timespec_subtract(struct timespec *result,
|
||||
const struct timespec *l,
|
||||
const struct timespec *r)
|
||||
{
|
||||
# define NS_PER_S 1000000000 // nano-seconds per second
|
||||
struct timespec right = *r;
|
||||
int secs;
|
||||
|
||||
// Perform the carry for the later subtraction by updating y.
|
||||
if (l->tv_nsec < right.tv_nsec) {
|
||||
secs = (right.tv_nsec - l->tv_nsec) / NS_PER_S + 1;
|
||||
right.tv_nsec -= NS_PER_S * secs;
|
||||
right.tv_sec += secs;
|
||||
}
|
||||
if (l->tv_nsec - right.tv_nsec > NS_PER_S) {
|
||||
secs = (l->tv_nsec - right.tv_nsec) / NS_PER_S;
|
||||
right.tv_nsec += NS_PER_S * secs;
|
||||
right.tv_sec -= secs;
|
||||
}
|
||||
|
||||
// Compute the time remaining. tv_nsec is certainly positive.
|
||||
result->tv_sec = l->tv_sec - right.tv_sec;
|
||||
result->tv_nsec = l->tv_nsec - right.tv_nsec;
|
||||
|
||||
return l->tv_sec < right.tv_sec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a certain number of nanoseconds to the specified time.
|
||||
*
|
||||
* @param ts - The time to which to add to.
|
||||
* @param ns - The number of nanoseconds to add. Allowed range
|
||||
* is -NS_PER_S..NS_PER_S (i.e., plus minus one second).
|
||||
*/
|
||||
static void timespec_add_ns(struct timespec *ts, long ns)
|
||||
{
|
||||
ts->tv_nsec += ns;
|
||||
if (ts->tv_nsec > NS_PER_S) {
|
||||
ts->tv_nsec -= NS_PER_S;
|
||||
ts->tv_sec += 1;
|
||||
} else if (ts->tv_nsec < 0) {
|
||||
ts->tv_nsec += NS_PER_S;
|
||||
ts->tv_sec -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t Timer_Silence(
|
||||
void *pArg)
|
||||
{
|
||||
struct timespec now, diff;
|
||||
int32_t res;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
timespec_subtract(&diff, &now, &start);
|
||||
res = ((diff.tv_sec) * 1000 + (diff.tv_nsec) / 1000000);
|
||||
|
||||
return (res >= 0 ? res : 0);
|
||||
}
|
||||
|
||||
static void Timer_Silence_Reset(
|
||||
void *pArg)
|
||||
{
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
}
|
||||
|
||||
static void get_abstime(
|
||||
struct timespec *abstime,
|
||||
unsigned long milliseconds)
|
||||
{
|
||||
clock_gettime(CLOCK_MONOTONIC, abstime);
|
||||
if (milliseconds > 1000) {
|
||||
fprintf(stderr, "DLMSTP: limited timeout of %lums to 1000ms\n",
|
||||
milliseconds);
|
||||
milliseconds = 1000;
|
||||
}
|
||||
timespec_add_ns(abstime, 1000000 * milliseconds);
|
||||
}
|
||||
|
||||
void dlmstp_cleanup(
|
||||
void)
|
||||
{
|
||||
pthread_cond_destroy(&Received_Frame_Flag);
|
||||
pthread_cond_destroy(&Receive_Packet_Flag);
|
||||
pthread_cond_destroy(&Master_Done_Flag);
|
||||
pthread_mutex_destroy(&Received_Frame_Mutex);
|
||||
pthread_mutex_destroy(&Receive_Packet_Mutex);
|
||||
pthread_mutex_destroy(&Master_Done_Mutex);
|
||||
}
|
||||
|
||||
/* returns number of bytes sent on success, zero on failure */
|
||||
int dlmstp_send_pdu(
|
||||
BACNET_ADDRESS * dest, /* destination address */
|
||||
BACNET_NPDU_DATA * npdu_data, /* network information */
|
||||
uint8_t * pdu, /* any data to be sent - may be null */
|
||||
unsigned pdu_len)
|
||||
{ /* number of bytes of data */
|
||||
int bytes_sent = 0;
|
||||
struct mstp_pdu_packet *pkt;
|
||||
unsigned i = 0;
|
||||
|
||||
pkt = (struct mstp_pdu_packet *) Ringbuf_Data_Peek(&PDU_Queue);
|
||||
if (pkt) {
|
||||
pkt->data_expecting_reply = npdu_data->data_expecting_reply;
|
||||
for (i = 0; i < pdu_len; i++) {
|
||||
pkt->buffer[i] = pdu[i];
|
||||
}
|
||||
pkt->length = pdu_len;
|
||||
if (dest && dest->mac_len) {
|
||||
pkt->destination_mac = dest->mac[0];
|
||||
} else {
|
||||
/* mac_len = 0 is a broadcast address */
|
||||
pkt->destination_mac = MSTP_BROADCAST_ADDRESS;
|
||||
}
|
||||
if (Ringbuf_Data_Put(&PDU_Queue, (uint8_t *)pkt)) {
|
||||
bytes_sent = pdu_len;
|
||||
}
|
||||
}
|
||||
|
||||
return bytes_sent;
|
||||
}
|
||||
|
||||
uint16_t dlmstp_receive(
|
||||
BACNET_ADDRESS * src, /* source address */
|
||||
uint8_t * pdu, /* PDU data */
|
||||
uint16_t max_pdu, /* amount of space available in the PDU */
|
||||
unsigned timeout)
|
||||
{ /* milliseconds to wait for a packet */
|
||||
uint16_t pdu_len = 0;
|
||||
struct timespec abstime;
|
||||
|
||||
(void) max_pdu;
|
||||
/* see if there is a packet available, and a place
|
||||
to put the reply (if necessary) and process it */
|
||||
pthread_mutex_lock(&Receive_Packet_Mutex);
|
||||
get_abstime(&abstime, timeout);
|
||||
pthread_cond_timedwait(&Receive_Packet_Flag, &Receive_Packet_Mutex,
|
||||
&abstime);
|
||||
if (Receive_Packet.ready) {
|
||||
if (Receive_Packet.pdu_len) {
|
||||
MSTP_Packets++;
|
||||
if (src) {
|
||||
memmove(src, &Receive_Packet.address,
|
||||
sizeof(Receive_Packet.address));
|
||||
}
|
||||
if (pdu) {
|
||||
memmove(pdu, &Receive_Packet.pdu,
|
||||
sizeof(Receive_Packet.pdu));
|
||||
}
|
||||
pdu_len = Receive_Packet.pdu_len;
|
||||
}
|
||||
Receive_Packet.ready = false;
|
||||
}
|
||||
pthread_mutex_unlock(&Receive_Packet_Mutex);
|
||||
|
||||
return pdu_len;
|
||||
}
|
||||
|
||||
static void *dlmstp_master_fsm_task(
|
||||
void *pArg)
|
||||
{
|
||||
uint32_t silence = 0;
|
||||
bool run_master = false;
|
||||
|
||||
(void) pArg;
|
||||
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 >= Treply_timeout)
|
||||
run_master = true;
|
||||
break;
|
||||
case MSTP_MASTER_STATE_POLL_FOR_MASTER:
|
||||
if (silence >= Tusage_timeout)
|
||||
run_master = true;
|
||||
break;
|
||||
default:
|
||||
run_master = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (run_master) {
|
||||
if (MSTP_Port.This_Station <= 127) {
|
||||
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;
|
||||
|
||||
pthread_mutex_lock(&Receive_Packet_Mutex);
|
||||
if (Receive_Packet.ready) {
|
||||
debug_printf("MS/TP: Dropped! Not Ready.\n");
|
||||
} else {
|
||||
/* bounds check - maybe this should send an abort? */
|
||||
pdu_len = mstp_port->DataLength;
|
||||
if (pdu_len > sizeof(Receive_Packet.pdu)) {
|
||||
pdu_len = sizeof(Receive_Packet.pdu);
|
||||
}
|
||||
if (pdu_len == 0) {
|
||||
debug_printf("MS/TP: PDU Length is 0!\n");
|
||||
}
|
||||
memmove((void *) &Receive_Packet.pdu[0],
|
||||
(void *) &mstp_port->InputBuffer[0], pdu_len);
|
||||
dlmstp_fill_bacnet_address(&Receive_Packet.address,
|
||||
mstp_port->SourceAddress);
|
||||
Receive_Packet.pdu_len = mstp_port->DataLength;
|
||||
Receive_Packet.ready = true;
|
||||
pthread_cond_signal(&Receive_Packet_Flag);
|
||||
}
|
||||
pthread_mutex_unlock(&Receive_Packet_Mutex);
|
||||
|
||||
return pdu_len;
|
||||
}
|
||||
|
||||
/* for the MS/TP state machine to use for getting data to send */
|
||||
/* Return: amount of PDU data */
|
||||
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;
|
||||
|
||||
(void) timeout;
|
||||
if (Ringbuf_Empty(&PDU_Queue)) {
|
||||
return 0;
|
||||
}
|
||||
pkt = (struct mstp_pdu_packet *) Ringbuf_Peek(&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);
|
||||
(void) Ringbuf_Pop(&PDU_Queue, NULL);
|
||||
|
||||
return pdu_len;
|
||||
}
|
||||
|
||||
static 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_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 0
|
||||
/* the NDPU priority doesn't get passed through the stack, and
|
||||
all outgoing messages have NORMAL priority */
|
||||
if (request.npdu_data.priority != reply.npdu_data.priority) {
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr,
|
||||
"DLMSTP: DER Compare failed: " "NPDU Priority mismatch.\n");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
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;
|
||||
|
||||
(void) timeout;
|
||||
if (Ringbuf_Empty(&PDU_Queue)) {
|
||||
return 0;
|
||||
}
|
||||
pkt = (struct mstp_pdu_packet *) Ringbuf_Peek(&PDU_Queue);
|
||||
/* is this the reply to the DER? */
|
||||
matched =
|
||||
dlmstp_compare_data_expecting_reply(&mstp_port->InputBuffer[0],
|
||||
mstp_port->DataLength, mstp_port->SourceAddress,
|
||||
(uint8_t *) & pkt->buffer[0], pkt->length, pkt->destination_mac);
|
||||
if (!matched) {
|
||||
return 0;
|
||||
}
|
||||
if (pkt->data_expecting_reply) {
|
||||
frame_type = FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY;
|
||||
} else {
|
||||
frame_type = FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY;
|
||||
}
|
||||
/* convert the PDU into the MSTP Frame */
|
||||
pdu_len = MSTP_Create_Frame(&mstp_port->OutputBuffer[0], /* <-- loading this */
|
||||
mstp_port->OutputBufferSize, frame_type, pkt->destination_mac,
|
||||
mstp_port->This_Station, (uint8_t *) & pkt->buffer[0], pkt->length);
|
||||
(void) Ringbuf_Pop(&PDU_Queue, NULL);
|
||||
|
||||
return pdu_len;
|
||||
}
|
||||
|
||||
void dlmstp_set_mac_address(
|
||||
uint8_t mac_address)
|
||||
{
|
||||
/* Master Nodes can only have address 0-127 */
|
||||
if (mac_address <= 127) {
|
||||
MSTP_Port.This_Station = mac_address;
|
||||
/* FIXME: implement your data storage */
|
||||
/* I2C_Write_Byte(
|
||||
EEPROM_DEVICE_ADDRESS,
|
||||
mac_address,
|
||||
EEPROM_MSTP_MAC_ADDR); */
|
||||
if (mac_address > MSTP_Port.Nmax_master)
|
||||
dlmstp_set_max_master(mac_address);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t dlmstp_mac_address(
|
||||
void)
|
||||
{
|
||||
return MSTP_Port.This_Station;
|
||||
}
|
||||
|
||||
/* This parameter represents the value of the Max_Info_Frames property of */
|
||||
/* the node's Device object. The value of Max_Info_Frames specifies the */
|
||||
/* maximum number of information frames the node may send before it must */
|
||||
/* pass the token. Max_Info_Frames may have different values on different */
|
||||
/* nodes. This may be used to allocate more or less of the available link */
|
||||
/* bandwidth to particular nodes. If Max_Info_Frames is not writable in a */
|
||||
/* node, its value shall be 1. */
|
||||
void dlmstp_set_max_info_frames(
|
||||
uint8_t max_info_frames)
|
||||
{
|
||||
if (max_info_frames >= 1) {
|
||||
MSTP_Port.Nmax_info_frames = max_info_frames;
|
||||
/* FIXME: implement your data storage */
|
||||
/* I2C_Write_Byte(
|
||||
EEPROM_DEVICE_ADDRESS,
|
||||
(uint8_t)max_info_frames,
|
||||
EEPROM_MSTP_MAX_INFO_FRAMES_ADDR); */
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t dlmstp_max_info_frames(
|
||||
void)
|
||||
{
|
||||
return MSTP_Port.Nmax_info_frames;
|
||||
}
|
||||
|
||||
/* This parameter represents the value of the Max_Master property of the */
|
||||
/* node's Device object. The value of Max_Master specifies the highest */
|
||||
/* allowable address for master nodes. The value of Max_Master shall be */
|
||||
/* less than or equal to 127. If Max_Master is not writable in a node, */
|
||||
/* its value shall be 127. */
|
||||
void dlmstp_set_max_master(
|
||||
uint8_t max_master)
|
||||
{
|
||||
if (max_master <= 127) {
|
||||
if (MSTP_Port.This_Station <= max_master) {
|
||||
MSTP_Port.Nmax_master = max_master;
|
||||
/* FIXME: implement your data storage */
|
||||
/* I2C_Write_Byte(
|
||||
EEPROM_DEVICE_ADDRESS,
|
||||
max_master,
|
||||
EEPROM_MSTP_MAX_MASTER_ADDR); */
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t dlmstp_max_master(
|
||||
void)
|
||||
{
|
||||
return MSTP_Port.Nmax_master;
|
||||
}
|
||||
|
||||
/* RS485 Baud Rate 9600, 19200, 38400, 57600, 115200 */
|
||||
void dlmstp_set_baud_rate(
|
||||
uint32_t baud)
|
||||
{
|
||||
RS485_Set_Baud_Rate(baud);
|
||||
}
|
||||
|
||||
uint32_t dlmstp_baud_rate(
|
||||
void)
|
||||
{
|
||||
return RS485_Get_Baud_Rate();
|
||||
}
|
||||
|
||||
void dlmstp_get_my_address(
|
||||
BACNET_ADDRESS * my_address)
|
||||
{
|
||||
int i = 0; /* counter */
|
||||
|
||||
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(
|
||||
char *ifname)
|
||||
{
|
||||
unsigned long hThread = 0;
|
||||
pthread_condattr_t attr;
|
||||
int rv = 0;
|
||||
|
||||
pthread_condattr_init(&attr);
|
||||
if ((rv = pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) != 0) {
|
||||
fprintf(stderr, "MS/TP Interface: %s\n failed to set MONOTONIC clock\n",
|
||||
ifname);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* initialize PDU queue */
|
||||
Ringbuf_Init(&PDU_Queue, (uint8_t *) & PDU_Buffer,
|
||||
sizeof(struct mstp_pdu_packet), MSTP_PDU_PACKET_COUNT);
|
||||
/* initialize packet queue */
|
||||
Receive_Packet.ready = false;
|
||||
Receive_Packet.pdu_len = 0;
|
||||
rv = pthread_cond_init(&Receive_Packet_Flag, &attr);
|
||||
if (rv != 0) {
|
||||
fprintf(stderr,
|
||||
"MS/TP Interface: %s\n cannot allocate PThread Condition.\n",
|
||||
ifname);
|
||||
exit(1);
|
||||
}
|
||||
rv = pthread_mutex_init(&Receive_Packet_Mutex, NULL);
|
||||
if (rv != 0) {
|
||||
fprintf(stderr,
|
||||
"MS/TP Interface: %s\n cannot allocate PThread Mutex.\n", ifname);
|
||||
exit(1);
|
||||
}
|
||||
/* initialize hardware */
|
||||
if (ifname) {
|
||||
RS485_Set_Interface(ifname);
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "MS/TP Interface: %s\n", ifname);
|
||||
#endif
|
||||
}
|
||||
RS485_Initialize();
|
||||
MSTP_Port.InputBuffer = &RxBuffer[0];
|
||||
MSTP_Port.InputBufferSize = sizeof(RxBuffer);
|
||||
MSTP_Port.OutputBuffer = &TxBuffer[0];
|
||||
MSTP_Port.OutputBufferSize = sizeof(TxBuffer);
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
MSTP_Port.SilenceTimer = Timer_Silence;
|
||||
MSTP_Port.SilenceTimerReset = Timer_Silence_Reset;
|
||||
MSTP_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
|
||||
/* start the threads */
|
||||
/* rv = pthread_create(&hThread, NULL, dlmstp_receive_fsm_task, NULL); */
|
||||
/* if (rv != 0) {
|
||||
fprintf(stderr, "Failed to start recive FSM task\n");
|
||||
} */
|
||||
rv = pthread_create(&hThread, NULL, dlmstp_master_fsm_task, NULL);
|
||||
if (rv != 0) {
|
||||
fprintf(stderr, "Failed to start Master Node FSM task\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef TEST_DLMSTP
|
||||
#include <stdio.h>
|
||||
|
||||
void apdu_handler(
|
||||
BACNET_ADDRESS * src, /* source address */
|
||||
uint8_t * apdu, /* APDU data */
|
||||
uint16_t pdu_len)
|
||||
{ /* for confirmed messages */
|
||||
(void) src;
|
||||
(void) apdu;
|
||||
(void) pdu_len;
|
||||
}
|
||||
|
||||
static char *Network_Interface = NULL;
|
||||
|
||||
int main(
|
||||
int argc,
|
||||
char *argv[])
|
||||
{
|
||||
uint16_t pdu_len = 0;
|
||||
|
||||
/* argv has the "COM4" or some other device */
|
||||
if (argc > 1) {
|
||||
Network_Interface = argv[1];
|
||||
}
|
||||
dlmstp_set_baud_rate(38400);
|
||||
dlmstp_set_mac_address(0x05);
|
||||
dlmstp_set_max_info_frames(DEFAULT_MAX_INFO_FRAMES);
|
||||
dlmstp_set_max_master(DEFAULT_MAX_MASTER);
|
||||
dlmstp_init(Network_Interface);
|
||||
/* forever task */
|
||||
for (;;) {
|
||||
pdu_len = dlmstp_receive(NULL, NULL, 0, UINT_MAX);
|
||||
MSTP_Create_And_Send_Frame(&MSTP_Port, FRAME_TYPE_TEST_REQUEST,
|
||||
MSTP_Port.SourceAddress, MSTP_Port.This_Station, NULL, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,33 @@
|
||||
#Makefile to build test case
|
||||
CC = gcc
|
||||
BASEDIR = .
|
||||
# -g for debugging with gdb
|
||||
DEFINES = -DBIG_ENDIAN=0 -DBACDL_MSTP=1 -DTEST_DLMSTP
|
||||
INCLUDES = -I. -I../../
|
||||
CFLAGS = -Wall $(INCLUDES) $(DEFINES) -g
|
||||
|
||||
SRCS = rs485.c \
|
||||
dlmstp.c \
|
||||
../../mstp.c \
|
||||
../../crc.c
|
||||
|
||||
OBJS = ${SRCS:.c=.o}
|
||||
|
||||
TARGET = dlmstp
|
||||
|
||||
all: ${TARGET}
|
||||
|
||||
${TARGET}: ${OBJS}
|
||||
${CC} -pthread -o $@ ${OBJS}
|
||||
|
||||
.c.o:
|
||||
${CC} -c ${CFLAGS} $*.c -o $@
|
||||
|
||||
depend:
|
||||
rm -f .depend
|
||||
${CC} -MM ${CFLAGS} *.c >> .depend
|
||||
|
||||
clean:
|
||||
rm -rf core ${TARGET} $(OBJS) *.bak *.1 *.ini
|
||||
|
||||
include: .depend
|
||||
@@ -0,0 +1,979 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2008 Steve Karg <skarg@users.sourceforge.net>
|
||||
*
|
||||
* 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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#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);
|
||||
sem_destroy(&poSharedData->Receive_Packet_Flag);
|
||||
pthread_cond_destroy(&poSharedData->Master_Done_Flag);
|
||||
pthread_mutex_destroy(&poSharedData->Received_Frame_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_Data_Peek(&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];
|
||||
if (Ringbuf_Data_Put(&poSharedData->PDU_Queue, (uint8_t *)pkt)) {
|
||||
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 = sem_timedwait(&poSharedData->Receive_Packet_Flag, &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;
|
||||
sem_post(&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_Peek(&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);
|
||||
(void) Ringbuf_Pop(&poSharedData->PDU_Queue, NULL);
|
||||
|
||||
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_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 0
|
||||
/* the NDPU priority doesn't get passed through the stack, and
|
||||
all outgoing messages have NORMAL priority */
|
||||
if (request.npdu_data.priority != reply.npdu_data.priority) {
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr,
|
||||
"DLMSTP: DER Compare failed: " "NPDU Priority mismatch.\n");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
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_Peek(&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) {
|
||||
/* Walk the rest of the ring buffer to see if we can find a match */
|
||||
while (!matched &&
|
||||
(pkt = (struct mstp_pdu_packet *)Ringbuf_Peek_Next(&poSharedData->PDU_Queue, (uint8_t *)pkt)) != NULL) {
|
||||
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) {
|
||||
/* Still didn't find a match so just bail out */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (pkt->data_expecting_reply) {
|
||||
frame_type = FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY;
|
||||
} else {
|
||||
frame_type = FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY;
|
||||
}
|
||||
/* convert the PDU into the MSTP Frame */
|
||||
pdu_len = MSTP_Create_Frame(&mstp_port->OutputBuffer[0], /* <-- loading this */
|
||||
mstp_port->OutputBufferSize, frame_type, pkt->destination_mac,
|
||||
mstp_port->This_Station, (uint8_t *) & pkt->buffer[0], pkt->length);
|
||||
/* This will pop the element no matter where we found it */
|
||||
(void) Ringbuf_Pop_Element(&poSharedData->PDU_Queue, (uint8_t *)pkt, NULL);
|
||||
|
||||
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 = sem_init(&poSharedData->Receive_Packet_Flag, 0, 0);
|
||||
if (rv != 0) {
|
||||
fprintf(stderr,
|
||||
"MS/TP Interface: %s\n cannot allocate PThread Condition.\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;
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
|
||||
*
|
||||
* 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 <semaphore.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "bacdef.h"
|
||||
#include "npdu.h"
|
||||
#include <termios.h>
|
||||
#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_SEM Receive_Packet_Flag;
|
||||
*/
|
||||
sem_t Receive_Packet_Flag;
|
||||
/* 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 <asm/termbits.h>, which is
|
||||
included by <termios.h> */
|
||||
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 */
|
||||
@@ -0,0 +1,458 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
|
||||
#include <stdint.h> /* for standard integer types uint8_t etc. */
|
||||
#include <stdbool.h> /* for the standard bool type. */
|
||||
|
||||
#include "net.h"
|
||||
#include "bacdef.h"
|
||||
#include "ethernet.h"
|
||||
#include "bacint.h"
|
||||
|
||||
/** @file linux/ethernet.c Provides Linux-specific functions for BACnet/Ethernet. */
|
||||
|
||||
/* commonly used comparison address for ethernet */
|
||||
uint8_t Ethernet_Broadcast[MAX_MAC_LEN] =
|
||||
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
/* commonly used empty address for ethernet quick compare */
|
||||
uint8_t Ethernet_Empty_MAC[MAX_MAC_LEN] = { 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
/* my local device data - MAC address */
|
||||
uint8_t Ethernet_MAC_Address[MAX_MAC_LEN] = { 0 };
|
||||
|
||||
static int eth802_sockfd = -1; /* 802.2 file handle */
|
||||
static struct sockaddr eth_addr = { 0 }; /* used for binding 802.2 */
|
||||
|
||||
bool ethernet_valid(
|
||||
void)
|
||||
{
|
||||
return (eth802_sockfd >= 0);
|
||||
}
|
||||
|
||||
void ethernet_cleanup(
|
||||
void)
|
||||
{
|
||||
if (ethernet_valid())
|
||||
close(eth802_sockfd);
|
||||
eth802_sockfd = -1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/*----------------------------------------------------------------------
|
||||
Portable function to set a socket into nonblocking mode.
|
||||
Calling this on a socket causes all future read() and write() calls on
|
||||
that socket to do only as much as they can immediately, and return
|
||||
without waiting.
|
||||
If no data can be read or written, they return -1 and set errno
|
||||
to EAGAIN (or EWOULDBLOCK).
|
||||
Thanks to Bjorn Reese for this code.
|
||||
----------------------------------------------------------------------*/
|
||||
int setNonblocking(
|
||||
int fd)
|
||||
{
|
||||
int flags;
|
||||
|
||||
if (-1 == (flags = fcntl(fd, F_GETFL, 0)))
|
||||
flags = 0;
|
||||
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* opens an 802.2 socket to receive and send packets */
|
||||
static int ethernet_bind(
|
||||
struct sockaddr *eth_addr,
|
||||
char *interface_name)
|
||||
{
|
||||
int sock_fd = -1; /* return value */
|
||||
#if 0
|
||||
int sockopt = 0;
|
||||
#endif
|
||||
int uid = 0;
|
||||
|
||||
fprintf(stderr, "ethernet: opening \"%s\"\n", interface_name);
|
||||
/* check to see if we are being run as root */
|
||||
uid = getuid();
|
||||
if (uid != 0) {
|
||||
fprintf(stderr,
|
||||
"ethernet: Unable to open an 802.2 socket. "
|
||||
"Try running with root priveleges.\n");
|
||||
return sock_fd;
|
||||
}
|
||||
/* note: on some systems you may have to add or enable in */
|
||||
/* modules.conf (or in modutils/alias on Debian with update-modules) */
|
||||
/* alias net-pf-17 af_packet */
|
||||
/* Then follow it by: # modprobe af_packet */
|
||||
/* Note: PF_INET/SOCK_PACKET has been replaced with
|
||||
PF_PACKET/(SOCK_PACKET, SOCK_DGRAM, SOCK_RAW). */
|
||||
|
||||
/* Attempt to open the socket for 802.2 ethernet frames */
|
||||
if ((sock_fd = socket(PF_INET, SOCK_PACKET, htons(ETH_P_802_2))) < 0) {
|
||||
/* Error occured */
|
||||
fprintf(stderr, "ethernet: Error opening socket: %s\n",
|
||||
strerror(errno));
|
||||
fprintf(stderr,
|
||||
"You might need to add the following to modules.conf\n"
|
||||
"(or in /etc/modutils/alias on Debian with update-modules):\n"
|
||||
"alias net-pf-17 af_packet\n"
|
||||
"Also, add af_packet to /etc/modules.\n" "Then follow it by:\n"
|
||||
"# modprobe af_packet\n");
|
||||
exit(-1);
|
||||
}
|
||||
#if 0
|
||||
/* It is very advisable to do a IP_HDRINCL call, to make sure
|
||||
that the kernel knows the header is included in the data,
|
||||
and doesn't insert its own header into the packet before our data */
|
||||
if (setsockopt(sock_fd, IPPROTO_IP, IP_HDRINCL, &sockopt,
|
||||
sizeof(sockopt)) < 0) {
|
||||
printf("Warning: Cannot set HDRINCL!\n");
|
||||
}
|
||||
#endif
|
||||
/* Bind the socket to an address */
|
||||
eth_addr->sa_family = PF_INET;
|
||||
/* Clear the memory before copying */
|
||||
memset(eth_addr->sa_data, '\0', sizeof(eth_addr->sa_data));
|
||||
/* Strcpy the interface name into the address */
|
||||
strncpy(eth_addr->sa_data, interface_name, sizeof(eth_addr->sa_data) - 1);
|
||||
fprintf(stderr, "ethernet: binding \"%s\"\n", eth_addr->sa_data);
|
||||
/* Attempt to bind the socket to the interface */
|
||||
if (bind(sock_fd, eth_addr, sizeof(struct sockaddr)) != 0) {
|
||||
/* Bind problem, close socket and return */
|
||||
fprintf(stderr, "ethernet: Unable to bind 802.2 socket : %s\n",
|
||||
strerror(errno));
|
||||
fprintf(stderr,
|
||||
"You might need to add the following to modules.conf\n"
|
||||
"(or in /etc/modutils/alias on Debian with update-modules):\n"
|
||||
"alias net-pf-17 af_packet\n"
|
||||
"Also, add af_packet to /etc/modules.\n" "Then follow it by:\n"
|
||||
"# modprobe af_packet\n");
|
||||
/* Close the socket */
|
||||
close(sock_fd);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
atexit(ethernet_cleanup);
|
||||
|
||||
return sock_fd;
|
||||
}
|
||||
|
||||
/* function to find the local ethernet MAC address */
|
||||
static int get_local_hwaddr(
|
||||
const char *ifname,
|
||||
unsigned char *mac)
|
||||
{
|
||||
struct ifreq ifr;
|
||||
int fd;
|
||||
int rv; /* return value - error value from df or ioctl call */
|
||||
|
||||
/* determine the local MAC address */
|
||||
strcpy(ifr.ifr_name, ifname);
|
||||
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (fd < 0)
|
||||
rv = fd;
|
||||
else {
|
||||
rv = ioctl(fd, SIOCGIFHWADDR, &ifr);
|
||||
if (rv >= 0) /* worked okay */
|
||||
memcpy(mac, ifr.ifr_hwaddr.sa_data, IFHWADDRLEN);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool ethernet_init(
|
||||
char *interface_name)
|
||||
{
|
||||
if (interface_name) {
|
||||
get_local_hwaddr(interface_name, Ethernet_MAC_Address);
|
||||
eth802_sockfd = ethernet_bind(ð_addr, interface_name);
|
||||
} else {
|
||||
get_local_hwaddr("eth0", Ethernet_MAC_Address);
|
||||
eth802_sockfd = ethernet_bind(ð_addr, "eth0");
|
||||
}
|
||||
|
||||
return ethernet_valid();
|
||||
}
|
||||
|
||||
int ethernet_send(
|
||||
uint8_t * mtu,
|
||||
int mtu_len)
|
||||
{
|
||||
int bytes = 0;
|
||||
|
||||
/* Send the packet */
|
||||
bytes =
|
||||
sendto(eth802_sockfd, &mtu, mtu_len, 0, (struct sockaddr *) ð_addr,
|
||||
sizeof(struct sockaddr));
|
||||
/* did it get sent? */
|
||||
if (bytes < 0)
|
||||
fprintf(stderr, "ethernet: Error sending packet: %s\n",
|
||||
strerror(errno));
|
||||
|
||||
return bytes;
|
||||
|
||||
}
|
||||
|
||||
/* function to send a packet out the 802.2 socket */
|
||||
/* returns number of bytes sent on success, negative on failure */
|
||||
int ethernet_send_pdu(
|
||||
BACNET_ADDRESS * dest, /* destination address */
|
||||
BACNET_NPDU_DATA * npdu_data, /* network information */
|
||||
uint8_t * pdu, /* any data to be sent - may be null */
|
||||
unsigned pdu_len)
|
||||
{ /* number of bytes of data */
|
||||
int i = 0; /* counter */
|
||||
int bytes = 0;
|
||||
BACNET_ADDRESS src = { 0 }; /* source address for npdu */
|
||||
uint8_t mtu[MAX_MPDU] = { 0 }; /* our buffer */
|
||||
int mtu_len = 0;
|
||||
|
||||
(void) npdu_data;
|
||||
/* load the BACnet address for NPDU data */
|
||||
for (i = 0; i < 6; i++) {
|
||||
src.mac[i] = Ethernet_MAC_Address[i];
|
||||
src.mac_len++;
|
||||
}
|
||||
|
||||
/* don't waste time if the socket is not valid */
|
||||
if (eth802_sockfd < 0) {
|
||||
fprintf(stderr, "ethernet: 802.2 socket is invalid!\n");
|
||||
return -1;
|
||||
}
|
||||
/* load destination ethernet MAC address */
|
||||
if (dest->mac_len == 6) {
|
||||
for (i = 0; i < 6; i++) {
|
||||
mtu[i] = dest->mac[i];
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "ethernet: invalid destination MAC address!\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* load source ethernet MAC address */
|
||||
if (src.mac_len == 6) {
|
||||
for (i = 0; i < 6; i++) {
|
||||
mtu[6 + i] = src.mac[i];
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "ethernet: invalid source MAC address!\n");
|
||||
return -3;
|
||||
}
|
||||
/* Logical PDU portion */
|
||||
mtu[14] = 0x82; /* DSAP for BACnet */
|
||||
mtu[15] = 0x82; /* SSAP for BACnet */
|
||||
mtu[16] = 0x03; /* Control byte in header */
|
||||
mtu_len = 17;
|
||||
if ((mtu_len + pdu_len) > MAX_MPDU) {
|
||||
fprintf(stderr, "ethernet: PDU is too big to send!\n");
|
||||
return -4;
|
||||
}
|
||||
memcpy(&mtu[mtu_len], pdu, pdu_len);
|
||||
mtu_len += pdu_len;
|
||||
/* packet length - only the logical portion, not the address */
|
||||
encode_unsigned16(&mtu[12], 3 + pdu_len);
|
||||
|
||||
/* Send the packet */
|
||||
bytes =
|
||||
sendto(eth802_sockfd, &mtu, mtu_len, 0, (struct sockaddr *) ð_addr,
|
||||
sizeof(struct sockaddr));
|
||||
/* did it get sent? */
|
||||
if (bytes < 0)
|
||||
fprintf(stderr, "ethernet: Error sending packet: %s\n",
|
||||
strerror(errno));
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/* receives an 802.2 framed packet */
|
||||
/* returns the number of octets in the PDU, or zero on failure */
|
||||
uint16_t ethernet_receive(
|
||||
BACNET_ADDRESS * src, /* source address */
|
||||
uint8_t * pdu, /* PDU data */
|
||||
uint16_t max_pdu, /* amount of space available in the PDU */
|
||||
unsigned timeout)
|
||||
{ /* number of milliseconds to wait for a packet */
|
||||
int received_bytes;
|
||||
uint8_t buf[MAX_MPDU] = { 0 }; /* data */
|
||||
uint16_t pdu_len = 0; /* return value */
|
||||
fd_set read_fds;
|
||||
int max;
|
||||
struct timeval select_timeout;
|
||||
|
||||
/* Make sure the socket is open */
|
||||
if (eth802_sockfd <= 0)
|
||||
return 0;
|
||||
|
||||
/* we could just use a non-blocking socket, but that consumes all
|
||||
the CPU time. We can use a timeout; it is only supported as
|
||||
a select. */
|
||||
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(eth802_sockfd, &read_fds);
|
||||
max = eth802_sockfd;
|
||||
|
||||
if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0)
|
||||
received_bytes = read(eth802_sockfd, &buf[0], sizeof(buf));
|
||||
else
|
||||
return 0;
|
||||
|
||||
/* See if there is a problem */
|
||||
if (received_bytes < 0) {
|
||||
/* EAGAIN Non-blocking I/O has been selected */
|
||||
/* using O_NONBLOCK and no data */
|
||||
/* was immediately available for reading. */
|
||||
if (errno != EAGAIN)
|
||||
fprintf(stderr, "ethernet: Read error in receiving packet: %s\n",
|
||||
strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (received_bytes == 0)
|
||||
return 0;
|
||||
|
||||
/* the signature of an 802.2 BACnet packet */
|
||||
if ((buf[14] != 0x82) && (buf[15] != 0x82)) {
|
||||
/*fprintf(stderr,"ethernet: Non-BACnet packet\n"); */
|
||||
return 0;
|
||||
}
|
||||
/* copy the source address */
|
||||
src->mac_len = 6;
|
||||
memmove(src->mac, &buf[6], 6);
|
||||
|
||||
/* check destination address for when */
|
||||
/* the Ethernet card is in promiscious mode */
|
||||
if ((memcmp(&buf[0], Ethernet_MAC_Address, 6) != 0)
|
||||
&& (memcmp(&buf[0], Ethernet_Broadcast, 6) != 0)) {
|
||||
/*fprintf(stderr, "ethernet: This packet isn't for us\n"); */
|
||||
return 0;
|
||||
}
|
||||
|
||||
(void) decode_unsigned16(&buf[12], &pdu_len);
|
||||
pdu_len -= 3 /* DSAP, SSAP, LLC Control */ ;
|
||||
/* copy the buffer into the PDU */
|
||||
if (pdu_len < max_pdu)
|
||||
memmove(&pdu[0], &buf[17], pdu_len);
|
||||
/* ignore packets that are too large */
|
||||
else
|
||||
pdu_len = 0;
|
||||
|
||||
|
||||
return pdu_len;
|
||||
}
|
||||
|
||||
void ethernet_set_my_address(
|
||||
BACNET_ADDRESS * my_address)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (i = 0; i < 6; i++) {
|
||||
Ethernet_MAC_Address[i] = my_address->mac[i];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void ethernet_get_my_address(
|
||||
BACNET_ADDRESS * my_address)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
my_address->mac_len = 0;
|
||||
for (i = 0; i < 6; i++) {
|
||||
my_address->mac[i] = Ethernet_MAC_Address[i];
|
||||
my_address->mac_len++;
|
||||
}
|
||||
my_address->net = 0; /* DNET=0 is local only, no routing */
|
||||
my_address->len = 0;
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
my_address->adr[i] = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void ethernet_get_broadcast_address(
|
||||
BACNET_ADDRESS * dest)
|
||||
{ /* destination address */
|
||||
int i = 0; /* counter */
|
||||
|
||||
if (dest) {
|
||||
for (i = 0; i < 6; i++) {
|
||||
dest->mac[i] = Ethernet_Broadcast[i];
|
||||
}
|
||||
dest->mac_len = 6;
|
||||
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;
|
||||
}
|
||||
|
||||
void ethernet_debug_address(
|
||||
const char *info,
|
||||
BACNET_ADDRESS * dest)
|
||||
{
|
||||
int i = 0; /* counter */
|
||||
|
||||
if (info)
|
||||
fprintf(stderr, "%s", info);
|
||||
if (dest) {
|
||||
fprintf(stderr, "Address:\n");
|
||||
fprintf(stderr, " MAC Length=%d\n", dest->mac_len);
|
||||
fprintf(stderr, " MAC Address=");
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
fprintf(stderr, "%02X ", (unsigned) dest->mac[i]);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, " Net=%hu\n", dest->net);
|
||||
fprintf(stderr, " Len=%d\n", dest->len);
|
||||
fprintf(stderr, " Adr=");
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
fprintf(stderr, "%02X ", (unsigned) dest->adr[i]);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2008 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307
|
||||
USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
/* OS specific include*/
|
||||
#include "net.h"
|
||||
#include "timer.h"
|
||||
/* local includes */
|
||||
#include "bytes.h"
|
||||
#include "rs485.h"
|
||||
#include "crc.h"
|
||||
#include "mstp.h"
|
||||
#include "dlmstp.h"
|
||||
#include "mstptext.h"
|
||||
#include "bacint.h"
|
||||
|
||||
/** @file linux/mstpsnap.c Example application testing BACnet MS/TP on Linux. */
|
||||
|
||||
#ifndef max
|
||||
#define max(a,b) (((a) (b)) ? (a) : (b))
|
||||
#define min(a,b) (((a) < (b)) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
/* local port data - shared with RS-485 */
|
||||
static volatile struct mstp_port_struct_t MSTP_Port;
|
||||
/* buffers needed by mstp port struct */
|
||||
static uint8_t RxBuffer[MAX_MPDU];
|
||||
static uint8_t TxBuffer[MAX_MPDU];
|
||||
static uint32_t Timer_Silence(
|
||||
void *pArg)
|
||||
{
|
||||
uint32_t delta_time = 0;
|
||||
|
||||
delta_time = timer_milliseconds(TIMER_SILENCE);
|
||||
if (delta_time > 0xFFFF) {
|
||||
delta_time = 0xFFFF;
|
||||
}
|
||||
|
||||
return delta_time;
|
||||
}
|
||||
|
||||
static void Timer_Silence_Reset(
|
||||
void *pArg)
|
||||
{
|
||||
timer_reset(TIMER_SILENCE);
|
||||
}
|
||||
|
||||
/* functions used by the MS/TP state machine to put or get data */
|
||||
uint16_t MSTP_Put_Receive(
|
||||
volatile struct mstp_port_struct_t *mstp_port)
|
||||
{
|
||||
(void) mstp_port;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* for the MS/TP state machine to use for getting data to send */
|
||||
/* Return: amount of PDU data */
|
||||
uint16_t MSTP_Get_Send(
|
||||
volatile struct mstp_port_struct_t * mstp_port,
|
||||
unsigned timeout)
|
||||
{ /* milliseconds to wait for a packet */
|
||||
(void) mstp_port;
|
||||
(void) timeout;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t MSTP_Get_Reply(
|
||||
volatile struct mstp_port_struct_t * mstp_port,
|
||||
unsigned timeout)
|
||||
{ /* milliseconds to wait for a packet */
|
||||
(void) mstp_port;
|
||||
(void) timeout;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int network_init(
|
||||
const char *name,
|
||||
int protocol)
|
||||
{
|
||||
/* check to see if we are being run as root */
|
||||
if (getuid() != 0) {
|
||||
fprintf(stderr, "Requires root priveleges.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sockfd = socket(PF_PACKET, SOCK_RAW, htons(protocol));
|
||||
|
||||
if (sockfd == -1) {
|
||||
perror("Unable to create socket");
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
struct ifreq ifr;
|
||||
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
strncpy(ifr.ifr_name, name, strlen(name));
|
||||
|
||||
if (ioctl(sockfd, SIOCGIFINDEX, &ifr) == -1) {
|
||||
perror("Unable to get interface index");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_ll sll;
|
||||
|
||||
memset(&sll, 0, sizeof(sll));
|
||||
sll.sll_family = AF_PACKET;
|
||||
sll.sll_ifindex = ifr.ifr_ifindex;
|
||||
sll.sll_protocol = htons(protocol);
|
||||
|
||||
if (bind(sockfd, (struct sockaddr *) &sll, sizeof(sll)) == -1) {
|
||||
perror("Unable to bind socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
static void snap_received_packet(
|
||||
volatile struct mstp_port_struct_t *mstp_port,
|
||||
int sockfd)
|
||||
{
|
||||
uint16_t mtu_len = 0; /* number of octets of packet saved in file */
|
||||
unsigned i = 0; /* counter */
|
||||
static uint8_t mtu[1500] = { 0 };
|
||||
uint16_t max_data = 0;
|
||||
|
||||
mtu[0] = 0;
|
||||
mtu[1] = 0;
|
||||
mtu[2] = 0;
|
||||
mtu[3] = 0;
|
||||
mtu[4] = 0;
|
||||
mtu[5] = mstp_port->DestinationAddress;
|
||||
mtu[6] = 0;
|
||||
mtu[7] = 0;
|
||||
mtu[8] = 0;
|
||||
mtu[9] = 0;
|
||||
mtu[10] = 0;
|
||||
mtu[11] = mstp_port->SourceAddress;
|
||||
/* length - 12, 13 */
|
||||
mtu[14] = 0xaa; /* DSAP for SNAP */
|
||||
mtu[15] = 0xaa; /* SSAP for SNAP */
|
||||
mtu[16] = 0x03; /* Control Field for SNAP */
|
||||
mtu[17] = 0x00; /* Organization Code: Cimetrics */
|
||||
mtu[18] = 0x10; /* Organization Code: Cimetrics */
|
||||
mtu[19] = 0x90; /* Organization Code: Cimetrics */
|
||||
mtu[20] = 0x00; /* Protocol ID */
|
||||
mtu[21] = 0x01; /* Protocol ID */
|
||||
mtu[22] = 0x00; /* delta time */
|
||||
mtu[23] = 0x00; /* delta time */
|
||||
mtu[24] = 0x80; /* unknown byte */
|
||||
mtu[25] = mstp_port->FrameType;
|
||||
mtu[26] = mstp_port->DestinationAddress;
|
||||
mtu[27] = mstp_port->SourceAddress;
|
||||
mtu[28] = HI_BYTE(mstp_port->DataLength);
|
||||
mtu[29] = LO_BYTE(mstp_port->DataLength);
|
||||
mtu[30] = mstp_port->HeaderCRCActual;
|
||||
mtu_len = 31;
|
||||
if (mstp_port->DataLength) {
|
||||
max_data = min(mstp_port->InputBufferSize, mstp_port->DataLength);
|
||||
for (i = 0; i < max_data; i++) {
|
||||
mtu[31 + i] = mstp_port->InputBuffer[i];
|
||||
}
|
||||
mtu[31 + max_data] = mstp_port->DataCRCActualMSB;
|
||||
mtu[31 + max_data + 1] = mstp_port->DataCRCActualLSB;
|
||||
mtu_len += (max_data + 2);
|
||||
}
|
||||
/* Ethernet length is data only - not address or length bytes */
|
||||
encode_unsigned16(&mtu[12], mtu_len - 14);
|
||||
(void) write(sockfd, &mtu[0], mtu_len);
|
||||
}
|
||||
|
||||
|
||||
static void cleanup(
|
||||
void)
|
||||
{
|
||||
}
|
||||
|
||||
#if (!defined(_WIN32))
|
||||
static void sig_int(
|
||||
int signo)
|
||||
{
|
||||
(void) signo;
|
||||
|
||||
cleanup();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void signal_init(
|
||||
void)
|
||||
{
|
||||
signal(SIGINT, sig_int);
|
||||
signal(SIGHUP, sig_int);
|
||||
signal(SIGTERM, sig_int);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* simple test to packetize the data and print it */
|
||||
int main(
|
||||
int argc,
|
||||
char *argv[])
|
||||
{
|
||||
volatile struct mstp_port_struct_t *mstp_port;
|
||||
long my_baud = 38400;
|
||||
uint32_t packet_count = 0;
|
||||
int sockfd = -1;
|
||||
char *my_interface = "eth0";
|
||||
|
||||
/* mimic our pointer in the state machine */
|
||||
mstp_port = &MSTP_Port;
|
||||
if ((argc > 1) && (strcmp(argv[1], "--help") == 0)) {
|
||||
printf("mstsnap [serial] [baud] [network]\r\n"
|
||||
"Captures MS/TP packets from a serial interface\r\n"
|
||||
"and sends them to a network interface using SNAP \r\n"
|
||||
"protocol packets (mimics Cimetrics U+4 packet).\r\n" "\r\n"
|
||||
"Command line options:\r\n" "[serial] - serial interface.\r\n"
|
||||
" defaults to /dev/ttyUSB0.\r\n"
|
||||
"[baud] - baud rate. 9600, 19200, 38400, 57600, 115200\r\n"
|
||||
" defaults to 38400.\r\n" "[network] - network interface.\r\n"
|
||||
" defaults to eth0.\r\n" "");
|
||||
return 0;
|
||||
}
|
||||
/* initialize our interface */
|
||||
if (argc > 1) {
|
||||
RS485_Set_Interface(argv[1]);
|
||||
}
|
||||
if (argc > 2) {
|
||||
my_baud = strtol(argv[2], NULL, 0);
|
||||
}
|
||||
if (argc > 3) {
|
||||
my_interface = argv[3];
|
||||
}
|
||||
sockfd = network_init(my_interface, ETH_P_ALL);
|
||||
if (sockfd == -1) {
|
||||
return 1;
|
||||
}
|
||||
RS485_Set_Baud_Rate(my_baud);
|
||||
RS485_Initialize();
|
||||
MSTP_Port.InputBuffer = &RxBuffer[0];
|
||||
MSTP_Port.InputBufferSize = sizeof(RxBuffer);
|
||||
MSTP_Port.OutputBuffer = &TxBuffer[0];
|
||||
MSTP_Port.OutputBufferSize = sizeof(TxBuffer);
|
||||
MSTP_Port.This_Station = 127;
|
||||
MSTP_Port.Nmax_info_frames = 1;
|
||||
MSTP_Port.Nmax_master = 127;
|
||||
MSTP_Port.SilenceTimer = Timer_Silence;
|
||||
MSTP_Port.SilenceTimerReset = Timer_Silence_Reset;
|
||||
MSTP_Init(mstp_port);
|
||||
fprintf(stdout, "mstpcap: Using %s for capture at %ld bps.\n",
|
||||
RS485_Interface(), (long) RS485_Get_Baud_Rate());
|
||||
atexit(cleanup);
|
||||
#if defined(_WIN32)
|
||||
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
|
||||
SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlCHandler, TRUE);
|
||||
#else
|
||||
signal_init();
|
||||
#endif
|
||||
/* run forever */
|
||||
for (;;) {
|
||||
RS485_Check_UART_Data(mstp_port);
|
||||
MSTP_Receive_Frame_FSM(mstp_port);
|
||||
/* process the data portion of the frame */
|
||||
if (mstp_port->ReceivedValidFrame) {
|
||||
mstp_port->ReceivedValidFrame = false;
|
||||
snap_received_packet(mstp_port, sockfd);
|
||||
packet_count++;
|
||||
} else if (mstp_port->ReceivedInvalidFrame) {
|
||||
mstp_port->ReceivedInvalidFrame = false;
|
||||
fprintf(stderr, "ReceivedInvalidFrame\n");
|
||||
snap_received_packet(mstp_port, sockfd);
|
||||
packet_count++;
|
||||
}
|
||||
if (!(packet_count % 100)) {
|
||||
fprintf(stdout, "\r%hu packets", packet_count);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#Makefile to build BACnet Application for the Linux Port
|
||||
|
||||
# Compiler to use
|
||||
CC = gcc
|
||||
# Executable file name
|
||||
TARGET = mstpsnap
|
||||
|
||||
# Configure the BACnet Datalink Layer
|
||||
BACDL_DEFINE = -DBACDL_MSTP
|
||||
BACNET_DEFINES = -DPRINT_ENABLED=1 -DBACAPP_ALL -DBACFILE
|
||||
DEFINES = $(BACNET_DEFINES) $(BACDL_DEFINE)
|
||||
|
||||
# Directories
|
||||
BACNET_PORT = linux
|
||||
BACNET_PORT_DIR = .
|
||||
BACNET_SOURCE_DIR = ../../src
|
||||
BACNET_INCLUDE = ../../include
|
||||
|
||||
# Compiler Setup
|
||||
INCLUDES = -I$(BACNET_INCLUDE) -I$(BACNET_PORT_DIR)
|
||||
ifeq (${BACNET_PORT},linux)
|
||||
PFLAGS = -pthread
|
||||
TARGET_BIN = ${TARGET}
|
||||
LIBRARIES=-lc,-lgcc,-lrt,-lm
|
||||
endif
|
||||
ifeq (${BACNET_PORT},win32)
|
||||
TARGET_BIN = ${TARGET}.exe
|
||||
LIBRARIES=-lws2_32,-lgcc,-lm,-liphlpapi
|
||||
endif
|
||||
#DEBUGGING = -g
|
||||
#OPTIMIZATION = -O0
|
||||
OPTIMIZATION = -Os
|
||||
CFLAGS = -Wall $(DEBUGGING) $(OPTIMIZATION) $(INCLUDES) $(DEFINES) -fdata-sections -ffunction-sections
|
||||
LFLAGS = -Wl,-Map=$(TARGET).map,$(LIBRARIES),--gc-sections
|
||||
|
||||
SRCS = mstpsnap.c \
|
||||
${BACNET_PORT_DIR}/rs485.c \
|
||||
${BACNET_PORT_DIR}/timer.c \
|
||||
${BACNET_SOURCE_DIR}/bacint.c \
|
||||
${BACNET_SOURCE_DIR}/mstp.c \
|
||||
${BACNET_SOURCE_DIR}/fifo.c \
|
||||
${BACNET_SOURCE_DIR}/mstptext.c \
|
||||
${BACNET_SOURCE_DIR}/debug.c \
|
||||
${BACNET_SOURCE_DIR}/indtext.c \
|
||||
${BACNET_SOURCE_DIR}/crc.c
|
||||
|
||||
OBJS = ${SRCS:.c=.o}
|
||||
|
||||
all: ${TARGET_BIN}
|
||||
size ${TARGET_BIN}
|
||||
|
||||
${TARGET_BIN}: ${OBJS}
|
||||
${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@
|
||||
|
||||
.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
|
||||
@@ -0,0 +1,110 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2005 Steve Karg <skarg@users.sourceforge.net>
|
||||
*
|
||||
* 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 NET_H
|
||||
#define NET_H
|
||||
|
||||
/* common unix sockets headers needed */
|
||||
#include <sys/types.h> /* basic system data types */
|
||||
#include <sys/time.h> /* timeval{} for select() */
|
||||
#include <time.h> /* timespec{} for pselect() */
|
||||
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
|
||||
#include <arpa/inet.h> /* inet(3) functions */
|
||||
#include <fcntl.h> /* for nonblocking */
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h> /* for S_xxx file mode constants */
|
||||
#include <sys/uio.h> /* for iovec{} and readv/writev */
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/un.h> /* for Unix domain sockets */
|
||||
|
||||
#ifdef HAVE_SYS_SELECT_H
|
||||
#include <sys/select.h> /* for convenience */
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_POLL_H
|
||||
#include <poll.h> /* for convenience */
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_STRINGS_H
|
||||
#include <strings.h> /* for convenience */
|
||||
#endif
|
||||
|
||||
/* Three headers are normally needed for socket/file ioctl's:
|
||||
* <sys/ioctl.h>, <sys/filio.h>, and <sys/sockio.h>.
|
||||
*/
|
||||
#ifdef HAVE_SYS_IOCTL_H
|
||||
#include <sys/ioctl.h>
|
||||
#endif
|
||||
#ifdef HAVE_SYS_FILIO_H
|
||||
#include <sys/filio.h>
|
||||
#endif
|
||||
#ifdef HAVE_SYS_SOCKIO_H
|
||||
#include <sys/sockio.h>
|
||||
#endif
|
||||
|
||||
#include <pthread.h>
|
||||
#include <semaphore.h>
|
||||
|
||||
#define ENUMS
|
||||
#include <sys/socket.h>
|
||||
#ifndef __CYGWIN__
|
||||
#include <net/route.h>
|
||||
#endif
|
||||
#include <net/if.h>
|
||||
#ifndef __CYGWIN__
|
||||
#include <net/if_arp.h>
|
||||
#endif
|
||||
#include <features.h> /* for the glibc version number */
|
||||
#if __GLIBC__ >= 2 && __GLIBC_MINOR >= 1
|
||||
#include <netpacket/packet.h>
|
||||
#include <net/ethernet.h> /* the L2 protocols */
|
||||
#else
|
||||
#include <asm/types.h>
|
||||
#ifndef __CYGWIN__
|
||||
#include <linux/if_packet.h>
|
||||
#include <linux/if_arcnet.h>
|
||||
#include <linux/if_ether.h>
|
||||
#endif
|
||||
#endif
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <netdb.h>
|
||||
|
||||
/** @file linux/net.h Includes Linux network headers. */
|
||||
|
||||
/* Local helper functions for this port */
|
||||
extern int bip_get_local_netmask(
|
||||
struct in_addr *netmask);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
This is a port to Linux for testing.
|
||||
The unit tests can be run via the test.sh script.
|
||||
@@ -0,0 +1,748 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2007 Steve Karg <skarg@users.sourceforge.net>
|
||||
Updated by Nikola Jelic 2011 <nikola.jelic@euroicc.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307
|
||||
USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
|
||||
/** @file linux/rs485.c Provides Linux-specific functions for RS-485 serial. */
|
||||
|
||||
/* The module handles sending data out the RS-485 port */
|
||||
/* and handles receiving data from the RS-485 port. */
|
||||
/* Customize this file for your specific hardware */
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Linux includes */
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <sched.h>
|
||||
#include <linux/serial.h> /* for struct serial_struct */
|
||||
#include <math.h> /* for calculation of custom divisor */
|
||||
#include <sys/ioctl.h>
|
||||
/* for scandir */
|
||||
#include <dirent.h>
|
||||
/* for basename */
|
||||
#include <libgen.h>
|
||||
|
||||
/* Local includes */
|
||||
#include "mstp.h"
|
||||
#include "rs485.h"
|
||||
#include "fifo.h"
|
||||
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "dlmstp_linux.h"
|
||||
|
||||
/* Posix serial programming reference:
|
||||
http://www.easysw.com/~mike/serial/serial.html */
|
||||
|
||||
/* Use ionice wrapper to improve serial performance:
|
||||
$ sudo ionice -c 1 -n 0 ./bin/bacserv 12345
|
||||
*/
|
||||
|
||||
/* handle returned from open() */
|
||||
static int RS485_Handle = -1;
|
||||
/* baudrate settings are defined in <asm/termbits.h>, which is
|
||||
included by <termios.h> */
|
||||
static unsigned int RS485_Baud = B38400;
|
||||
/* serial port name, /dev/ttyS0,
|
||||
/dev/ttyUSB0 for USB->RS485 from B&B Electronics USOPTL4 */
|
||||
static char *RS485_Port_Name = "/dev/ttyUSB0";
|
||||
/* some terminal I/O have RS-485 specific functionality */
|
||||
#ifndef RS485MOD
|
||||
#define RS485MOD 0
|
||||
#endif
|
||||
/* serial I/O settings */
|
||||
static struct termios RS485_oldtio;
|
||||
/* for setting custom divisor */
|
||||
static struct serial_struct RS485_oldserial;
|
||||
/* indicator of special baud rate */
|
||||
static bool RS485_SpecBaud = false;
|
||||
|
||||
/* Ring buffer for incoming bytes, in order to speed up the receiving. */
|
||||
static FIFO_BUFFER Rx_FIFO;
|
||||
/* buffer size needs to be a power of 2 */
|
||||
static uint8_t Rx_Buffer[4096];
|
||||
|
||||
#define _POSIX_SOURCE 1 /* POSIX compliant source */
|
||||
|
||||
/*********************************************************************
|
||||
* DESCRIPTION: Configures the interface name
|
||||
* RETURN: none
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*********************************************************************/
|
||||
void RS485_Set_Interface(
|
||||
char *ifname)
|
||||
{
|
||||
/* note: expects a constant char, or char from the heap */
|
||||
if (ifname) {
|
||||
RS485_Port_Name = ifname;
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************
|
||||
* DESCRIPTION: Returns the interface name
|
||||
* RETURN: none
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*********************************************************************/
|
||||
const char *RS485_Interface(
|
||||
void)
|
||||
{
|
||||
return RS485_Port_Name;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Returns the baud rate that we are currently running at
|
||||
* RETURN: none
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
uint32_t RS485_Get_Baud_Rate(
|
||||
void)
|
||||
{
|
||||
uint32_t baud = 0;
|
||||
|
||||
switch (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:
|
||||
if (!RS485_SpecBaud) {
|
||||
/* Linux asks for custom divisor
|
||||
only when baud is set on 38400 */
|
||||
baud = 38400;
|
||||
} else {
|
||||
baud = 76800;
|
||||
}
|
||||
break;
|
||||
case B57600:
|
||||
baud = 57600;
|
||||
break;
|
||||
case B115200:
|
||||
baud = 115200;
|
||||
break;
|
||||
case B230400:
|
||||
baud = 230400;
|
||||
break;
|
||||
default:
|
||||
baud = 9600;
|
||||
}
|
||||
|
||||
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
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
bool RS485_Set_Baud_Rate(
|
||||
uint32_t baud)
|
||||
{
|
||||
bool valid = true;
|
||||
|
||||
RS485_SpecBaud = false;
|
||||
switch (baud) {
|
||||
case 0:
|
||||
RS485_Baud = B0;
|
||||
break;
|
||||
case 50:
|
||||
RS485_Baud = B50;
|
||||
break;
|
||||
case 75:
|
||||
RS485_Baud = B75;
|
||||
break;
|
||||
case 110:
|
||||
RS485_Baud = B110;
|
||||
break;
|
||||
case 134:
|
||||
RS485_Baud = B134;
|
||||
break;
|
||||
case 150:
|
||||
RS485_Baud = B150;
|
||||
break;
|
||||
case 200:
|
||||
RS485_Baud = B200;
|
||||
break;
|
||||
case 300:
|
||||
RS485_Baud = B300;
|
||||
break;
|
||||
case 600:
|
||||
RS485_Baud = B600;
|
||||
break;
|
||||
case 1200:
|
||||
RS485_Baud = B1200;
|
||||
break;
|
||||
case 1800:
|
||||
RS485_Baud = B1800;
|
||||
break;
|
||||
case 2400:
|
||||
RS485_Baud = B2400;
|
||||
break;
|
||||
case 4800:
|
||||
RS485_Baud = B4800;
|
||||
break;
|
||||
case 9600:
|
||||
RS485_Baud = B9600;
|
||||
break;
|
||||
case 19200:
|
||||
RS485_Baud = B19200;
|
||||
break;
|
||||
case 38400:
|
||||
RS485_Baud = B38400;
|
||||
break;
|
||||
case 57600:
|
||||
RS485_Baud = B57600;
|
||||
break;
|
||||
case 76800:
|
||||
RS485_Baud = B38400;
|
||||
RS485_SpecBaud = true;
|
||||
break;
|
||||
case 115200:
|
||||
RS485_Baud = B115200;
|
||||
break;
|
||||
case 230400:
|
||||
RS485_Baud = B230400;
|
||||
break;
|
||||
default:
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
/* FIXME: store the baud rate */
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Transmit a frame on the wire
|
||||
* RETURN: none
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
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) */
|
||||
uint32_t turnaround_time = Tturnaround * 1000;
|
||||
uint32_t baud;
|
||||
ssize_t written = 0;
|
||||
int greska;
|
||||
SHARED_MSTP_DATA *poSharedData = NULL;
|
||||
|
||||
if (mstp_port) {
|
||||
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;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Get a byte of receive data
|
||||
* RETURN: none
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
void RS485_Check_UART_Data(
|
||||
volatile struct mstp_port_struct_t *mstp_port)
|
||||
{
|
||||
fd_set input;
|
||||
struct timeval waiter;
|
||||
uint8_t buf[2048];
|
||||
int 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 - just poll */
|
||||
waiter.tv_sec = 0;
|
||||
waiter.tv_usec = 0;
|
||||
} 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 - just poll */
|
||||
waiter.tv_sec = 0;
|
||||
waiter.tv_usec = 0;
|
||||
} 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(
|
||||
void)
|
||||
{
|
||||
/* restore the old port settings */
|
||||
tcsetattr(RS485_Handle, TCSANOW, &RS485_oldtio);
|
||||
ioctl(RS485_Handle, TIOCSSERIAL, &RS485_oldserial);
|
||||
close(RS485_Handle);
|
||||
}
|
||||
|
||||
|
||||
void RS485_Initialize(
|
||||
void)
|
||||
{
|
||||
struct termios newtio;
|
||||
struct serial_struct newserial;
|
||||
float baud_error = 0.0;
|
||||
|
||||
printf("RS485: Initializing %s", RS485_Port_Name);
|
||||
/*
|
||||
Open device for reading and writing.
|
||||
Blocking mode - more CPU effecient
|
||||
*/
|
||||
RS485_Handle = open(RS485_Port_Name, O_RDWR | O_NOCTTY /*| O_NDELAY */ );
|
||||
if (RS485_Handle < 0) {
|
||||
perror(RS485_Port_Name);
|
||||
exit(-1);
|
||||
}
|
||||
#if 0
|
||||
/* non blocking for the read */
|
||||
fcntl(RS485_Handle, F_SETFL, FNDELAY);
|
||||
#else
|
||||
/* efficient blocking for the read */
|
||||
fcntl(RS485_Handle, F_SETFL, 0);
|
||||
#endif
|
||||
/* save current serial port settings */
|
||||
tcgetattr(RS485_Handle, &RS485_oldtio);
|
||||
/* we read the old serial setup */
|
||||
ioctl(RS485_Handle, TIOCGSERIAL, &RS485_oldserial);
|
||||
/* we need a copy of existing settings */
|
||||
memcpy(&newserial, &RS485_oldserial, sizeof(struct serial_struct));
|
||||
/* 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)
|
||||
CS8 : 8n1 (8bit,no parity,1 stopbit)
|
||||
CLOCAL : local connection, no modem contol
|
||||
CREAD : enable receiving characters
|
||||
*/
|
||||
newtio.c_cflag = RS485_Baud | CS8 | CLOCAL | CREAD | RS485MOD;
|
||||
/* 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(RS485_Handle, TCSAFLUSH, &newtio);
|
||||
if (RS485_SpecBaud) {
|
||||
/* 76800, custom divisor must be set */
|
||||
newserial.flags |= ASYNC_SPD_CUST;
|
||||
newserial.custom_divisor =
|
||||
round(((float) newserial.baud_base) / 76800);
|
||||
/* we must check that we calculated some sane value;
|
||||
small baud bases yield bad custom divisor values */
|
||||
baud_error =
|
||||
fabs(1 -
|
||||
((float) newserial.baud_base) /
|
||||
((float) newserial.custom_divisor) / 76800);
|
||||
if ((newserial.custom_divisor == 0) || (baud_error > 0.02)) {
|
||||
/* bad divisor */
|
||||
fprintf(stderr, "bad custom divisor %d, base baud %d\n",
|
||||
newserial.custom_divisor, newserial.baud_base);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
/* if all goes well, set new divisor */
|
||||
ioctl(RS485_Handle, TIOCSSERIAL, &newserial);
|
||||
}
|
||||
printf(" at Baud Rate %u", RS485_Get_Baud_Rate());
|
||||
/* destructor */
|
||||
atexit(RS485_Cleanup);
|
||||
/* flush any data waiting */
|
||||
usleep(200000);
|
||||
tcflush(RS485_Handle, TCIOFLUSH);
|
||||
/* ringbuffer */
|
||||
FIFO_Init(&Rx_FIFO, Rx_Buffer, sizeof(Rx_Buffer));
|
||||
printf("=success!\n");
|
||||
}
|
||||
|
||||
/* Print in a format for Wireshark ExtCap */
|
||||
void RS485_Print_Ports(void)
|
||||
{
|
||||
int n;
|
||||
struct dirent **namelist;
|
||||
const char* sysdir = "/sys/class/tty/";
|
||||
struct stat st;
|
||||
char buffer[1024];
|
||||
char device_dir[1024];
|
||||
char *driver_name = NULL;
|
||||
int fd = 0;
|
||||
bool valid_port = false;
|
||||
struct serial_struct serinfo;
|
||||
|
||||
// Scan through /sys/class/tty - it contains all tty-devices in the system
|
||||
n = scandir(sysdir, &namelist, NULL, NULL);
|
||||
if (n < 0) {
|
||||
perror("RS485: scandir");
|
||||
} else {
|
||||
while (n--) {
|
||||
if (strcmp(namelist[n]->d_name,"..") &&
|
||||
strcmp(namelist[n]->d_name,".")) {
|
||||
snprintf(device_dir, sizeof(device_dir),
|
||||
"%s%s/device",
|
||||
sysdir, namelist[n]->d_name);
|
||||
// Stat the devicedir and handle it if it is a symlink
|
||||
if (lstat(device_dir, &st)==0 && S_ISLNK(st.st_mode)) {
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
snprintf(device_dir, sizeof(device_dir),
|
||||
"%s%s/device/driver",
|
||||
sysdir, namelist[n]->d_name);
|
||||
if (readlink(device_dir, buffer, sizeof(buffer)) > 0) {
|
||||
valid_port = false;
|
||||
driver_name=basename(buffer);
|
||||
if (strcmp(driver_name,"serial8250") == 0) {
|
||||
// serial8250-devices must be probed
|
||||
snprintf(device_dir, sizeof(device_dir),
|
||||
"/dev/%s", namelist[n]->d_name);
|
||||
fd = open(device_dir,
|
||||
O_RDWR | O_NONBLOCK | O_NOCTTY);
|
||||
if (fd >= 0) {
|
||||
// Get serial_info
|
||||
if (ioctl(fd, TIOCGSERIAL, &serinfo)==0) {
|
||||
// If device type is not PORT_UNKNOWN
|
||||
// we accept the port
|
||||
if (serinfo.type != PORT_UNKNOWN) {
|
||||
valid_port = true;
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
} else {
|
||||
valid_port = true;
|
||||
}
|
||||
if (valid_port) {
|
||||
// print full absolute file path
|
||||
printf("interface {value=/dev/%s}"
|
||||
"{display=MS/TP Capture on /dev/%s}\n",
|
||||
namelist[n]->d_name, namelist[n]->d_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(namelist[n]);
|
||||
}
|
||||
free(namelist);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TEST_RS485
|
||||
#include <string.h>
|
||||
int main(
|
||||
int argc,
|
||||
char *argv[])
|
||||
{
|
||||
volatile struct mstp_port_struct_t mstp_port = {0};
|
||||
uint8_t token_buf[8] = {0x55, 0xFF, 0x00, 0x7E, 0x07, 0x00, 0x00, 0xFD};
|
||||
uint8_t pfm_buf[8] = {0x55, 0xFF, 0x01, 0x67, 0x07, 0x00, 0x00, 0x3E};
|
||||
long baud = 38400;
|
||||
bool write_token = false;
|
||||
bool write_pfm = false;
|
||||
|
||||
/* argv has the "/dev/ttyS0" or some other device */
|
||||
if (argc > 1) {
|
||||
RS485_Set_Interface(argv[1]);
|
||||
}
|
||||
if (argc > 2) {
|
||||
baud = strtol(argv[2], NULL, 0);
|
||||
}
|
||||
if (argc > 3) {
|
||||
if (strcmp("token", argv[3]) == 0) {
|
||||
write_token = true;
|
||||
}
|
||||
if (strcmp("pfm", argv[3]) == 0) {
|
||||
write_pfm = true;
|
||||
}
|
||||
}
|
||||
RS485_Set_Baud_Rate(baud);
|
||||
RS485_Initialize();
|
||||
for (;;) {
|
||||
if (write_token) {
|
||||
RS485_Send_Frame(NULL,token_buf, sizeof(token_buf));
|
||||
usleep(25000);
|
||||
} else if (write_pfm) {
|
||||
RS485_Send_Frame(NULL,pfm_buf, sizeof(pfm_buf));
|
||||
usleep(100000);
|
||||
} else {
|
||||
RS485_Check_UART_Data(&mstp_port);
|
||||
if (mstp_port.DataAvailable) {
|
||||
fprintf(stderr, "%02X ", mstp_port.DataRegister);
|
||||
mstp_port.DataAvailable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,76 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2004 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307
|
||||
USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
|
||||
#ifndef RS485_H
|
||||
#define RS485_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "mstp.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
void RS485_Set_Interface(
|
||||
char *ifname);
|
||||
const char *RS485_Interface(
|
||||
void);
|
||||
|
||||
void RS485_Initialize(
|
||||
void);
|
||||
|
||||
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) */
|
||||
|
||||
void RS485_Check_UART_Data(
|
||||
volatile struct mstp_port_struct_t *mstp_port); /* port specific data */
|
||||
uint32_t RS485_Get_Port_Baud_Rate(
|
||||
volatile struct mstp_port_struct_t *mstp_port);
|
||||
uint32_t RS485_Get_Baud_Rate(
|
||||
void);
|
||||
bool RS485_Set_Baud_Rate(
|
||||
uint32_t baud);
|
||||
|
||||
void RS485_Cleanup(
|
||||
void);
|
||||
void RS485_Print_Ports(
|
||||
void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif
|
||||
@@ -0,0 +1,36 @@
|
||||
#Makefile to build test case
|
||||
#CC = gcc
|
||||
TARGET = rs485
|
||||
|
||||
# Directories
|
||||
BACNET_SOURCE_DIR = ../../src
|
||||
BACNET_INCLUDE = ../../include
|
||||
|
||||
# -g for debugging with gdb
|
||||
DEFINES = -DBIG_ENDIAN=0 -DTEST_RS485 -DBACDL_TEST
|
||||
INCLUDES = -I. -I../../ -I$(BACNET_INCLUDE)
|
||||
CFLAGS = -Wall $(INCLUDES) $(DEFINES) -g
|
||||
LIBRARIES=-lc,-lgcc,-lrt,-lm
|
||||
LFLAGS = -Wl,-Map=$(TARGET).map,$(LIBRARIES),--gc-sections
|
||||
|
||||
SRCS = rs485.c \
|
||||
${BACNET_SOURCE_DIR}/fifo.c
|
||||
|
||||
OBJS = ${SRCS:.c=.o}
|
||||
|
||||
all: ${TARGET}
|
||||
|
||||
${TARGET}: ${OBJS}
|
||||
${CC} ${OBJS} ${LFLAGS} -o $@
|
||||
|
||||
.c.o:
|
||||
${CC} -c ${CFLAGS} $*.c -o $@
|
||||
|
||||
depend:
|
||||
rm -f .depend
|
||||
${CC} -MM ${CFLAGS} *.c >> .depend
|
||||
|
||||
clean:
|
||||
rm -rf core ${TARGET} $(OBJS) *.bak *.1 *.ini
|
||||
|
||||
include: .depend
|
||||
@@ -0,0 +1,367 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2007 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307
|
||||
USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
/* Linux includes */
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h> /* signal handling functions */
|
||||
|
||||
/* local includes */
|
||||
#include "bytes.h"
|
||||
#include "rs485.h"
|
||||
#include "crc.h"
|
||||
#include "mstp.h"
|
||||
#include "mstptext.h"
|
||||
|
||||
/** @file linux/rx_fsm.c Example app testing MS/TP Rx State Machine on Linux. */
|
||||
|
||||
#ifndef max
|
||||
#define max(a,b) (((a) (b)) ? (a) : (b))
|
||||
#define min(a,b) (((a) < (b)) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#ifndef LOCAL_PRINT
|
||||
#define LOCAL_PRINT 1
|
||||
#endif
|
||||
|
||||
/* local port data - shared with RS-485 */
|
||||
static volatile struct mstp_port_struct_t MSTP_Port;
|
||||
/* buffers needed by mstp port struct */
|
||||
static uint8_t RxBuffer[MAX_MPDU];
|
||||
static uint8_t TxBuffer[MAX_MPDU];
|
||||
static uint16_t SilenceTime;
|
||||
#define INCREMENT_AND_LIMIT_UINT16(x) {if (x < 0xFFFF) x++;}
|
||||
static uint16_t Timer_Silence(
|
||||
void)
|
||||
{
|
||||
return SilenceTime;
|
||||
}
|
||||
|
||||
static void Timer_Silence_Reset(
|
||||
void)
|
||||
{
|
||||
SilenceTime = 0;
|
||||
}
|
||||
|
||||
static void dlmstp_millisecond_timer(
|
||||
void)
|
||||
{
|
||||
INCREMENT_AND_LIMIT_UINT16(SilenceTime);
|
||||
}
|
||||
|
||||
void *milliseconds_task(
|
||||
void *pArg)
|
||||
{
|
||||
struct timespec timeOut, remains;
|
||||
|
||||
timeOut.tv_sec = 0;
|
||||
timeOut.tv_nsec = 10000000; /* 1 milliseconds */
|
||||
|
||||
for (;;) {
|
||||
nanosleep(&timeOut, &remains);
|
||||
dlmstp_millisecond_timer();
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* functions used by the MS/TP state machine to put or get data */
|
||||
uint16_t MSTP_Put_Receive(
|
||||
volatile struct mstp_port_struct_t * mstp_port)
|
||||
{
|
||||
(void) mstp_port;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* for the MS/TP state machine to use for getting data to send */
|
||||
/* Return: amount of PDU data */
|
||||
uint16_t MSTP_Get_Send(
|
||||
volatile struct mstp_port_struct_t * mstp_port,
|
||||
unsigned timeout)
|
||||
{ /* milliseconds to wait for a packet */
|
||||
(void) mstp_port;
|
||||
(void) timeout;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t MSTP_Get_Reply(
|
||||
volatile struct mstp_port_struct_t * mstp_port,
|
||||
unsigned timeout)
|
||||
{ /* milliseconds to wait for a packet */
|
||||
(void) mstp_port;
|
||||
(void) timeout;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* returns a delta timestamp */
|
||||
int timestamp_ms(
|
||||
void)
|
||||
{
|
||||
struct timeval tv;
|
||||
int delta_ticks = 0;
|
||||
long ticks = 0;
|
||||
static long last_ticks = 0;
|
||||
int rv = 0;
|
||||
|
||||
rv = gettimeofday(&tv, NULL);
|
||||
if (rv == -1)
|
||||
ticks = 0;
|
||||
else
|
||||
ticks = (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
|
||||
|
||||
delta_ticks = ticks - last_ticks;
|
||||
last_ticks = ticks;
|
||||
|
||||
return delta_ticks;
|
||||
}
|
||||
|
||||
static const char *Capture_Filename = "mstp.cap";
|
||||
static FILE *pFile = NULL; /* stream pointer */
|
||||
|
||||
/* write packet to file in libpcap format */
|
||||
static void write_global_header(
|
||||
void)
|
||||
{
|
||||
uint32_t magic_number = 0xa1b2c3d4; /* magic number */
|
||||
uint16_t version_major = 2; /* major version number */
|
||||
uint16_t version_minor = 4; /* minor version number */
|
||||
int32_t thiszone = 0; /* GMT to local correction */
|
||||
uint32_t sigfigs = 0; /* accuracy of timestamps */
|
||||
uint32_t snaplen = 65535; /* max length of captured packets, in octets */
|
||||
uint32_t network = 165; /* data link type - BACNET_MS_TP */
|
||||
|
||||
/* create a new file. */
|
||||
pFile = fopen(Capture_Filename, "wb");
|
||||
if (pFile) {
|
||||
fwrite(&magic_number, sizeof(magic_number), 1, pFile);
|
||||
fwrite(&version_major, sizeof(version_major), 1, pFile);
|
||||
fwrite(&version_minor, sizeof(version_minor), 1, pFile);
|
||||
fwrite(&thiszone, sizeof(thiszone), 1, pFile);
|
||||
fwrite(&sigfigs, sizeof(sigfigs), 1, pFile);
|
||||
fwrite(&snaplen, sizeof(snaplen), 1, pFile);
|
||||
fwrite(&network, sizeof(network), 1, pFile);
|
||||
} else {
|
||||
fprintf(stderr, "rx_fsm: failed to open %s: %s\n", Capture_Filename,
|
||||
strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static void write_received_packet(
|
||||
volatile struct mstp_port_struct_t *mstp_port)
|
||||
{
|
||||
uint32_t ts_sec; /* timestamp seconds */
|
||||
uint32_t ts_usec; /* timestamp microseconds */
|
||||
uint32_t incl_len; /* number of octets of packet saved in file */
|
||||
uint32_t orig_len; /* actual length of packet */
|
||||
uint8_t header[8]; /* MS/TP header */
|
||||
struct timeval tv;
|
||||
size_t max_data = 0;
|
||||
|
||||
if (pFile) {
|
||||
gettimeofday(&tv, NULL);
|
||||
ts_sec = tv.tv_sec;
|
||||
ts_usec = tv.tv_usec;
|
||||
fwrite(&ts_sec, sizeof(ts_sec), 1, pFile);
|
||||
fwrite(&ts_usec, sizeof(ts_usec), 1, pFile);
|
||||
if (mstp_port->DataLength) {
|
||||
max_data = min(mstp_port->InputBufferSize, mstp_port->DataLength);
|
||||
incl_len = orig_len = 8 + max_data + 2;
|
||||
} else {
|
||||
incl_len = orig_len = 8;
|
||||
}
|
||||
fwrite(&incl_len, sizeof(incl_len), 1, pFile);
|
||||
fwrite(&orig_len, sizeof(orig_len), 1, pFile);
|
||||
header[0] = 0x55;
|
||||
header[1] = 0xFF;
|
||||
header[2] = mstp_port->FrameType;
|
||||
header[3] = mstp_port->DestinationAddress;
|
||||
header[4] = mstp_port->SourceAddress;
|
||||
header[5] = HI_BYTE(mstp_port->DataLength);
|
||||
header[6] = LO_BYTE(mstp_port->DataLength);
|
||||
header[7] = mstp_port->HeaderCRCActual;
|
||||
fwrite(header, sizeof(header), 1, pFile);
|
||||
if (mstp_port->DataLength) {
|
||||
fwrite(mstp_port->InputBuffer, max_data, 1, pFile);
|
||||
fwrite((char *) &(mstp_port->DataCRCActualMSB), 1, 1, pFile);
|
||||
fwrite((char *) &(mstp_port->DataCRCActualLSB), 1, 1, pFile);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "rx_fsm: failed to open %s: %s\n", Capture_Filename,
|
||||
strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static void cleanup(
|
||||
void)
|
||||
{
|
||||
if (pFile) {
|
||||
fflush(pFile); /* stream pointer */
|
||||
fclose(pFile); /* stream pointer */
|
||||
}
|
||||
pFile = NULL;
|
||||
}
|
||||
|
||||
#if LOCAL_PRINT
|
||||
static void print_received_packet(
|
||||
volatile struct mstp_port_struct_t *mstp_port)
|
||||
{
|
||||
unsigned i = 0;
|
||||
int timestamp = 0;
|
||||
size_t max_data = 0;
|
||||
|
||||
timestamp = timestamp_ms();
|
||||
fprintf(stderr, "%03d ", timestamp);
|
||||
/* Preamble: two octet preamble: X`55', X`FF' */
|
||||
/* Frame Type: one octet */
|
||||
/* Destination Address: one octet address */
|
||||
/* Source Address: one octet address */
|
||||
/* Length: two octets, most significant octet first, of the Data field */
|
||||
/* Header CRC: one octet */
|
||||
/* Data: (present only if Length is non-zero) */
|
||||
/* Data CRC: (present only if Length is non-zero) two octets, */
|
||||
/* least significant octet first */
|
||||
/* (pad): (optional) at most one octet of padding: X'FF' */
|
||||
fprintf(stderr, "55 FF %02X %02X %02X %02X %02X %02X ",
|
||||
mstp_port->FrameType, mstp_port->DestinationAddress,
|
||||
mstp_port->SourceAddress, HI_BYTE(mstp_port->DataLength),
|
||||
LO_BYTE(mstp_port->DataLength), mstp_port->HeaderCRCActual);
|
||||
if (mstp_port->DataLength) {
|
||||
max_data = min(mstp_port->InputBufferSize, mstp_port->DataLength);
|
||||
for (i = 0; i < max_data; i++) {
|
||||
fprintf(stderr, "%02X ", mstp_port->InputBuffer[i]);
|
||||
}
|
||||
fprintf(stderr, "%02X %02X ", mstp_port->DataCRCActualMSB,
|
||||
mstp_port->DataCRCActualLSB);
|
||||
}
|
||||
fprintf(stderr, "%s", mstptext_frame_type(mstp_port->FrameType));
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
static void sig_int(
|
||||
int signo)
|
||||
{
|
||||
(void) signo;
|
||||
|
||||
cleanup();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void signal_init(
|
||||
void)
|
||||
{
|
||||
signal(SIGINT, sig_int);
|
||||
signal(SIGHUP, sig_int);
|
||||
signal(SIGTERM, sig_int);
|
||||
}
|
||||
|
||||
/* simple test to packetize the data and print it */
|
||||
int main(
|
||||
int argc,
|
||||
char *argv[])
|
||||
{
|
||||
volatile struct mstp_port_struct_t *mstp_port;
|
||||
int rc = 0;
|
||||
pthread_t hThread;
|
||||
int my_mac = 127;
|
||||
long my_baud = 38400;
|
||||
|
||||
/* mimic our pointer in the state machine */
|
||||
mstp_port = &MSTP_Port;
|
||||
/* initialize our interface */
|
||||
if (argc > 1) {
|
||||
RS485_Set_Interface(argv[1]);
|
||||
}
|
||||
if (argc > 2) {
|
||||
my_baud = strtol(argv[2], NULL, 0);
|
||||
}
|
||||
if (argc > 3) {
|
||||
my_mac = strtol(argv[3], NULL, 0);
|
||||
if (my_mac > 127)
|
||||
my_mac = 127;
|
||||
}
|
||||
RS485_Set_Baud_Rate(my_baud);
|
||||
RS485_Initialize();
|
||||
MSTP_Port.InputBuffer = &RxBuffer[0];
|
||||
MSTP_Port.InputBufferSize = sizeof(RxBuffer);
|
||||
MSTP_Port.OutputBuffer = &TxBuffer[0];
|
||||
MSTP_Port.OutputBufferSize = sizeof(TxBuffer);
|
||||
MSTP_Port.This_Station = my_mac;
|
||||
MSTP_Port.Nmax_info_frames = 1;
|
||||
MSTP_Port.Nmax_master = 127;
|
||||
MSTP_Port.SilenceTimer = Timer_Silence;
|
||||
MSTP_Port.SilenceTimerReset = Timer_Silence_Reset;
|
||||
MSTP_Init(mstp_port);
|
||||
mstp_port->Lurking = true;
|
||||
/* start our MilliSec task */
|
||||
rc = pthread_create(&hThread, NULL, milliseconds_task, NULL);
|
||||
atexit(cleanup);
|
||||
write_global_header();
|
||||
fflush(pFile); /* stream pointer */
|
||||
/* run forever */
|
||||
for (;;) {
|
||||
RS485_Check_UART_Data(mstp_port);
|
||||
MSTP_Receive_Frame_FSM(mstp_port);
|
||||
/* process the data portion of the frame */
|
||||
if (mstp_port->ReceivedValidFrame) {
|
||||
mstp_port->ReceivedValidFrame = false;
|
||||
#if LOCAL_PRINT
|
||||
print_received_packet(mstp_port);
|
||||
#endif
|
||||
write_received_packet(mstp_port);
|
||||
fflush(pFile); /* stream pointer */
|
||||
} else if (mstp_port->ReceivedInvalidFrame) {
|
||||
mstp_port->ReceivedInvalidFrame = false;
|
||||
fprintf(stderr, "ReceivedInvalidFrame\n");
|
||||
#if LOCAL_PRINT
|
||||
print_received_packet(mstp_port);
|
||||
#endif
|
||||
write_received_packet(mstp_port);
|
||||
fflush(pFile); /* stream pointer */
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#Makefile to build test case
|
||||
CC = gcc
|
||||
SRCDIR = ../../src
|
||||
INCDIR = ../../include
|
||||
# -g for debugging with gdb
|
||||
DEFINES = -DBIG_ENDIAN=0 -DBACDL_MSTP=1
|
||||
INCLUDES = -I. -I$(INCDIR)
|
||||
CFLAGS = -Wall $(INCLUDES) $(DEFINES) -g
|
||||
|
||||
SRCS = rs485.c \
|
||||
rx_fsm.c \
|
||||
$(SRCDIR)/mstp.c \
|
||||
$(SRCDIR)/mstptext.c \
|
||||
$(SRCDIR)/indtext.c \
|
||||
$(SRCDIR)/crc.c
|
||||
|
||||
OBJS = ${SRCS:.c=.o}
|
||||
|
||||
TARGET = rx_fsm
|
||||
|
||||
all: ${TARGET}
|
||||
|
||||
${TARGET}: ${OBJS}
|
||||
${CC} -pthread -o $@ ${OBJS}
|
||||
|
||||
.c.o:
|
||||
${CC} -c ${CFLAGS} $*.c -o $@
|
||||
|
||||
depend:
|
||||
rm -f .depend
|
||||
${CC} -MM ${CFLAGS} *.c >> .depend
|
||||
|
||||
clean:
|
||||
rm -rf core ${TARGET} $(OBJS) *.bak *.1 *.ini
|
||||
|
||||
include: .depend
|
||||
@@ -0,0 +1,140 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2009 Steve Karg <skarg@users.sourceforge.net>
|
||||
*
|
||||
* 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 <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include "timer.h"
|
||||
|
||||
/** @file linux/timer.c Provides Linux-specific time and timer functions. */
|
||||
|
||||
/* counter for the various timers */
|
||||
static volatile uint32_t Millisecond_Counter[MAX_MILLISECOND_TIMERS];
|
||||
|
||||
/* start time for the clock */
|
||||
static struct timespec start;
|
||||
/* The timeGetTime function retrieves the system time, in milliseconds.
|
||||
The system time is the time elapsed since Windows was started. */
|
||||
uint32_t timeGetTime(
|
||||
void)
|
||||
{
|
||||
struct timespec now;
|
||||
uint32_t ticks;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
|
||||
ticks =
|
||||
(now.tv_sec - start.tv_sec) * 1000 + (now.tv_nsec -
|
||||
start.tv_nsec) / 1000000;
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
* Description: returns the current millisecond count
|
||||
* Returns: none
|
||||
* Notes: none
|
||||
*************************************************************************/
|
||||
uint32_t timer_milliseconds(
|
||||
unsigned index)
|
||||
{
|
||||
uint32_t now = timeGetTime();
|
||||
uint32_t delta_time = 0;
|
||||
|
||||
if (index < MAX_MILLISECOND_TIMERS) {
|
||||
if (Millisecond_Counter[index] <= now) {
|
||||
delta_time = now - Millisecond_Counter[index];
|
||||
} else {
|
||||
delta_time = (UINT32_MAX - Millisecond_Counter[index]) + now + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return delta_time;
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
* Description: compares the current time count with a value
|
||||
* Returns: true if the time has elapsed
|
||||
* Notes: none
|
||||
*************************************************************************/
|
||||
bool timer_elapsed_milliseconds(
|
||||
unsigned index,
|
||||
uint32_t value)
|
||||
{
|
||||
return (timer_milliseconds(index) >= value);
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
* Description: compares the current time count with a value
|
||||
* Returns: true if the time has elapsed
|
||||
* Notes: none
|
||||
*************************************************************************/
|
||||
bool timer_elapsed_seconds(
|
||||
unsigned index,
|
||||
uint32_t seconds)
|
||||
{
|
||||
return ((timer_milliseconds(index) / 1000) >= seconds);
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
* Description: compares the current time count with a value
|
||||
* Returns: true if the time has elapsed
|
||||
* Notes: none
|
||||
*************************************************************************/
|
||||
bool timer_elapsed_minutes(
|
||||
unsigned index,
|
||||
uint32_t minutes)
|
||||
{
|
||||
return ((timer_milliseconds(index) / (1000 * 60)) >= minutes);
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
* Description: Sets the timer counter to zero.
|
||||
* Returns: none
|
||||
* Notes: none
|
||||
*************************************************************************/
|
||||
uint32_t timer_reset(
|
||||
unsigned index)
|
||||
{
|
||||
uint32_t timer_value = 0;
|
||||
|
||||
if (index < MAX_MILLISECOND_TIMERS) {
|
||||
timer_value = timer_milliseconds(index);
|
||||
Millisecond_Counter[index] = timeGetTime();
|
||||
}
|
||||
|
||||
return timer_value;
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
* Description: Initialization for timer
|
||||
* Returns: none
|
||||
* Notes: none
|
||||
*************************************************************************/
|
||||
void timer_init(
|
||||
void)
|
||||
{
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2009 Steve Karg <skarg@users.sourceforge.net>
|
||||
*
|
||||
* 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 TIMER_H
|
||||
#define TIMER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/time.h> /* for timeval */
|
||||
|
||||
/* Timer Module */
|
||||
#ifndef MAX_MILLISECOND_TIMERS
|
||||
#define TIMER_SILENCE 0
|
||||
#define MAX_MILLISECOND_TIMERS 1
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
uint32_t timeGetTime(
|
||||
void);
|
||||
|
||||
void timer_init(
|
||||
void);
|
||||
uint32_t timer_milliseconds(
|
||||
unsigned index);
|
||||
bool timer_elapsed_milliseconds(
|
||||
unsigned index,
|
||||
uint32_t value);
|
||||
bool timer_elapsed_seconds(
|
||||
unsigned index,
|
||||
uint32_t value);
|
||||
bool timer_elapsed_minutes(
|
||||
unsigned index,
|
||||
uint32_t seconds);
|
||||
uint32_t timer_milliseconds_set(
|
||||
unsigned index,
|
||||
uint32_t value);
|
||||
uint32_t timer_reset(
|
||||
unsigned index);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
#endif
|
||||
Reference in New Issue
Block a user