From a95b7d597e4c81ba7ad0ca5fb2709c80c2ec5c06 Mon Sep 17 00:00:00 2001 From: Greg Shue <32416235+gregshue@users.noreply.github.com> Date: Tue, 22 Sep 2020 07:24:38 -0700 Subject: [PATCH] Zephyr core bacnet server port; bacnet/device test (#123) Co-authored-by: Gregory Shue --- ports/zephyr/bip-init.c | 615 ++++++ ports/zephyr/bip6-init.c | 564 ++++++ ports/zephyr/datalink/bvlc.c | 64 + ports/zephyr/datetime-init.c | 108 ++ ports/zephyr/device.c | 1923 +++++++++++++++++++ ports/zephyr/main.c | 1 + ports/zephyr/mstimer-init.c | 45 + src/bacnet/bacdef.h | 8 +- src/bacnet/basic/bbmd/h_bbmd.c | 3 +- test/bacnet/device/CMakeLists.txt | 30 + test/bacnet/device/src/main.c | 93 + zephyr/CMakeLists.txt | 177 +- zephyr/Kconfig | 4 +- zephyr/samples/profiles/b-ss/CMakeLists.txt | 8 + zephyr/samples/profiles/b-ss/README.rst | 23 + zephyr/samples/profiles/b-ss/prj.conf | 83 + zephyr/samples/profiles/b-ss/sample.yaml | 7 + zephyr/samples/profiles/b-ss/src/main.c | 157 ++ zephyr/subsys/CMakeLists.txt | 4 + zephyr/subsys/Kconfig | 8 + zephyr/subsys/server/CMakeLists.txt | 11 + zephyr/subsys/server/Kconfig | 35 + zephyr/subsys/server/server.c | 242 +++ zephyr/tests/bacnet/device/CMakeLists.txt | 25 + zephyr/tests/bacnet/device/prj.conf | 11 + zephyr/tests/bacnet/device/testcase.yaml | 3 + 26 files changed, 4166 insertions(+), 86 deletions(-) create mode 100644 ports/zephyr/bip-init.c create mode 100644 ports/zephyr/bip6-init.c create mode 100644 ports/zephyr/datalink/bvlc.c create mode 100644 ports/zephyr/datetime-init.c create mode 100644 ports/zephyr/device.c create mode 100644 ports/zephyr/mstimer-init.c create mode 100644 test/bacnet/device/CMakeLists.txt create mode 100644 test/bacnet/device/src/main.c create mode 100644 zephyr/samples/profiles/b-ss/CMakeLists.txt create mode 100644 zephyr/samples/profiles/b-ss/README.rst create mode 100644 zephyr/samples/profiles/b-ss/prj.conf create mode 100644 zephyr/samples/profiles/b-ss/sample.yaml create mode 100644 zephyr/samples/profiles/b-ss/src/main.c create mode 100644 zephyr/subsys/CMakeLists.txt create mode 100644 zephyr/subsys/Kconfig create mode 100644 zephyr/subsys/server/CMakeLists.txt create mode 100644 zephyr/subsys/server/Kconfig create mode 100644 zephyr/subsys/server/server.c create mode 100644 zephyr/tests/bacnet/device/CMakeLists.txt create mode 100644 zephyr/tests/bacnet/device/prj.conf create mode 100644 zephyr/tests/bacnet/device/testcase.yaml diff --git a/ports/zephyr/bip-init.c b/ports/zephyr/bip-init.c new file mode 100644 index 00000000..46b35ff7 --- /dev/null +++ b/ports/zephyr/bip-init.c @@ -0,0 +1,615 @@ +/*####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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "bacnet/bacdcode.h" +#include "bacnet/bacint.h" +#include "bacnet/datalink/bip.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/bbmd/h_bbmd.h" + +/* Logging module registration is already done in ports/zephyr/main.c */ +#include +#include + +LOG_MODULE_DECLARE(bacnet, CONFIG_BACNETSTACK_LOG_LEVEL); + +#define THIS_FILE "bip-init.c" + +/* zephyr socket */ +static int BIP_Socket = -1; + +/* NOTE: we store address and port in network byte order + since BACnet/IP uses network byte order for all address byte arrays +*/ +/* port to use - stored here in network byte order */ +static uint16_t BIP_Port = htons(CONFIG_BACDL_BIP_PORT); +/* IP address - stored here in network byte order */ +static struct in_addr BIP_Address; +/* IP broadcast address - stored here in network byte order */ +static struct in_addr BIP_Broadcast_Addr; + +/* Used by inet_ntoa */ +#if CONFIG_BACNETSTACK_LOG_LEVEL +static char ipv4_addr_str[16]={0}; +#else +static char ipv4_addr_str[]=""; +#endif + +/** +* @brief Return a string representation of an IPv4 address +* @param a - IPv4 address +* @return Pointer to global string +*/ +char* inet_ntoa(struct in_addr *a) +{ + #if CONFIG_BACNETSTACK_LOG_LEVEL + + /* Avoid overwhelming the logging system */ + while(log_buffered_cnt()) + { + k_sleep(K_MSEC(1)); + } + + snprintf(ipv4_addr_str, sizeof(ipv4_addr_str), "%d.%d.%d.%d", + a->s4_addr[0],a->s4_addr[1],a->s4_addr[2],a->s4_addr[3]); + #endif + return &ipv4_addr_str[0]; +} + +/** + * @brief Print the IPv4 address with debug info + * @param str - debug info string + * @param addr - IPv4 address + */ +static void debug_print_ipv4(const char *str, const struct in_addr *addr, + const unsigned int port, const unsigned int count) +{ + LOG_DBG("%s %s:%hu (%u bytes)", log_strdup(str), log_strdup(inet_ntoa((struct in_addr*) &addr)), + ntohs(port), count); +} + + +/** + * @brief Set the BACnet IPv4 UDP port number + * @param port - IPv4 UDP port number - in host byte order + */ +void bip_set_port(uint16_t port) +{ + BIP_Port = htons(port); +} + +/** + * @brief Get the BACnet IPv4 UDP port number + * @return IPv4 UDP port number - in host byte order + */ +uint16_t bip_get_port(void) +{ + return ntohs(BIP_Port); +} + +/** + * @brief Get the IPv4 address for my interface. Used for sending src address. + * @param addr - BACnet datalink address + */ +void bip_get_my_address(BACNET_ADDRESS *addr) +{ + unsigned int i = 0; + + if (addr) { + addr->mac_len = BIP_ADDRESS_MAX; /* 6 */ + memcpy(&addr->mac[0], &BIP_Address.s_addr, IP_ADDRESS_MAX); /* 4 */ + memcpy(&addr->mac[IP_ADDRESS_MAX], &BIP_Port, sizeof(BIP_Port)); + /* local only, no routing */ + addr->net = 0; + /* no SLEN */ + addr->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + /* no SADR */ + addr->adr[i] = 0; + } + } +} + +/** + * Get the IPv4 broadcast address for my interface. + * + * @param addr - BACnet datalink address + */ + +void bip_get_broadcast_address(BACNET_ADDRESS *dest) +{ + int i = 0; + + if (dest) { + dest->mac_len = BIP_ADDRESS_MAX; + memcpy(&dest->mac[0], &BIP_Broadcast_Addr.s_addr, IP_ADDRESS_MAX); + memcpy(&dest->mac[IP_ADDRESS_MAX], &BIP_Port, sizeof(BIP_Port)); + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } + } + return; +} + + +/** + * Set the BACnet/IP address + * + * @param addr - network IPv4 address + */ +bool bip_set_addr(BACNET_IP_ADDRESS *addr) +{ + if (addr) { + memcpy(&BIP_Address.s_addr, &addr->address[0], IP_ADDRESS_MAX); + memcpy(&BIP_Port, &addr->port, sizeof(addr->port)); + return true; + } + return false; +} + +/** + * @brief Get the BACnet/IP address + * @param addr - network IPv4 address + * @return true if the address was retrieved + */ +bool bip_get_addr(BACNET_IP_ADDRESS *addr) +{ + if (addr) { + memcpy(&addr->address[0], &BIP_Address.s_addr, IP_ADDRESS_MAX); + memcpy(&addr->port, &BIP_Port, sizeof(addr->port)); + return true; + } + return false; +} + +/** + * @brief Set the BACnet/IP address + * @param addr - network IPv4 address + * @return true if the address was set + */ +bool bip_set_broadcast_addr(BACNET_IP_ADDRESS *addr) +{ + if (addr) { + memcpy(&BIP_Broadcast_Addr.s_addr, &addr->address[0], IP_ADDRESS_MAX); + return true; + } + return false; +} + +/** + * Get the BACnet/IP address + * + * @return BACnet/IP address + */ +bool bip_get_broadcast_addr(BACNET_IP_ADDRESS *addr) +{ + if (addr) { + memcpy(&addr->address[0], &BIP_Broadcast_Addr.s_addr, IP_ADDRESS_MAX); + addr->port = ntohs(BIP_Port); + return true; + } + return false; +} + +/** + * @brief Set the BACnet/IP subnet mask CIDR prefix + * @return true if the subnet mask CIDR prefix is set + */ +bool bip_set_subnet_prefix(uint8_t prefix) +{ + /* not something we do within this driver */ + return false; +} + +/** + * @brief Get the BACnet/IP subnet mask CIDR prefix + * @return subnet mask CIDR prefix 1..32 + */ +uint8_t bip_get_subnet_prefix(void) +{ + uint32_t address = 0; + uint32_t broadcast = 0; + uint32_t test_broadcast = 0; + uint32_t mask = 0xFFFFFFFE; + uint8_t prefix = 0; + + address = BIP_Address.s_addr; + broadcast = BIP_Broadcast_Addr.s_addr; + /* calculate the subnet prefix from the broadcast address */ + for (prefix = 1; prefix <= 32; prefix++) { + test_broadcast = (address & mask) | (~mask); + if (test_broadcast == broadcast) { + break; + } + mask = mask<<1; + } + + return prefix; +} + +/** + * The send function for BACnet/IP driver layer + * + * @param dest - Points to a BACNET_IP_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 bip_send_mpdu(BACNET_IP_ADDRESS *dest, uint8_t *mtu, uint16_t mtu_len) +{ + struct sockaddr_in bip_dest = { 0 }; + + /* assumes that the driver has already been initialized */ + if (BIP_Socket < 0) { + LOG_ERR("%s:%d - Socket not initialized!", THIS_FILE, __LINE__); + return BIP_Socket; + } + + /* load destination IP address */ + bip_dest.sin_family = AF_INET; + memcpy(&bip_dest.sin_addr.s_addr, &dest->address[0], IP_ADDRESS_MAX); + bip_dest.sin_port = htons(dest->port); + + /* Send the packet */ + debug_print_ipv4("Sending MPDU->", &bip_dest.sin_addr, bip_dest.sin_port, + mtu_len); + return zsock_sendto(BIP_Socket, (char *)mtu, mtu_len, 0, + (struct sockaddr *)&bip_dest, sizeof(struct sockaddr)); +} + +/** + * 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 bip_receive( + BACNET_ADDRESS *src, uint8_t *npdu, uint16_t max_npdu, unsigned timeout) +{ + uint16_t npdu_len = 0; /* return value */ + zsock_fd_set read_fds; + int max = 0; + struct zsock_timeval select_timeout; + struct sockaddr_in sin = { 0 }; + BACNET_IP_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 (BIP_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; + } + ZSOCK_FD_ZERO(&read_fds); + ZSOCK_FD_SET(BIP_Socket, &read_fds); + max = BIP_Socket; + + /* see if there is a packet for us */ + if (zsock_select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) { + received_bytes = zsock_recvfrom(BIP_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) { + LOG_WRN("%s:%d - RX zsock_recvfrom() error: %d", THIS_FILE, __LINE__, received_bytes); + return 0; + } + /* no problem, just no bytes */ + if (received_bytes == 0) { + return 0; + } + /* the signature of a BACnet/IP packet */ + if (npdu[0] != BVLL_TYPE_BACNET_IP) { + LOG_WRN("%s:%d - RX bad packet", THIS_FILE, __LINE__); + return 0; + } + + /* Data link layer addressing between B/IPv4 nodes consists of a 32-bit + IPv4 address followed by a two-octet UDP port number (both of which + shall be transmitted with the most significant octet first). This + address shall be referred to as a B/IPv4 address. + */ + + memcpy(&addr.address[0], &sin.sin_addr.s_addr, IP_ADDRESS_MAX); + addr.port = ntohs(sin.sin_port); + + debug_print_ipv4("Received MPDU->", &sin.sin_addr, sin.sin_port, + received_bytes); + /* pass the packet into the BBMD handler */ + offset = bvlc_handler(&addr, src, npdu, received_bytes); + if (offset > 0) { + npdu_len = received_bytes - offset; + debug_print_ipv4("Received NPDU->", &sin.sin_addr, sin.sin_port, + npdu_len); + 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 { + LOG_WRN("%s:%d - NPDU dropped!", THIS_FILE, __LINE__); + npdu_len = 0; + } + } + + return npdu_len; +} + +/** + * The common send function for BACnet/IP application layer + * + * @param dest - Points to a #BACNET_ADDRESS structure containing the + * destination address. + * @param npdu_data - Points to a BACNET_NPDU_DATA structure containing the + * destination network layer control flags and data. + * @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 bip_send_pdu(BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, + unsigned pdu_len) +{ + dest->net = BACNET_BROADCAST_NETWORK; + return bvlc_send_pdu(dest, npdu_data, pdu, pdu_len); +} + +/** 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 net_if *interface = 0; + int index = -1; + uint8_t x=0; + + BACNET_IP_ADDRESS unicast = {0}; + BACNET_IP_ADDRESS broadcast = {0}; + + /* Network byte order */ + unicast.port = BIP_Port; + broadcast.port = BIP_Port; + + LOG_INF("bip_set_interface()"); + LOG_INF("UDP port: %d", ntohs(BIP_Port)); + + if(ifname) + { + index = atoi(ifname); + + /* if index is zero, discern between "0" and a parse error */ + if(!index && strcmp(ifname,"0")) + { + LOG_ERR("%s:%d - Argument must parse to an integer", THIS_FILE, __LINE__); + } + else + { + interface = net_if_get_by_index(index); + if(interface) + { + LOG_INF("Using interface %d", index); + } + else + { + LOG_ERR("%s:%d - No interface at index %d", THIS_FILE, __LINE__, index); + } + } + } + + if(index == -1) + { + LOG_WRN("%s:%d - No valid interface specified - using default ",THIS_FILE, __LINE__); + interface = net_if_get_default(); + } + + if(interface) + { + LOG_INF("Interface set"); + + if(CONFIG_BACDL_BIP_ADDRESS_INDEX >= NET_IF_MAX_IPV4_ADDR) + { + LOG_ERR("%s:%d - IPv4 address index of %d is out of range (0-%d)",THIS_FILE, __LINE__, CONFIG_BACDL_BIP_ADDRESS_INDEX, NET_IF_MAX_IPV4_ADDR-1); + return; + } + + LOG_INF("Using IPv4 address at index %d", CONFIG_BACDL_BIP_ADDRESS_INDEX); + + /* Build the broadcast address from the unicast and netmask */ + for(x=0; xconfig.ip.ipv4->unicast[CONFIG_BACDL_BIP_ADDRESS_INDEX].address.in_addr.s4_addr[x] | + ~interface->config.ip.ipv4->netmask.s4_addr[x]; + + unicast.address[x] = interface->config.ip.ipv4->unicast[CONFIG_BACDL_BIP_ADDRESS_INDEX].address.in_addr.s4_addr[x]; + } + + bip_set_addr(&unicast); + bip_set_broadcast_addr(&broadcast); + + /* net_if -> net_if_config . net_if_ip . net_if_ipv4 -> net_if_addr . net_addr . in_addr . s4_addr[4] */ + LOG_INF(" Unicast: %s", log_strdup(inet_ntoa(&interface->config.ip.ipv4->unicast->address.in_addr))); + LOG_INF(" Broadcast: %s", log_strdup(inet_ntoa(&BIP_Broadcast_Addr))); + LOG_INF(" Netmask: %s", log_strdup(inet_ntoa(&interface->config.ip.ipv4->netmask)) ); + } + else + { + LOG_ERR("%s:%d - Failed to set interface", THIS_FILE, __LINE__); + } +} + +/** 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 Zephyr, ifname is the index number of the interface as a string. + * + * @param ifname [in] The named interface to use for the network layer. + * If NULL, the default 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 sock_fd = -1; + const int sockopt = 1; + int status = -1; + struct sockaddr_in sin = { 0 }; + + bip_set_interface(ifname); + + if (BIP_Address.s_addr == 0) { + LOG_ERR("%s:%d - Failed to get an IP address on interface: %s\n", THIS_FILE, __LINE__, log_strdup(ifname ? ifname : "[default]")); + return false; + } + + /* assumes that the driver has already been initialized */ + sock_fd = zsock_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + BIP_Socket = sock_fd; + if (sock_fd < 0) { + LOG_ERR("%s:%d - Failed to create socket", THIS_FILE, __LINE__); + return false; + } + else + { + LOG_DBG("Socket created"); + } + + /* Allow us to use the same socket for sending and receiving */ + /* This makes sure that the src port is correct when sending */ + status = zsock_setsockopt( + sock_fd, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt)); + if (status < 0) { + zsock_close(sock_fd); + BIP_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_Port; + + status = + zsock_bind(sock_fd, (const struct sockaddr *)&sin, sizeof(struct sockaddr)); + if (status < 0) { + zsock_close(sock_fd); + BIP_Socket = -1; + LOG_ERR("%s:%d - zsock_bind() failure", THIS_FILE, __LINE__); + return false; + } + else + { + LOG_DBG("Socket bound"); + } + + bvlc_init(); + + LOG_DBG("bip_init() success"); + return true; +} + +/** + * @brief Determine if this BACnet/IP datalink is valid + * @return true if the BACnet/IP datalink is valid + */ +bool bip_valid(void) +{ + return (BIP_Socket != -1); +} + +/** Cleanup and close out the BACnet/IP services by closing the socket. + * @ingroup DLBIP + */ +void bip_cleanup(void) +{ + LOG_DBG("bip_cleanup()"); + + BIP_Port = 0; + memset(&BIP_Address, 0, sizeof(BIP_Address)); + memset(&BIP_Broadcast_Addr, 0, sizeof(BIP_Broadcast_Addr)); + + if (BIP_Socket != -1) { + zsock_close(BIP_Socket); + } + BIP_Socket = -1; + + return; +} diff --git a/ports/zephyr/bip6-init.c b/ports/zephyr/bip6-init.c new file mode 100644 index 00000000..41858414 --- /dev/null +++ b/ports/zephyr/bip6-init.c @@ -0,0 +1,564 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005-2020 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "bacnet/bacdcode.h" +#include "bacnet/bacint.h" +#include "bacnet/datalink/bip6.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/bbmd6/h_bbmd6.h" + +/* Logging module registration is already done in ports/zephyr/main.c */ +#include +#include + +LOG_MODULE_DECLARE(bacnet, CONFIG_BACNETSTACK_LOG_LEVEL); + +#define THIS_FILE "bip6-init.c" + +#if (MAX_MAC_LEN < BIP6_ADDRESS_MAX) /* Make sure an 18 byte address can be stored */ +#error "BACNET_ADDRESS.mac (bacdef.h) is not large enough to store an IPv6 address." +#endif + +/* zephyr socket */ +static int BIP6_Socket = -1; + +/* NOTE: we store address and port in network byte order + since BACnet/IP uses network byte order for all address byte arrays +*/ +/* port to use - stored here in network byte order */ +static uint16_t BIP6_Port = htons(CONFIG_BACDL_BIP6_PORT); +/* IP address - stored here in network byte order */ +static struct in6_addr BIP6_Address; +/* IP multicast address - stored here in network byte order */ +static struct in6_addr BIP6_Multicast_Addr; + +/* Used by inet6_ntoa */ +#if CONFIG_BACNETSTACK_LOG_LEVEL +static char ipv6_addr_str[42]={0}; +#else +static char ipv6_addr_str[]=""; +#endif + +/** +* @brief Return a string representation of an IPv6 address +* @param a - IPv6 address +* @return Pointer to global string +*/ +static char* inet6_ntoa(struct in6_addr *a) +{ + #if CONFIG_BACNETSTACK_LOG_LEVEL + uint8_t x=0; + uint8_t d=0; + uint8_t non_zero_count = 0; + + /* Avoid overwhelming the logging system */ + while(log_buffered_cnt()) + { + k_sleep(K_MSEC(1)); + } + + for(x=0; xs6_addr[x] | a->s6_addr[x+1]) + { + non_zero_count++; + d+=sprintf(&ipv6_addr_str[d], "%02X%02X", a->s6_addr[x], a->s6_addr[x+1]); + } + + if(x<14) + { + d+=sprintf(&ipv6_addr_str[d], ":"); + } + } + + if(!non_zero_count) + { + sprintf(&ipv6_addr_str[0], "undefined"); + } + #endif + return &ipv6_addr_str[0]; +} + +/** + * @brief Set the BACnet IPv6 UDP port number + * @param port - IPv6 UDP port number - in host byte order + */ +void bip6_set_port(uint16_t port) +{ + BIP6_Port = htons(port); +} + +/** + * @brief Get the BACnet IPv6 UDP port number + * @return IPv4 UDP port number - in host byte order + */ +uint16_t bip6_get_port(void) +{ + return ntohs(BIP6_Port); +} + +/** + * @brief Get the IPv6 address for my interface. Used for sending src address. + * @param addr - BACnet datalink address + */ +void bip6_get_my_address(BACNET_ADDRESS *addr) +{ + unsigned int i = 0; + + if (addr) { + addr->mac_len = BIP6_ADDRESS_MAX; /* 18 */ + memcpy(&addr->mac[0], &BIP6_Address.s6_addr, IP6_ADDRESS_MAX); /* 16 */ + memcpy(&addr->mac[IP6_ADDRESS_MAX], &BIP6_Port, sizeof(BIP6_Port)); + /* local only, no routing */ + addr->net = 0; + /* no SLEN */ + addr->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + /* no SADR */ + addr->adr[i] = 0; + } + } +} + +/** + * Get the IPv6 broadcast address for my interface. + * + * @param addr - BACnet datalink address + */ +void bip6_get_broadcast_address(BACNET_ADDRESS *dest) +{ + /* BIP6_ADDRESS_MAX = 18 */ + /* IP6_ADDRESS_MAX = 16 */ + + /* Store IPv6 address and port in dest->mac and + clear dest->adr which is used for hardware MAC */ + + if (dest) { + dest->mac_len = BIP6_ADDRESS_MAX; + memcpy(&dest->mac[0], &BIP6_Multicast_Addr.s6_addr, IP6_ADDRESS_MAX); + memcpy(&dest->mac[IP6_ADDRESS_MAX], &BIP6_Port, sizeof(BIP6_Port) ); + memset(&dest->adr, 0, sizeof(dest->adr)); + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; + } + return; +} + +/** + * Set the BACnet/IP address + * + * @param addr - network IPv6 address + */ +bool bip6_set_addr(BACNET_IP6_ADDRESS *addr) +{ + if (addr) { + memcpy(&BIP6_Address.s6_addr, &addr->address[0], IP6_ADDRESS_MAX); + memcpy(&BIP6_Port, &addr->port, sizeof(addr->port)); + return true; + } + return false; +} + +/** + * @brief Get the BACnet/IP address + * @param addr - network IPv6 address + * @return true if the address was retrieved + */ +bool bip6_get_addr(BACNET_IP6_ADDRESS *addr) +{ + if (addr) { + memcpy(&addr->address[0], &BIP6_Address, IP6_ADDRESS_MAX); + memcpy(&addr->port, &BIP6_Port, sizeof(addr->port)); + return true; + } + return false; +} + +/** + * @brief Set the BACnet/IP address + * @param addr - network IPv6 address + * @return true if the address was set + */ +bool bip6_set_broadcast_addr(BACNET_IP6_ADDRESS *addr) +{ + if (addr) { + memcpy(&BIP6_Multicast_Addr.s6_addr, &addr->address[0], IP6_ADDRESS_MAX); + return true; + } + return false; +} + +/** + * Get the BACnet/IP address + * + * @return BACnet/IP address + */ +bool bip6_get_broadcast_addr(BACNET_IP6_ADDRESS *addr) +{ + if (addr) { + memcpy(&addr->address[0], &BIP6_Multicast_Addr.s6_addr, IP6_ADDRESS_MAX); + addr->port = ntohs(BIP6_Port); + return true; + } + return false; +} + +/** + * @brief Set the BACnet/IP subnet mask CIDR prefix + * @return true if the subnet mask CIDR prefix is set + */ +bool bip6_set_subnet_prefix(uint8_t prefix) +{ + /* not something we do within this driver */ + return false; +} + +/** + * @brief Set the BACnet/IP subnet mask CIDR prefix + * @return true if the subnet mask CIDR prefix is set + */ +uint8_t bip6_get_subnet_prefix(void) +{ + /* not something we do within this driver */ + return 0; +} + +/** + * The send function for BACnet/IP driver layer + * + * @param dest - Points to a BACNET_IP_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 bip6_dest = { 0 }; + + /* assumes that the driver has already been initialized */ + if (BIP6_Socket < 0) { + LOG_ERR("%s:%d - Socket not initialized!", THIS_FILE, __LINE__); + return BIP6_Socket; + } + + /* load destination IP address */ + bip6_dest.sin6_family = AF_INET6; + memcpy(&bip6_dest.sin6_addr.s6_addr, &dest->address[0], IP6_ADDRESS_MAX); + bip6_dest.sin6_port = htons(dest->port); + + /* Send the packet */ + return zsock_sendto(BIP6_Socket, (char *)mtu, mtu_len, 0, + (struct sockaddr *)&bip6_dest, sizeof(struct sockaddr)); +} + +uint16_t bip6_receive( + BACNET_ADDRESS *src, uint8_t *npdu, uint16_t max_npdu, unsigned timeout) +{ + uint16_t npdu_len = 0; /* return value */ + zsock_fd_set read_fds; + int max = 0; + struct zsock_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; + } + ZSOCK_FD_ZERO(&read_fds); + ZSOCK_FD_SET(BIP6_Socket, &read_fds); + max = BIP6_Socket; + + /* see if there is a packet for us */ + if (zsock_select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) { + received_bytes = zsock_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) { + LOG_WRN("%s:%d - RX zsock_recvfrom() error: %d", THIS_FILE, __LINE__, received_bytes); + 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) { + LOG_WRN("%s:%d - RX bad packet", THIS_FILE, __LINE__); + return 0; + } + + /* Data link layer addressing between B/IPv6 nodes consists of a 128-bit + IPv6 address followed by a two-octet UDP port number (both of which + shall be transmitted with the most significant octet first). This + address shall be referred to as a B/IPv6 address. + */ + + memcpy(&addr.address[0], &sin.sin6_addr.s6_addr, IP6_ADDRESS_MAX); + 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 { + LOG_WRN("%s:%d - NPDU dropped!", THIS_FILE, __LINE__); + npdu_len = 0; + } + } + return npdu_len; +} + +int bip6_send_pdu(BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, + unsigned pdu_len) +{ + dest->net = BACNET_BROADCAST_NETWORK; + return bvlc6_send_pdu(dest, npdu_data, pdu, pdu_len); +} + +void bip6_set_interface(char *ifname) +{ + struct net_if *interface = 0; + int index = -1; + int x=0; + + BACNET_IP6_ADDRESS unicast = {0}; + BACNET_IP6_ADDRESS multicast = {0}; + + unicast.port = BIP6_Port; + multicast.port = BIP6_Port; + + LOG_INF("bip6_set_interface()"); + LOG_INF("UDP port: %d", ntohs(BIP6_Port)); + + if(ifname) + { + index = atoi(ifname); + + /* if index is zero, discern between "0" and a parse error */ + if(!index && strcmp(ifname,"0")) + { + LOG_ERR("%s:%d - Argument must parse to an integer", THIS_FILE, __LINE__); + } + else + { + interface = net_if_get_by_index(index); + if(interface) + { + LOG_INF("Using interface %d", index); + } + else + { + LOG_ERR("%s:%d - No interface at index %d", THIS_FILE, __LINE__, index); + } + } + } + + if(index == -1) + { + LOG_WRN("%s:%d - No valid interface specified - using default ",THIS_FILE, __LINE__); + interface = net_if_get_default(); + } + + if(interface) + { + LOG_INF("Interface set - Configured addresses:"); + + for(x=0; xconfig.ip.ipv6->unicast[x].address.in6_addr ); + LOG_INF(" unicast[%d]: %s", x, log_strdup(ipv6_addr_str)); + } + + for(x=0; xconfig.ip.ipv6->mcast[x].address.in6_addr ); + LOG_INF(" multicast[%d]: %s", x, log_strdup(ipv6_addr_str)); + } + + if(CONFIG_BACDL_BIP6_ADDRESS_INDEX >= NET_IF_MAX_IPV6_ADDR) + { + LOG_ERR("%s:%d - IPv6 address index of %d is out of range (0-%d)", THIS_FILE, + __LINE__, CONFIG_BACDL_BIP6_ADDRESS_INDEX, NET_IF_MAX_IPV6_ADDR-1); + return; + } + + LOG_INF("Using IPv6 address at index %d", CONFIG_BACDL_BIP6_ADDRESS_INDEX); + + memcpy(&unicast.address, &interface->config.ip.ipv6->unicast + [CONFIG_BACDL_BIP6_ADDRESS_INDEX].address.in6_addr, IP6_ADDRESS_MAX); + + if(net_addr_pton(AF_INET6, CONFIG_BACDL_BIP6_MCAST_ADDRESS, &multicast.address)) + { + LOG_ERR("%s:%d - Failed to parse IPv6 multicast address: %s", THIS_FILE, __LINE__, CONFIG_BACDL_BIP6_MCAST_ADDRESS); + } + + bip6_set_addr(&unicast); + bip6_set_broadcast_addr(&multicast); + + LOG_INF(" Unicast: %s", log_strdup(inet6_ntoa((struct in6_addr*)&unicast.address))); + LOG_INF(" Multicast: %s", log_strdup(inet6_ntoa((struct in6_addr*)&multicast.address))); + } + else + { + LOG_ERR("%s:%d - Failed to set interface", THIS_FILE, __LINE__); + } +} + + +bool bip6_init(char *ifname) +{ + LOG_INF("bip6_init()"); + + int sock_fd = -1; + const int sockopt = 1; + int status = -1; + struct sockaddr_in6 sin6 = { 0 }; + + bip6_set_interface(ifname); + + if (BIP6_Address.s6_addr == 0) { + LOG_ERR("%s:%d - Failed to get an IPv6 address on interface: %s\n", THIS_FILE, __LINE__, log_strdup(ifname ? ifname : "[default]")); + return false; + } + + /* assumes that the driver has already been initialized */ + sock_fd = zsock_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + BIP6_Socket = sock_fd; + if (sock_fd < 0) { + LOG_ERR("%s:%d - Failed to create socket", THIS_FILE, __LINE__); + return false; + } + else + { + LOG_INF("Socket created"); + } + + /* Allow us to use the same socket for sending and receiving */ + /* This makes sure that the src port is correct when sending */ + status = zsock_setsockopt( + sock_fd, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt)); + if (status < 0) { + zsock_close(sock_fd); + BIP6_Socket = -1; + return false; + } + + /* bind the socket to the local port number and IP address */ + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = in6addr_any; + sin6.sin6_port = BIP6_Port; + + LOG_INF("Binding to port %d", ntohs(BIP6_Port)); + + status = + zsock_bind(sock_fd, (const struct sockaddr*)&sin6, sizeof(struct sockaddr)); + if (status < 0) { + zsock_close(sock_fd); + BIP6_Socket = -1; + LOG_ERR("%s:%d - zsock_bind() failure", THIS_FILE, __LINE__); + return false; + } + else + { + LOG_INF("Socket bound"); + } + + bvlc6_init(); + + LOG_INF("bip6_init() success"); + return true; +} + +bool bip6_valid(void) +{ + return (BIP6_Socket != -1); +} + +void bip6_cleanup(void) +{ + LOG_INF("bip6_cleanup()"); + + BIP6_Port = 0; + memset(&BIP6_Address, 0, sizeof(BIP6_Address)); + memset(&BIP6_Multicast_Addr, 0, sizeof(BIP6_Multicast_Addr)); + + if (BIP6_Socket != -1) { + zsock_close(BIP6_Socket); + } + BIP6_Socket = -1; + + return; +} diff --git a/ports/zephyr/datalink/bvlc.c b/ports/zephyr/datalink/bvlc.c new file mode 100644 index 00000000..145e982b --- /dev/null +++ b/ports/zephyr/datalink/bvlc.c @@ -0,0 +1,64 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2020 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 "bacnet/datalink/bvlc.h" + +/** + * @brief Encode the BVLC header + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param message_type - BVLL Messages + * @param length - number of bytes for this message type + * + * @return number of bytes encoded + */ +void bvlc_file_bdt_write( + void *data, + size_t len) +{ + /* TODO: Write BDT data blob into persistent storage */ +} + + +size_t bvlc_file_bdt_read( + void *data, + size_t len) +{ + size_t sz = -1; + + /* TODO: Read BD data blob from persistent storage */ + + return sz; +} diff --git a/ports/zephyr/datetime-init.c b/ports/zephyr/datetime-init.c new file mode 100644 index 00000000..741cc9fa --- /dev/null +++ b/ports/zephyr/datetime-init.c @@ -0,0 +1,108 @@ +/** + * @file + * @author Steve Karg + * @date 2009 + * @brief System time library header file. + * + * @section DESCRIPTION + * + * This library provides functions for getting and setting the system time. + */ +#include +#include +#include +#include +#include +#include +#include "bacnet/datetime.h" + + +/* HACK: + * - Zephyr does not declare timezone in any header file. + * - The gcc-arm-none-eabi-7-2018-q2-update/bin/arm-none-eabi/lib/thumb/v7e-m/ + * libc_nano.a 'time' symbol does not resolve '_gettimeofday' + * + * TODO: figure out how to link in the real time() and timezone; + */ +long timezone; + +time_t time(time_t *tloc) +{ + time_t time = { 0 }; + + return time; +} + +/** + * @brief Get the date, time, timezone, and UTC offset from system + * @param utc_time - the BACnet Date and Time structure to hold UTC time + * @param local_time - the BACnet Date and Time structure to hold local time + * @param utc_offset_minutes - number of minutes offset from UTC + * For example, -6*60 represents 6.00 hours behind UTC/GMT + * @param true if DST is enabled and active + * @return true if local time was retrieved + */ +bool datetime_local( + BACNET_DATE * bdate, + BACNET_TIME * btime, + int16_t * utc_offset_minutes, + bool * dst_active) +{ + bool status = false; + struct tm tblock_st = { 0 }; + struct tm *tblock = &tblock_st; + struct timeval tv; + + if (gettimeofday(&tv, NULL) == 0) + { + tblock = (struct tm *)localtime((const time_t *)&tv.tv_sec); + } + if (tblock) { + status = true; + /** struct tm + * int tm_sec Seconds [0,60]. + * int tm_min Minutes [0,59]. + * int tm_hour Hour [0,23]. + * int tm_mday Day of month [1,31]. + * int tm_mon Month of year [0,11]. + * int tm_year Years since 1900. + * int tm_wday Day of week [0,6] (Sunday =0). + * int tm_yday Day of year [0,365]. + * int tm_isdst Daylight Savings flag. + */ + datetime_set_date(bdate, (uint16_t)tblock->tm_year + 1900, + (uint8_t)tblock->tm_mon + 1, + (uint8_t)tblock->tm_mday); + datetime_set_time(btime, (uint8_t)tblock->tm_hour, + (uint8_t)tblock->tm_min, (uint8_t)tblock->tm_sec, + (uint8_t)(tv.tv_usec / 10000)); + if (dst_active) { + /* The value of tm_isdst is: + - positive if Daylight Saving Time is in effect, + - 0 if Daylight Saving Time is not in effect, and + - negative if the information is not available. */ + if (tblock->tm_isdst > 0) { + *dst_active = true; + } else { + *dst_active = false; + } + } + /* note: timezone is declared in stdlib. */ + if (utc_offset_minutes) { + /* timezone is set to the difference, in seconds, + between Coordinated Universal Time (UTC) and + local standard time */ + *utc_offset_minutes = timezone / 60; + } + } + + return status; +} + +/** + * initialize the date time + */ +void datetime_init(void) +{ + /* nothing to do */ +} diff --git a/ports/zephyr/device.c b/ports/zephyr/device.c new file mode 100644 index 00000000..9ea683e8 --- /dev/null +++ b/ports/zephyr/device.c @@ -0,0 +1,1923 @@ +/************************************************************************** + * + * Copyright (C) 2005,2006,2009 Steve Karg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + *********************************************************************/ + +/** @file device.c Base "class" for handling all BACnet objects belonging + * to a BACnet device, as well as Device-specific properties. */ + +#include +#include +#include /* for memmove */ +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacapp.h" +#include "bacnet/config.h" /* the custom stuff */ +#include "bacnet/datetime.h" +#include "bacnet/apdu.h" +#include "bacnet/wp.h" /* WriteProperty handling */ +#include "bacnet/rp.h" /* ReadProperty handling */ +#include "bacnet/dcc.h" /* DeviceCommunicationControl handling */ +#include "bacnet/version.h" +#include "bacnet/basic/object/device.h" /* me */ +#include "bacnet/basic/services.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/basic/binding/address.h" +/* include the device object */ +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/object/acc.h" +#include "bacnet/basic/object/ai.h" +#include "bacnet/basic/object/ao.h" +#include "bacnet/basic/object/av.h" +#include "bacnet/basic/object/bi.h" +#include "bacnet/basic/object/bo.h" +#include "bacnet/basic/object/bv.h" +#include "bacnet/basic/object/channel.h" +#include "bacnet/basic/object/command.h" +#include "bacnet/basic/object/csv.h" +#include "bacnet/basic/object/iv.h" +#include "bacnet/basic/object/lc.h" +#include "bacnet/basic/object/lsp.h" +#include "bacnet/basic/object/ms-input.h" +#include "bacnet/basic/object/mso.h" +#include "bacnet/basic/object/msv.h" +#if (BACNET_PROTOCOL_REVISION >= 17) +#include "bacnet/basic/object/netport.h" +#endif +#include "bacnet/basic/object/osv.h" +#include "bacnet/basic/object/piv.h" +#include "bacnet/basic/object/schedule.h" +#include "bacnet/basic/object/trendlog.h" +#if defined(INTRINSIC_REPORTING) +#include "bacnet/basic/object/nc.h" +#endif /* defined(INTRINSIC_REPORTING) */ +#if defined(BACFILE) +#include "bacnet/basic/object/bacfile.h" +#endif /* defined(BACFILE) */ +#if defined(BAC_UCI) +#include "bacnet/basic/ucix/ucix.h" +#endif /* defined(BAC_UCI) */ + +/* local forward (semi-private) and external prototypes */ +int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata); +bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data); +extern int Routed_Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata); +extern bool Routed_Device_Write_Property_Local( + BACNET_WRITE_PROPERTY_DATA *wp_data); + +/* may be overridden by outside table */ +static object_functions_t *Object_Table; + +static object_functions_t My_Object_Table[] = { + { OBJECT_DEVICE, NULL /* Init - don't init Device or it will recourse! */, + Device_Count, Device_Index_To_Instance, + Device_Valid_Object_Instance_Number, Device_Object_Name, + Device_Read_Property_Local, Device_Write_Property_Local, + Device_Property_Lists, DeviceGetRRInfo, NULL /* Iterator */, + NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, + NULL /* Intrinsic Reporting */ }, +#if 0 //TODO: Enable +#if (BACNET_PROTOCOL_REVISION >= 17) + { OBJECT_NETWORK_PORT, Network_Port_Init, Network_Port_Count, + Network_Port_Index_To_Instance, Network_Port_Valid_Instance, + Network_Port_Object_Name, Network_Port_Read_Property, + Network_Port_Write_Property, Network_Port_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, +#endif + { OBJECT_ANALOG_INPUT, Analog_Input_Init, Analog_Input_Count, + Analog_Input_Index_To_Instance, Analog_Input_Valid_Instance, + Analog_Input_Object_Name, Analog_Input_Read_Property, + Analog_Input_Write_Property, Analog_Input_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, + Analog_Input_Encode_Value_List, Analog_Input_Change_Of_Value, + Analog_Input_Change_Of_Value_Clear, Analog_Input_Intrinsic_Reporting }, + { OBJECT_ANALOG_OUTPUT, Analog_Output_Init, Analog_Output_Count, + Analog_Output_Index_To_Instance, Analog_Output_Valid_Instance, + Analog_Output_Object_Name, Analog_Output_Read_Property, + Analog_Output_Write_Property, Analog_Output_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, + { OBJECT_ANALOG_VALUE, Analog_Value_Init, Analog_Value_Count, + Analog_Value_Index_To_Instance, Analog_Value_Valid_Instance, + Analog_Value_Object_Name, Analog_Value_Read_Property, + Analog_Value_Write_Property, Analog_Value_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, + Analog_Value_Encode_Value_List, Analog_Value_Change_Of_Value, + Analog_Value_Change_Of_Value_Clear, Analog_Value_Intrinsic_Reporting }, + { OBJECT_BINARY_INPUT, Binary_Input_Init, Binary_Input_Count, + Binary_Input_Index_To_Instance, Binary_Input_Valid_Instance, + Binary_Input_Object_Name, Binary_Input_Read_Property, + Binary_Input_Write_Property, Binary_Input_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, + Binary_Input_Encode_Value_List, Binary_Input_Change_Of_Value, + Binary_Input_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */ }, + { OBJECT_BINARY_OUTPUT, Binary_Output_Init, Binary_Output_Count, + Binary_Output_Index_To_Instance, Binary_Output_Valid_Instance, + Binary_Output_Object_Name, Binary_Output_Read_Property, + Binary_Output_Write_Property, Binary_Output_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, + { OBJECT_BINARY_VALUE, Binary_Value_Init, Binary_Value_Count, + Binary_Value_Index_To_Instance, Binary_Value_Valid_Instance, + Binary_Value_Object_Name, Binary_Value_Read_Property, + Binary_Value_Write_Property, Binary_Value_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, + { OBJECT_CHARACTERSTRING_VALUE, CharacterString_Value_Init, + CharacterString_Value_Count, CharacterString_Value_Index_To_Instance, + CharacterString_Value_Valid_Instance, CharacterString_Value_Object_Name, + CharacterString_Value_Read_Property, + CharacterString_Value_Write_Property, + CharacterString_Value_Property_Lists, NULL /* ReadRangeInfo */, + NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, + NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, + { OBJECT_COMMAND, Command_Init, Command_Count, Command_Index_To_Instance, + Command_Valid_Instance, Command_Object_Name, Command_Read_Property, + Command_Write_Property, Command_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, + { OBJECT_INTEGER_VALUE, Integer_Value_Init, Integer_Value_Count, + Integer_Value_Index_To_Instance, Integer_Value_Valid_Instance, + Integer_Value_Object_Name, Integer_Value_Read_Property, + Integer_Value_Write_Property, Integer_Value_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, +#if defined(INTRINSIC_REPORTING) + { OBJECT_NOTIFICATION_CLASS, Notification_Class_Init, + Notification_Class_Count, Notification_Class_Index_To_Instance, + Notification_Class_Valid_Instance, Notification_Class_Object_Name, + Notification_Class_Read_Property, Notification_Class_Write_Property, + Notification_Class_Property_Lists, NULL /* ReadRangeInfo */, + NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, + NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, +#endif + { OBJECT_LIFE_SAFETY_POINT, Life_Safety_Point_Init, Life_Safety_Point_Count, + Life_Safety_Point_Index_To_Instance, Life_Safety_Point_Valid_Instance, + Life_Safety_Point_Object_Name, Life_Safety_Point_Read_Property, + Life_Safety_Point_Write_Property, Life_Safety_Point_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, + { OBJECT_LOAD_CONTROL, Load_Control_Init, Load_Control_Count, + Load_Control_Index_To_Instance, Load_Control_Valid_Instance, + Load_Control_Object_Name, Load_Control_Read_Property, + Load_Control_Write_Property, Load_Control_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, + { OBJECT_MULTI_STATE_INPUT, Multistate_Input_Init, Multistate_Input_Count, + Multistate_Input_Index_To_Instance, Multistate_Input_Valid_Instance, + Multistate_Input_Object_Name, Multistate_Input_Read_Property, + Multistate_Input_Write_Property, Multistate_Input_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, + { OBJECT_MULTI_STATE_OUTPUT, Multistate_Output_Init, + Multistate_Output_Count, Multistate_Output_Index_To_Instance, + Multistate_Output_Valid_Instance, Multistate_Output_Object_Name, + Multistate_Output_Read_Property, Multistate_Output_Write_Property, + Multistate_Output_Property_Lists, NULL /* ReadRangeInfo */, + NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, + NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, + { OBJECT_MULTI_STATE_VALUE, Multistate_Value_Init, Multistate_Value_Count, + Multistate_Value_Index_To_Instance, Multistate_Value_Valid_Instance, + Multistate_Value_Object_Name, Multistate_Value_Read_Property, + Multistate_Value_Write_Property, Multistate_Value_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, + Multistate_Value_Encode_Value_List, Multistate_Value_Change_Of_Value, + Multistate_Value_Change_Of_Value_Clear, + NULL /* Intrinsic Reporting */ }, + { OBJECT_TRENDLOG, Trend_Log_Init, Trend_Log_Count, + Trend_Log_Index_To_Instance, Trend_Log_Valid_Instance, + Trend_Log_Object_Name, Trend_Log_Read_Property, + Trend_Log_Write_Property, Trend_Log_Property_Lists, TrendLogGetRRInfo, + NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, + NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, +#if (BACNET_PROTOCOL_REVISION >= 14) && defined(BACAPP_LIGHTING_COMMAND) + { OBJECT_LIGHTING_OUTPUT, Lighting_Output_Init, Lighting_Output_Count, + Lighting_Output_Index_To_Instance, Lighting_Output_Valid_Instance, + Lighting_Output_Object_Name, Lighting_Output_Read_Property, + Lighting_Output_Write_Property, Lighting_Output_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, + { OBJECT_CHANNEL, Channel_Init, Channel_Count, Channel_Index_To_Instance, + Channel_Valid_Instance, Channel_Object_Name, Channel_Read_Property, + Channel_Write_Property, Channel_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, +#endif +#if defined(BACFILE) + { OBJECT_FILE, bacfile_init, bacfile_count, bacfile_index_to_instance, + bacfile_valid_instance, bacfile_object_name, bacfile_read_property, + bacfile_write_property, BACfile_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, +#endif + { OBJECT_OCTETSTRING_VALUE, OctetString_Value_Init, OctetString_Value_Count, + OctetString_Value_Index_To_Instance, OctetString_Value_Valid_Instance, + OctetString_Value_Object_Name, OctetString_Value_Read_Property, + OctetString_Value_Write_Property, OctetString_Value_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, + { OBJECT_POSITIVE_INTEGER_VALUE, PositiveInteger_Value_Init, + PositiveInteger_Value_Count, PositiveInteger_Value_Index_To_Instance, + PositiveInteger_Value_Valid_Instance, PositiveInteger_Value_Object_Name, + PositiveInteger_Value_Read_Property, + PositiveInteger_Value_Write_Property, + PositiveInteger_Value_Property_Lists, NULL /* ReadRangeInfo */, + NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, + NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, + { OBJECT_SCHEDULE, Schedule_Init, Schedule_Count, + Schedule_Index_To_Instance, Schedule_Valid_Instance, + Schedule_Object_Name, Schedule_Read_Property, Schedule_Write_Property, + Schedule_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, + NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, + NULL /* Intrinsic Reporting */ }, + {OBJECT_ACCUMULATOR, Accumulator_Init, Accumulator_Count, + Accumulator_Index_To_Instance, Accumulator_Valid_Instance, + Accumulator_Object_Name, Accumulator_Read_Property, + Accumulator_Write_Property, Accumulator_Property_Lists, + NULL /* ReadRangeInfo */ , NULL /* Iterator */ , + NULL /* Value_Lists */ , NULL /* COV */ , NULL /* COV Clear */ , + NULL /* Intrinsic Reporting */ }, +#endif //TODO: Enable + { MAX_BACNET_OBJECT_TYPE, NULL /* Init */, NULL /* Count */, + NULL /* Index_To_Instance */, NULL /* Valid_Instance */, + NULL /* Object_Name */, NULL /* Read_Property */, + NULL /* Write_Property */, NULL /* Property_Lists */, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, + NULL /* Intrinsic Reporting */ } +}; + +/** Glue function to let the Device object, when called by a handler, + * lookup which Object type needs to be invoked. + * @ingroup ObjHelpers + * @param Object_Type [in] The type of BACnet Object the handler wants to + * access. + * @return Pointer to the group of object helper functions that implement this + * type of Object. + */ +static struct object_functions *Device_Objects_Find_Functions( + BACNET_OBJECT_TYPE Object_Type) +{ + struct object_functions *pObject = NULL; + + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + /* handle each object type */ + if (pObject->Object_Type == Object_Type) { + return (pObject); + } + pObject++; + } + + return (NULL); +} + +/** Try to find a rr_info_function helper function for the requested object + * type. + * @ingroup ObjIntf + * + * @param object_type [in] The type of BACnet Object the handler wants to + * access. + * @return Pointer to the object helper function that implements the + * ReadRangeInfo function, Object_RR_Info, for this type of Object on + * success, else a NULL pointer if the type of Object isn't supported + * or doesn't have a ReadRangeInfo function. + */ +rr_info_function Device_Objects_RR_Info(BACNET_OBJECT_TYPE object_type) +{ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + return (pObject != NULL ? pObject->Object_RR_Info : NULL); +} + +/** For a given object type, returns the special property list. + * This function is used for ReadPropertyMultiple calls which want + * just Required, just Optional, or All properties. + * @ingroup ObjIntf + * + * @param object_type [in] The desired BACNET_OBJECT_TYPE whose properties + * are to be listed. + * @param pPropertyList [out] Reference to the structure which will, on return, + * list, separately, the Required, Optional, and Proprietary object + * properties with their counts. + */ +void Device_Objects_Property_List(BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + struct special_property_list_t *pPropertyList) +{ + struct object_functions *pObject = NULL; + + (void)object_instance; + pPropertyList->Required.pList = NULL; + pPropertyList->Optional.pList = NULL; + pPropertyList->Proprietary.pList = NULL; + + /* If we can find an entry for the required object type + * and there is an Object_List_RPM fn ptr then call it + * to populate the pointers to the individual list counters. + */ + + pObject = Device_Objects_Find_Functions(object_type); + if ((pObject != NULL) && (pObject->Object_RPM_List != NULL)) { + pObject->Object_RPM_List(&pPropertyList->Required.pList, + &pPropertyList->Optional.pList, &pPropertyList->Proprietary.pList); + } + + /* Fetch the counts if available otherwise zero them */ + pPropertyList->Required.count = pPropertyList->Required.pList == NULL + ? 0 + : property_list_count(pPropertyList->Required.pList); + + pPropertyList->Optional.count = pPropertyList->Optional.pList == NULL + ? 0 + : property_list_count(pPropertyList->Optional.pList); + + pPropertyList->Proprietary.count = pPropertyList->Proprietary.pList == NULL + ? 0 + : property_list_count(pPropertyList->Proprietary.pList); + + return; +} + +/* These three arrays are used by the ReadPropertyMultiple handler */ +static const int Device_Properties_Required[] = { PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_SYSTEM_STATUS, PROP_VENDOR_NAME, + PROP_VENDOR_IDENTIFIER, PROP_MODEL_NAME, PROP_FIRMWARE_REVISION, + PROP_APPLICATION_SOFTWARE_VERSION, PROP_PROTOCOL_VERSION, + PROP_PROTOCOL_REVISION, PROP_PROTOCOL_SERVICES_SUPPORTED, + PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, PROP_OBJECT_LIST, + PROP_MAX_APDU_LENGTH_ACCEPTED, PROP_SEGMENTATION_SUPPORTED, + PROP_APDU_TIMEOUT, PROP_NUMBER_OF_APDU_RETRIES, PROP_DEVICE_ADDRESS_BINDING, + PROP_DATABASE_REVISION, -1 }; + +static const int Device_Properties_Optional[] = { +#if defined(BACDL_MSTP) + PROP_MAX_MASTER, PROP_MAX_INFO_FRAMES, +#endif + PROP_DESCRIPTION, PROP_LOCAL_TIME, PROP_UTC_OFFSET, PROP_LOCAL_DATE, + PROP_DAYLIGHT_SAVINGS_STATUS, PROP_LOCATION, PROP_ACTIVE_COV_SUBSCRIPTIONS, +#if defined(BACNET_TIME_MASTER) + PROP_TIME_SYNCHRONIZATION_RECIPIENTS, PROP_TIME_SYNCHRONIZATION_INTERVAL, + PROP_ALIGN_INTERVALS, PROP_INTERVAL_OFFSET, +#endif + -1 +}; + +static const int Device_Properties_Proprietary[] = { -1 }; + +void Device_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary) +{ + if (pRequired) { + *pRequired = Device_Properties_Required; + } + if (pOptional) { + *pOptional = Device_Properties_Optional; + } + if (pProprietary) { + *pProprietary = Device_Properties_Proprietary; + } + + return; +} + +/* note: you really only need to define variables for + properties that are writable or that may change. + The properties that are constant can be hard coded + into the read-property encoding. */ + +static uint32_t Object_Instance_Number = 260001; +static BACNET_CHARACTER_STRING My_Object_Name; +static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL; +static char *Vendor_Name = BACNET_VENDOR_NAME; +static uint16_t Vendor_Identifier = BACNET_VENDOR_ID; +static char Model_Name[MAX_DEV_MOD_LEN + 1] = "GNU"; +static char Application_Software_Version[MAX_DEV_VER_LEN + 1] = "1.0"; +static const char *BACnet_Version = BACNET_VERSION_TEXT; +static char Location[MAX_DEV_LOC_LEN + 1] = "USA"; +static char Description[MAX_DEV_DESC_LEN + 1] = "server"; +/* static uint8_t Protocol_Version = 1; - constant, not settable */ +/* static uint8_t Protocol_Revision = 4; - constant, not settable */ +/* Protocol_Services_Supported - dynamically generated */ +/* Protocol_Object_Types_Supported - in RP encoding */ +/* Object_List - dynamically generated */ +/* static BACNET_SEGMENTATION Segmentation_Supported = SEGMENTATION_NONE; */ +/* static uint8_t Max_Segments_Accepted = 0; */ +/* VT_Classes_Supported */ +/* Active_VT_Sessions */ +static BACNET_TIME Local_Time; /* rely on OS, if there is one */ +static BACNET_DATE Local_Date; /* rely on OS, if there is one */ +/* NOTE: BACnet UTC Offset is inverse of common practice. + If your UTC offset is -5hours of GMT, + then BACnet UTC offset is +5hours. + BACnet UTC offset is expressed in minutes. */ +static int16_t UTC_Offset = 5 * 60; +static bool Daylight_Savings_Status = false; /* rely on OS */ +#if defined(BACNET_TIME_MASTER) +static bool Align_Intervals; +static uint32_t Interval_Minutes; +static uint32_t Interval_Offset_Minutes; +/* Time_Synchronization_Recipients */ +#endif +/* List_Of_Session_Keys */ +/* Max_Master - rely on MS/TP subsystem, if there is one */ +/* Max_Info_Frames - rely on MS/TP subsystem, if there is one */ +/* Device_Address_Binding - required, but relies on binding cache */ +static uint32_t Database_Revision = 0; +/* Configuration_Files */ +/* Last_Restore_Time */ +/* Backup_Failure_Timeout */ +/* Active_COV_Subscriptions */ +/* Slave_Proxy_Enable */ +/* Manual_Slave_Address_Binding */ +/* Auto_Slave_Discovery */ +/* Slave_Address_Binding */ +/* Profile_Name */ +static BACNET_REINITIALIZED_STATE Reinitialize_State = BACNET_REINIT_IDLE; +static const char *Reinit_Password = "filister"; + +/** Commands a Device re-initialization, to a given state. + * The request's password must match for the operation to succeed. + * This implementation provides a framework, but doesn't + * actually *DO* anything. + * @note You could use a mix of states and passwords to multiple outcomes. + * @note You probably want to restart *after* the simple ack has been sent + * from the return handler, so just set a local flag here. + * @ingroup ObjIntf + * + * @param rd_data [in,out] The information from the RD request. + * On failure, the error class and code will be set. + * @return True if succeeds (password is correct), else False. + */ +bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) +{ + bool status = false; + + /* Note: you could use a mix of state and password to multiple things */ + if (characterstring_ansi_same(&rd_data->password, Reinit_Password)) { + switch (rd_data->state) { + case BACNET_REINIT_COLDSTART: + case BACNET_REINIT_WARMSTART: + dcc_set_status_duration(COMMUNICATION_ENABLE, 0); + /* note: you probably want to restart *after* the + simple ack has been sent from the return handler + so just set a flag from here */ + Reinitialize_State = rd_data->state; + status = true; + break; + case BACNET_REINIT_STARTBACKUP: + case BACNET_REINIT_ENDBACKUP: + case BACNET_REINIT_STARTRESTORE: + case BACNET_REINIT_ENDRESTORE: + case BACNET_REINIT_ABORTRESTORE: + if (dcc_communication_disabled()) { + rd_data->error_class = ERROR_CLASS_SERVICES; + rd_data->error_code = ERROR_CODE_COMMUNICATION_DISABLED; + } else { + rd_data->error_class = ERROR_CLASS_SERVICES; + rd_data->error_code = + ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED; + } + break; + default: + rd_data->error_class = ERROR_CLASS_SERVICES; + rd_data->error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; + break; + } + } else { + rd_data->error_class = ERROR_CLASS_SECURITY; + rd_data->error_code = ERROR_CODE_PASSWORD_FAILURE; + } + + return status; +} + +BACNET_REINITIALIZED_STATE Device_Reinitialized_State(void) +{ + return Reinitialize_State; +} + +unsigned Device_Count(void) +{ + return 1; +} + +uint32_t Device_Index_To_Instance(unsigned index) +{ + (void)index; + return Object_Instance_Number; +} + +/* methods to manipulate the data */ + +/** Return the Object Instance number for our (single) Device Object. + * This is a key function, widely invoked by the handler code, since + * it provides "our" (ie, local) address. + * @ingroup ObjIntf + * @return The Instance number used in the BACNET_OBJECT_ID for the Device. + */ +uint32_t Device_Object_Instance_Number(void) +{ +#ifdef BAC_ROUTING + return Routed_Device_Object_Instance_Number(); +#else + return Object_Instance_Number; +#endif +} + +bool Device_Set_Object_Instance_Number(uint32_t object_id) +{ + bool status = true; /* return value */ + + if (object_id <= BACNET_MAX_INSTANCE) { + /* Make the change and update the database revision */ + Object_Instance_Number = object_id; + Device_Inc_Database_Revision(); + } else { + status = false; + } + + return status; +} + +bool Device_Valid_Object_Instance_Number(uint32_t object_id) +{ + return (Object_Instance_Number == object_id); +} + +bool Device_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) +{ + bool status = false; + + if (object_instance == Object_Instance_Number) { + status = characterstring_copy(object_name, &My_Object_Name); + } + + return status; +} + +bool Device_Set_Object_Name(BACNET_CHARACTER_STRING *object_name) +{ + bool status = false; /*return value */ + + if (!characterstring_same(&My_Object_Name, object_name)) { + /* Make the change and update the database revision */ + status = characterstring_copy(&My_Object_Name, object_name); + Device_Inc_Database_Revision(); + } + + return status; +} + +bool Device_Object_Name_ANSI_Init(const char *value) +{ + return characterstring_init_ansi(&My_Object_Name, value); +} + +BACNET_DEVICE_STATUS Device_System_Status(void) +{ + return System_Status; +} + +int Device_Set_System_Status(BACNET_DEVICE_STATUS status, bool local) +{ + int result = 0; /*return value - 0 = ok, -1 = bad value, -2 = not allowed */ + + /* We limit the options available depending on whether the source is + * internal or external. */ + if (local) { + switch (status) { + case STATUS_OPERATIONAL: + case STATUS_OPERATIONAL_READ_ONLY: + case STATUS_DOWNLOAD_REQUIRED: + case STATUS_DOWNLOAD_IN_PROGRESS: + case STATUS_NON_OPERATIONAL: + System_Status = status; + break; + + /* Don't support backup at present so don't allow setting */ + case STATUS_BACKUP_IN_PROGRESS: + result = -2; + break; + + default: + result = -1; + break; + } + } else { + switch (status) { + /* Allow these for the moment as a way to easily alter + * overall device operation. The lack of password protection + * or other authentication makes allowing writes to this + * property a risky facility to provide. + */ + case STATUS_OPERATIONAL: + case STATUS_OPERATIONAL_READ_ONLY: + case STATUS_NON_OPERATIONAL: + System_Status = status; + break; + + /* Don't allow outsider set this - it should probably + * be set if the device config is incomplete or + * corrupted or perhaps after some sort of operator + * wipe operation. + */ + case STATUS_DOWNLOAD_REQUIRED: + /* Don't allow outsider set this - it should be set + * internally at the start of a multi packet download + * perhaps indirectly via PT or WF to a config file. + */ + case STATUS_DOWNLOAD_IN_PROGRESS: + /* Don't support backup at present so don't allow setting */ + case STATUS_BACKUP_IN_PROGRESS: + result = -2; + break; + + default: + result = -1; + break; + } + } + + return (result); +} + +const char *Device_Vendor_Name(void) +{ + return Vendor_Name; +} + +/** Returns the Vendor ID for this Device. + * See the assignments at + * http://www.bacnet.org/VendorID/BACnet%20Vendor%20IDs.htm + * @return The Vendor ID of this Device. + */ +uint16_t Device_Vendor_Identifier(void) +{ + return Vendor_Identifier; +} + +void Device_Set_Vendor_Identifier(uint16_t vendor_id) +{ + Vendor_Identifier = vendor_id; +} + +const char *Device_Model_Name(void) +{ + return Model_Name; +} + +bool Device_Set_Model_Name(const char *name, size_t length) +{ + bool status = false; /*return value */ + + if (length < sizeof(Model_Name)) { + memmove(Model_Name, name, length); + Model_Name[length] = 0; + status = true; + } + + return status; +} + +const char *Device_Firmware_Revision(void) +{ + return BACnet_Version; +} + +const char *Device_Application_Software_Version(void) +{ + return Application_Software_Version; +} + +bool Device_Set_Application_Software_Version(const char *name, size_t length) +{ + bool status = false; /*return value */ + + if (length < sizeof(Application_Software_Version)) { + memmove(Application_Software_Version, name, length); + Application_Software_Version[length] = 0; + status = true; + } + + return status; +} + +const char *Device_Description(void) +{ + return Description; +} + +bool Device_Set_Description(const char *name, size_t length) +{ + bool status = false; /*return value */ + + if (length < sizeof(Description)) { + memmove(Description, name, length); + Description[length] = 0; + status = true; + } + + return status; +} + +const char *Device_Location(void) +{ + return Location; +} + +bool Device_Set_Location(const char *name, size_t length) +{ + bool status = false; /*return value */ + + if (length < sizeof(Location)) { + memmove(Location, name, length); + Location[length] = 0; + status = true; + } + + return status; +} + +uint8_t Device_Protocol_Version(void) +{ + return BACNET_PROTOCOL_VERSION; +} + +uint8_t Device_Protocol_Revision(void) +{ + return BACNET_PROTOCOL_REVISION; +} + +BACNET_SEGMENTATION Device_Segmentation_Supported(void) +{ + return SEGMENTATION_NONE; +} + +uint32_t Device_Database_Revision(void) +{ + return Database_Revision; +} + +void Device_Set_Database_Revision(uint32_t revision) +{ + Database_Revision = revision; +} + +/* + * Shortcut for incrementing database revision as this is potentially + * the most common operation if changing object names and ids is + * implemented. + */ +void Device_Inc_Database_Revision(void) +{ + Database_Revision++; +} + +/** Get the total count of objects supported by this Device Object. + * @note Since many network clients depend on the object list + * for discovery, it must be consistent! + * @return The count of objects, for all supported Object types. + */ +unsigned Device_Object_List_Count(void) +{ + unsigned count = 0; /* number of objects */ + struct object_functions *pObject = NULL; + + /* initialize the default return values */ + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count) { + count += pObject->Object_Count(); + } + pObject++; + } + + return count; +} + +/** Lookup the Object at the given array index in the Device's Object List. + * Even though we don't keep a single linear array of objects in the Device, + * this method acts as though we do and works through a virtual, concatenated + * array of all of our object type arrays. + * + * @param array_index [in] The desired array index (1 to N) + * @param object_type [out] The object's type, if found. + * @param instance [out] The object's instance number, if found. + * @return True if found, else false. + */ +bool Device_Object_List_Identifier( + uint32_t array_index, BACNET_OBJECT_TYPE *object_type, uint32_t *instance) +{ + bool status = false; + uint32_t count = 0; + uint32_t object_index = 0; + uint32_t temp_index = 0; + struct object_functions *pObject = NULL; + + /* array index zero is length - so invalid */ + if (array_index == 0) { + return status; + } + object_index = array_index - 1; + /* initialize the default return values */ + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count) { + object_index -= count; + count = pObject->Object_Count(); + if (object_index < count) { + /* Use the iterator function if available otherwise + * look for the index to instance to get the ID */ + if (pObject->Object_Iterator) { + /* First find the first object */ + temp_index = pObject->Object_Iterator(~(unsigned)0); + /* Then step through the objects to find the nth */ + while (object_index != 0) { + temp_index = pObject->Object_Iterator(temp_index); + object_index--; + } + /* set the object_index up before falling through to next + * bit */ + object_index = temp_index; + } + if (pObject->Object_Index_To_Instance) { + *object_type = pObject->Object_Type; + *instance = pObject->Object_Index_To_Instance(object_index); + status = true; + break; + } + } + } + pObject++; + } + + return status; +} + +/** Determine if we have an object with the given object_name. + * If the object_type and object_instance pointers are not null, + * and the lookup succeeds, they will be given the resulting values. + * @param object_name [in] The desired Object Name to look for. + * @param object_type [out] The BACNET_OBJECT_TYPE of the matching Object. + * @param object_instance [out] The object instance number of the matching + * Object. + * @return True on success or else False if not found. + */ +bool Device_Valid_Object_Name(BACNET_CHARACTER_STRING *object_name1, + BACNET_OBJECT_TYPE *object_type, + uint32_t *object_instance) +{ + bool found = false; + BACNET_OBJECT_TYPE type = OBJECT_NONE; + uint32_t instance; + uint32_t max_objects = 0, i = 0; + bool check_id = false; + BACNET_CHARACTER_STRING object_name2; + struct object_functions *pObject = NULL; + + max_objects = Device_Object_List_Count(); + for (i = 1; i <= max_objects; i++) { + check_id = Device_Object_List_Identifier(i, &type, &instance); + if (check_id) { + pObject = Device_Objects_Find_Functions(type); + if ((pObject != NULL) && (pObject->Object_Name != NULL) && + (pObject->Object_Name(instance, &object_name2) && + characterstring_same(object_name1, &object_name2))) { + found = true; + if (object_type) { + *object_type = type; + } + if (object_instance) { + *object_instance = instance; + } + break; + } + } + } + + return found; +} + +/** Determine if we have an object of this type and instance number. + * @param object_type [in] The desired BACNET_OBJECT_TYPE + * @param object_instance [in] The object instance number to be looked up. + * @return True if found, else False if no such Object in this device. + */ +bool Device_Valid_Object_Id + (BACNET_OBJECT_TYPE object_type, uint32_t object_instance) +{ + bool status = false; /* return value */ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if ((pObject != NULL) && (pObject->Object_Valid_Instance != NULL)) { + status = pObject->Object_Valid_Instance(object_instance); + } + + return status; +} + +/** Copy a child object's object_name value, given its ID. + * @param object_type [in] The BACNET_OBJECT_TYPE of the child Object. + * @param object_instance [in] The object instance number of the child Object. + * @param object_name [out] The Object Name found for this child Object. + * @return True on success or else False if not found. + */ +bool Device_Object_Name_Copy(BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_CHARACTER_STRING *object_name) +{ + struct object_functions *pObject = NULL; + bool found = false; + + pObject = Device_Objects_Find_Functions(object_type); + if ((pObject != NULL) && (pObject->Object_Name != NULL)) { + found = pObject->Object_Name(object_instance, object_name); + } + + return found; +} + +static void Update_Current_Time(void) +{ + datetime_local( + &Local_Date, &Local_Time, &UTC_Offset, &Daylight_Savings_Status); +} + +void Device_getCurrentDateTime(BACNET_DATE_TIME *DateTime) +{ + Update_Current_Time(); + + DateTime->date = Local_Date; + DateTime->time = Local_Time; +} + +int32_t Device_UTC_Offset(void) +{ + Update_Current_Time(); + + return UTC_Offset; +} + +void Device_UTC_Offset_Set(int16_t offset) +{ + UTC_Offset = offset; +} + +bool Device_Daylight_Savings_Status(void) +{ + return Daylight_Savings_Status; +} + +#if defined(BACNET_TIME_MASTER) +/** + * Sets the time sync interval in minutes + * + * @param flag + * This property, of type BOOLEAN, specifies whether (TRUE) + * or not (FALSE) clock-aligned periodic time synchronization is + * enabled. If periodic time synchronization is enabled and the + * time synchronization interval is a factor of (divides without + * remainder) an hour or day, then the beginning of the period + * specified for time synchronization shall be aligned to the hour or + * day, respectively. If this property is present, it shall be writable. + */ +bool Device_Align_Intervals_Set(bool flag) +{ + Align_Intervals = flag; + + return true; +} + +bool Device_Align_Intervals(void) +{ + return Align_Intervals; +} + +/** + * Sets the time sync interval in minutes + * + * @param minutes + * This property, of type Unsigned, specifies the periodic + * interval in minutes at which TimeSynchronization and + * UTCTimeSynchronization requests shall be sent. If this + * property has a value of zero, then periodic time synchronization is + * disabled. If this property is present, it shall be writable. + */ +bool Device_Time_Sync_Interval_Set(uint32_t minutes) +{ + Interval_Minutes = minutes; + + return true; +} + +uint32_t Device_Time_Sync_Interval(void) +{ + return Interval_Minutes; +} + +/** + * Sets the time sync interval offset value. + * + * @param minutes + * This property, of type Unsigned, specifies the offset in + * minutes from the beginning of the period specified for time + * synchronization until the actual time synchronization requests + * are sent. The offset used shall be the value of Interval_Offset + * modulo the value of Time_Synchronization_Interval; + * e.g., if Interval_Offset has the value 31 and + * Time_Synchronization_Interval is 30, the offset used shall be 1. + * Interval_Offset shall have no effect if Align_Intervals is + * FALSE. If this property is present, it shall be writable. + */ +bool Device_Interval_Offset_Set(uint32_t minutes) +{ + Interval_Offset_Minutes = minutes; + + return true; +} + +uint32_t Device_Interval_Offset(void) +{ + return Interval_Offset_Minutes; +} +#endif + +/* return the length of the apdu encoded or BACNET_STATUS_ERROR for error or + BACNET_STATUS_ABORT for abort message */ +int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = 0; /* return value */ + int len = 0; /* apdu len intermediate value */ + BACNET_BIT_STRING bit_string = { 0 }; + BACNET_CHARACTER_STRING char_string = { 0 }; + uint32_t i = 0; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t instance = 0; + uint32_t count = 0; + uint8_t *apdu = NULL; + struct object_functions *pObject = NULL; + bool found = false; + uint16_t apdu_max = 0; + + if ((rpdata == NULL) || (rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + apdu = rpdata->application_data; + apdu_max = rpdata->application_data_len; + switch (rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_application_object_id( + &apdu[0], OBJECT_DEVICE, Object_Instance_Number); + break; + case PROP_OBJECT_NAME: + apdu_len = + encode_application_character_string(&apdu[0], &My_Object_Name); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_application_enumerated(&apdu[0], OBJECT_DEVICE); + break; + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, Description); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_SYSTEM_STATUS: + apdu_len = encode_application_enumerated(&apdu[0], System_Status); + break; + case PROP_VENDOR_NAME: + characterstring_init_ansi(&char_string, Vendor_Name); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_VENDOR_IDENTIFIER: + apdu_len = encode_application_unsigned(&apdu[0], Vendor_Identifier); + break; + case PROP_MODEL_NAME: + characterstring_init_ansi(&char_string, Model_Name); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_FIRMWARE_REVISION: + characterstring_init_ansi(&char_string, BACnet_Version); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_APPLICATION_SOFTWARE_VERSION: + characterstring_init_ansi( + &char_string, Application_Software_Version); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_LOCATION: + characterstring_init_ansi(&char_string, Location); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_LOCAL_TIME: + Update_Current_Time(); + apdu_len = encode_application_time(&apdu[0], &Local_Time); + break; + case PROP_UTC_OFFSET: + Update_Current_Time(); + apdu_len = encode_application_signed(&apdu[0], UTC_Offset); + break; + case PROP_LOCAL_DATE: + Update_Current_Time(); + apdu_len = encode_application_date(&apdu[0], &Local_Date); + break; + case PROP_DAYLIGHT_SAVINGS_STATUS: + Update_Current_Time(); + apdu_len = + encode_application_boolean(&apdu[0], Daylight_Savings_Status); + break; + case PROP_PROTOCOL_VERSION: + apdu_len = encode_application_unsigned( + &apdu[0], Device_Protocol_Version()); + break; + case PROP_PROTOCOL_REVISION: + apdu_len = encode_application_unsigned( + &apdu[0], Device_Protocol_Revision()); + break; + case PROP_PROTOCOL_SERVICES_SUPPORTED: + /* Note: list of services that are executed, not initiated. */ + bitstring_init(&bit_string); + for (i = 0; i < MAX_BACNET_SERVICES_SUPPORTED; i++) { + /* automatic lookup based on handlers set */ + bitstring_set_bit(&bit_string, (uint8_t)i, + apdu_service_supported((BACNET_SERVICES_SUPPORTED)i)); + } + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED: + /* Note: this is the list of objects that can be in this device, + not a list of objects that this device can access */ + bitstring_init(&bit_string); + for (i = 0; i < MAX_ASHRAE_OBJECT_TYPE; i++) { + /* initialize all the object types to not-supported */ + bitstring_set_bit(&bit_string, (uint8_t)i, false); + } + /* set the object types with objects to supported */ + + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if ((pObject->Object_Count) && (pObject->Object_Count() > 0)) { + bitstring_set_bit( + &bit_string, (uint8_t)pObject->Object_Type, true); + } + pObject++; + } + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + case PROP_OBJECT_LIST: + count = Device_Object_List_Count(); + /* Array element zero is the number of objects in the list */ + if (rpdata->array_index == 0) { + apdu_len = encode_application_unsigned(&apdu[0], count); + /* if no index was specified, then try to encode the entire list + */ + /* into one packet. Note that more than likely you will have */ + /* to return an error if the number of encoded objects exceeds + */ + /* your maximum APDU size. */ + } else if (rpdata->array_index == BACNET_ARRAY_ALL) { + for (i = 1; i <= count; i++) { + found = Device_Object_List_Identifier( + i, &object_type, &instance); + if (found) { + len = encode_application_object_id( + &apdu[apdu_len], object_type, instance); + apdu_len += len; + /* assume next one is the same size as this one */ + /* can we all fit into the APDU? Don't check for last + * entry */ + if ((i != count) && (apdu_len + len) >= apdu_max) { + /* Abort response */ + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + apdu_len = BACNET_STATUS_ABORT; + break; + } + } else { + /* error: internal error? */ + rpdata->error_class = ERROR_CLASS_SERVICES; + rpdata->error_code = ERROR_CODE_OTHER; + apdu_len = BACNET_STATUS_ERROR; + break; + } + } + } else { + found = Device_Object_List_Identifier( + rpdata->array_index, &object_type, &instance); + if (found) { + apdu_len = encode_application_object_id( + &apdu[0], object_type, instance); + } else { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = BACNET_STATUS_ERROR; + } + } + break; + case PROP_MAX_APDU_LENGTH_ACCEPTED: + apdu_len = encode_application_unsigned(&apdu[0], MAX_APDU); + break; + case PROP_SEGMENTATION_SUPPORTED: + apdu_len = encode_application_enumerated( + &apdu[0], Device_Segmentation_Supported()); + break; + case PROP_APDU_TIMEOUT: + apdu_len = encode_application_unsigned(&apdu[0], apdu_timeout()); + break; + case PROP_NUMBER_OF_APDU_RETRIES: + apdu_len = encode_application_unsigned(&apdu[0], apdu_retries()); + break; + case PROP_DEVICE_ADDRESS_BINDING: + apdu_len = address_list_encode(&apdu[0], apdu_max); + break; + case PROP_DATABASE_REVISION: + apdu_len = encode_application_unsigned(&apdu[0], Database_Revision); + break; +#if defined(BACDL_MSTP) + case PROP_MAX_INFO_FRAMES: + apdu_len = + encode_application_unsigned(&apdu[0], dlmstp_max_info_frames()); + break; + case PROP_MAX_MASTER: + apdu_len = + encode_application_unsigned(&apdu[0], dlmstp_max_master()); + break; +#endif +#if defined(BACNET_TIME_MASTER) + case PROP_TIME_SYNCHRONIZATION_RECIPIENTS: + apdu_len = handler_timesync_encode_recipients(&apdu[0], MAX_APDU); + if (apdu_len < 0) { + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + apdu_len = BACNET_STATUS_ABORT; + } + break; + case PROP_TIME_SYNCHRONIZATION_INTERVAL: + apdu_len = encode_application_unsigned( + &apdu[0], Device_Time_Sync_Interval()); + break; + case PROP_ALIGN_INTERVALS: + apdu_len = + encode_application_boolean(&apdu[0], Device_Align_Intervals()); + break; + case PROP_INTERVAL_OFFSET: + apdu_len = + encode_application_unsigned(&apdu[0], Device_Interval_Offset()); + break; +#endif + case PROP_ACTIVE_COV_SUBSCRIPTIONS: + apdu_len = handler_cov_encode_subscriptions(&apdu[0], apdu_max); + break; + default: + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = BACNET_STATUS_ERROR; + break; + } + /* only array properties can have array options */ + if ((apdu_len >= 0) && (rpdata->object_property != PROP_OBJECT_LIST) && + (rpdata->array_index != BACNET_ARRAY_ALL)) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** Looks up the requested Object and Property, and encodes its Value in an + * APDU. + * @ingroup ObjIntf + * If the Object or Property can't be found, sets the error class and code. + * + * @param rpdata [in,out] Structure with the desired Object and Property info + * on entry, and APDU message on return. + * @return The length of the APDU on success, else BACNET_STATUS_ERROR + */ +int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = BACNET_STATUS_ERROR; + struct object_functions *pObject = NULL; +#if (BACNET_PROTOCOL_REVISION >= 14) + struct special_property_list_t property_list; +#endif + + /* initialize the default return values */ + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; + pObject = Device_Objects_Find_Functions(rpdata->object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(rpdata->object_instance)) { + if (pObject->Object_Read_Property) { +#if (BACNET_PROTOCOL_REVISION >= 14) + if ((int)rpdata->object_property == PROP_PROPERTY_LIST) { + Device_Objects_Property_List(rpdata->object_type, + rpdata->object_instance, &property_list); + apdu_len = property_list_encode(rpdata, + property_list.Required.pList, + property_list.Optional.pList, + property_list.Proprietary.pList); + } else +#endif + { + apdu_len = pObject->Object_Read_Property(rpdata); + } + } + } + } + + return apdu_len; +} + +/* returns true if successful */ +bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t object_instance = 0; + int result = 0; +#if defined(BACNET_TIME_MASTER) + uint32_t minutes = 0; +#endif + + /* decode the some of the request */ + len = bacapp_decode_application_data( + wp_data->application_data, wp_data->application_data_len, &value); + if (len < 0) { + /* error while decoding - a value larger than we can handle */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + return false; + } + if ((wp_data->object_property != PROP_OBJECT_LIST) && + (wp_data->array_index != BACNET_ARRAY_ALL)) { + /* only array properties can have array options */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + return false; + } + /* FIXME: len < application_data_len: more data? */ + switch (wp_data->object_property) { + case PROP_OBJECT_IDENTIFIER: + status = WPValidateArgType(&value, BACNET_APPLICATION_TAG_OBJECT_ID, + &wp_data->error_class, &wp_data->error_code); + if (status) { + if ((value.type.Object_Id.type == OBJECT_DEVICE) && + (Device_Set_Object_Instance_Number( + value.type.Object_Id.instance))) { + /* FIXME: we could send an I-Am broadcast to let the world + * know */ + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_NUMBER_OF_APDU_RETRIES: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT, + &wp_data->error_class, &wp_data->error_code); + if (status) { + /* FIXME: bounds check? */ + apdu_retries_set((uint8_t)value.type.Unsigned_Int); + } + break; + case PROP_APDU_TIMEOUT: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT, + &wp_data->error_class, &wp_data->error_code); + if (status) { + /* FIXME: bounds check? */ + apdu_timeout_set((uint16_t)value.type.Unsigned_Int); + } + break; + case PROP_VENDOR_IDENTIFIER: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT, + &wp_data->error_class, &wp_data->error_code); + if (status) { + /* FIXME: bounds check? */ + Device_Set_Vendor_Identifier((uint16_t)value.type.Unsigned_Int); + } + break; + case PROP_SYSTEM_STATUS: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED, + &wp_data->error_class, &wp_data->error_code); + if (status) { + result = Device_Set_System_Status( + (BACNET_DEVICE_STATUS)value.type.Enumerated, false); + if (result != 0) { + /* result: - 0 = ok, -1 = bad value, -2 = not allowed */ + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + if (result == -1) { + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } else { + wp_data->error_code = + ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED; + } + } + } + break; + case PROP_OBJECT_NAME: + status = WPValidateString(&value, + characterstring_capacity(&My_Object_Name), false, + &wp_data->error_class, &wp_data->error_code); + if (status) { + /* All the object names in a device must be unique */ + if (Device_Valid_Object_Name(&value.type.Character_String, + &object_type, &object_instance)) { + if ((object_type == wp_data->object_type) && + (object_instance == wp_data->object_instance)) { + /* writing same name to same object */ + status = true; + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_DUPLICATE_NAME; + } + } else { + Device_Set_Object_Name(&value.type.Character_String); + } + } + break; + case PROP_LOCATION: + status = WPValidateString(&value, MAX_DEV_LOC_LEN, true, + &wp_data->error_class, &wp_data->error_code); + if (status) { + Device_Set_Location( + characterstring_value(&value.type.Character_String), + characterstring_length(&value.type.Character_String)); + } + break; + + case PROP_DESCRIPTION: + status = WPValidateString(&value, MAX_DEV_DESC_LEN, true, + &wp_data->error_class, &wp_data->error_code); + if (status) { + Device_Set_Description( + characterstring_value(&value.type.Character_String), + characterstring_length(&value.type.Character_String)); + } + break; + case PROP_MODEL_NAME: + status = WPValidateString(&value, MAX_DEV_MOD_LEN, true, + &wp_data->error_class, &wp_data->error_code); + if (status) { + Device_Set_Model_Name( + characterstring_value(&value.type.Character_String), + characterstring_length(&value.type.Character_String)); + } + break; +#if defined(BACNET_TIME_MASTER) + case PROP_TIME_SYNCHRONIZATION_INTERVAL: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if (value.type.Unsigned_Int < 65535) { + minutes = value.type.Unsigned_Int; + Device_Time_Sync_Interval_Set(minutes); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_ALIGN_INTERVALS: + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + Device_Align_Intervals_Set(value.type.Boolean); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_INTERVAL_OFFSET: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if (value.type.Unsigned_Int < 65535) { + minutes = value.type.Unsigned_Int; + Device_Interval_Offset_Set(minutes); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; +#else + case PROP_TIME_SYNCHRONIZATION_INTERVAL: + case PROP_ALIGN_INTERVALS: + case PROP_INTERVAL_OFFSET: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + break; +#endif + case PROP_UTC_OFFSET: + if (value.tag == BACNET_APPLICATION_TAG_SIGNED_INT) { + if ((value.type.Signed_Int < (12 * 60)) && + (value.type.Signed_Int > (-12 * 60))) { + Device_UTC_Offset_Set(value.type.Signed_Int); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; +#if defined(BACDL_MSTP) + case PROP_MAX_INFO_FRAMES: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT, + &wp_data->error_class, &wp_data->error_code); + if (status) { + if (value.type.Unsigned_Int <= 255) { + dlmstp_set_max_info_frames( + (uint8_t)value.type.Unsigned_Int); + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_MAX_MASTER: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT, + &wp_data->error_class, &wp_data->error_code); + if (status) { + if ((value.type.Unsigned_Int > 0) && + (value.type.Unsigned_Int <= 127)) { + dlmstp_set_max_master((uint8_t)value.type.Unsigned_Int); + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; +#else + case PROP_MAX_INFO_FRAMES: + case PROP_MAX_MASTER: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + break; +#endif + case PROP_OBJECT_TYPE: + case PROP_VENDOR_NAME: + case PROP_FIRMWARE_REVISION: + case PROP_APPLICATION_SOFTWARE_VERSION: + case PROP_LOCAL_TIME: + case PROP_LOCAL_DATE: + case PROP_DAYLIGHT_SAVINGS_STATUS: + case PROP_PROTOCOL_VERSION: + case PROP_PROTOCOL_REVISION: + case PROP_PROTOCOL_SERVICES_SUPPORTED: + case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED: + case PROP_OBJECT_LIST: + case PROP_MAX_APDU_LENGTH_ACCEPTED: + case PROP_SEGMENTATION_SUPPORTED: + case PROP_DEVICE_ADDRESS_BINDING: + case PROP_DATABASE_REVISION: + case PROP_ACTIVE_COV_SUBSCRIPTIONS: +#if defined(BACNET_TIME_MASTER) + case PROP_TIME_SYNCHRONIZATION_RECIPIENTS: +#endif + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + default: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + break; + } + + return status; +} + +/** Looks up the requested Object and Property, and set the new Value in it, + * if allowed. + * If the Object or Property can't be found, sets the error class and code. + * @ingroup ObjIntf + * + * @param wp_data [in,out] Structure with the desired Object and Property info + * and new Value on entry, and APDU message on return. + * @return True on success, else False if there is an error. + */ +bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + bool status = false; /* Ever the pessamist! */ + struct object_functions *pObject = NULL; + + /* initialize the default return values */ + wp_data->error_class = ERROR_CLASS_OBJECT; + wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + pObject = Device_Objects_Find_Functions(wp_data->object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(wp_data->object_instance)) { + if (pObject->Object_Write_Property) { +#if (BACNET_PROTOCOL_REVISION >= 14) + if (wp_data->object_property == PROP_PROPERTY_LIST) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else +#endif + { + status = pObject->Object_Write_Property(wp_data); + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } + } else { + wp_data->error_class = ERROR_CLASS_OBJECT; + wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + wp_data->error_class = ERROR_CLASS_OBJECT; + wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return (status); +} + +/** Looks up the requested Object, and fills the Property Value list. + * If the Object or Property can't be found, returns false. + * @ingroup ObjHelpers + * @param [in] The object type to be looked up. + * @param [in] The object instance number to be looked up. + * @param [out] The value list + * @return True if the object instance supports this feature and value changed. + */ +bool Device_Encode_Value_List(BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_VALUE *value_list) +{ + bool status = false; /* Ever the pessamist! */ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(object_instance)) { + if (pObject->Object_Value_List) { + status = + pObject->Object_Value_List(object_instance, value_list); + } + } + } + + return (status); +} + +/** Checks the COV flag in the requested Object + * @ingroup ObjHelpers + * @param [in] The object type to be looked up. + * @param [in] The object instance to be looked up. + * @return True if the COV flag is set + */ +bool Device_COV(BACNET_OBJECT_TYPE object_type, uint32_t object_instance) +{ + bool status = false; /* Ever the pessamist! */ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(object_instance)) { + if (pObject->Object_COV) { + status = pObject->Object_COV(object_instance); + } + } + } + + return (status); +} + +/** Clears the COV flag in the requested Object + * @ingroup ObjHelpers + * @param [in] The object type to be looked up. + * @param [in] The object instance to be looked up. + */ +void Device_COV_Clear(BACNET_OBJECT_TYPE object_type, uint32_t object_instance) +{ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(object_instance)) { + if (pObject->Object_COV_Clear) { + pObject->Object_COV_Clear(object_instance); + } + } + } +} + +#if defined(INTRINSIC_REPORTING) +void Device_local_reporting(void) +{ + struct object_functions *pObject = NULL; + uint32_t objects_count = 0; + uint32_t object_instance = 0; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t idx = 0; + + objects_count = Device_Object_List_Count(); + + /* loop for all objects */ + for (idx = 1; idx <= objects_count; idx++) { + Device_Object_List_Identifier(idx, &object_type, &object_instance); + + pObject = Device_Objects_Find_Functions(object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(object_instance)) { + if (pObject->Object_Intrinsic_Reporting) { + pObject->Object_Intrinsic_Reporting(object_instance); + } + } + } + } +} +#endif + +/** Looks up the requested Object to see if the functionality is supported. + * @ingroup ObjHelpers + * @param [in] The object type to be looked up. + * @return True if the object instance supports this feature. + */ +bool Device_Value_List_Supported(BACNET_OBJECT_TYPE object_type) +{ + bool status = false; /* Ever the pessamist! */ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if (pObject != NULL) { + if (pObject->Object_Value_List) { + status = true; + } + } + + return (status); +} + +/** Initialize the Device Object. + Initialize the group of object helper functions for any supported Object. + Initialize each of the Device Object child Object instances. + * @ingroup ObjIntf + * @param object_table [in,out] array of structure with object functions. + * Each Child Object must provide some implementation of each of these + * functions in order to properly support the default handlers. + */ +void Device_Init(object_functions_t *object_table) +{ + struct object_functions *pObject = NULL; +#if defined(BAC_UCI) + const char *uciname; + struct uci_context *ctx; + fprintf(stderr, "Device_Init\n"); + ctx = ucix_init("bacnet_dev"); + if (!ctx) + fprintf(stderr, "Failed to load config file bacnet_dev\n"); + uciname = ucix_get_option(ctx, "bacnet_dev", "0", "Name"); + if (uciname != 0) { + characterstring_init_ansi(&My_Object_Name, uciname); + } else { +#endif /* defined(BAC_UCI) */ + characterstring_init_ansi(&My_Object_Name, "SimpleServer"); +#if defined(BAC_UCI) + } + ucix_cleanup(ctx); +#endif /* defined(BAC_UCI) */ + datetime_init(); + if (object_table) { + Object_Table = object_table; + } else { + Object_Table = &My_Object_Table[0]; + } + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Init) { + pObject->Object_Init(); + } + pObject++; + } +} + +bool DeviceGetRRInfo(BACNET_READ_RANGE_DATA *pRequest, /* Info on the request */ + RR_PROP_INFO *pInfo) +{ /* Where to put the response */ + bool status = false; /* return value */ + + switch (pRequest->object_property) { + case PROP_VT_CLASSES_SUPPORTED: + case PROP_ACTIVE_VT_SESSIONS: + case PROP_LIST_OF_SESSION_KEYS: + case PROP_TIME_SYNCHRONIZATION_RECIPIENTS: + case PROP_MANUAL_SLAVE_ADDRESS_BINDING: + case PROP_SLAVE_ADDRESS_BINDING: + case PROP_RESTART_NOTIFICATION_RECIPIENTS: + case PROP_UTC_TIME_SYNCHRONIZATION_RECIPIENTS: + pInfo->RequestTypes = RR_BY_POSITION; + pRequest->error_class = ERROR_CLASS_PROPERTY; + pRequest->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + break; + + case PROP_DEVICE_ADDRESS_BINDING: + pInfo->RequestTypes = RR_BY_POSITION; + pInfo->Handler = rr_address_list_encode; + status = true; + break; + + case PROP_ACTIVE_COV_SUBSCRIPTIONS: + pInfo->RequestTypes = RR_BY_POSITION; + pRequest->error_class = ERROR_CLASS_PROPERTY; + pRequest->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + break; + default: + pRequest->error_class = ERROR_CLASS_SERVICES; + pRequest->error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST; + break; + } + + return status; +} + +#ifdef BAC_ROUTING +/**************************************************************************** + ************* BACnet Routing Functionality (Optional) ********************** + **************************************************************************** + * The supporting functions are located in gw_device.c, except for those + * that need access to local data in this file. + ****************************************************************************/ + +/** Initialize the first of our array of Devices with the main Device's + * information, and then swap out some of the Device object functions and + * replace with ones appropriate for routing. + * @ingroup ObjIntf + * @param first_object_instance Set the first (gateway) Device to this + instance number. + */ +void Routing_Device_Init(uint32_t first_object_instance) +{ + struct object_functions *pDevObject = NULL; + + /* Initialize with our preset strings */ + Add_Routed_Device(first_object_instance, &My_Object_Name, Description); + + /* Now substitute our routed versions of the main object functions. */ + pDevObject = Object_Table; + pDevObject->Object_Index_To_Instance = Routed_Device_Index_To_Instance; + pDevObject->Object_Valid_Instance = + Routed_Device_Valid_Object_Instance_Number; + pDevObject->Object_Name = Routed_Device_Name; + pDevObject->Object_Read_Property = Routed_Device_Read_Property_Local; + pDevObject->Object_Write_Property = Routed_Device_Write_Property_Local; +} + +#endif /* BAC_ROUTING */ diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index 0b313826..9c65433f 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -14,6 +14,7 @@ #include #include +#define LOG_LEVEL CONFIG_BACNETSTACK_LOG_LEVEL #include LOG_MODULE_REGISTER(bacnet); diff --git a/ports/zephyr/mstimer-init.c b/ports/zephyr/mstimer-init.c new file mode 100644 index 00000000..460b4be5 --- /dev/null +++ b/ports/zephyr/mstimer-init.c @@ -0,0 +1,45 @@ +/************************************************************************** +* +* Copyright (C) 2009 Steve Karg +* Multimedia Timer contribution by Cameron Crothers, 2008 +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*********************************************************************/ +#include +#include "bacnet/basic/sys/mstimer.h" + +/** +* @brief returns the current millisecond count +* @return millisecond counter +*/ +unsigned long mstimer_now(void) +{ + return (unsigned long) k_uptime_get(); +} + +/** +* @brief Initialization for timer +*/ +void mstimer_init(void) +{ + +} + diff --git a/src/bacnet/bacdef.h b/src/bacnet/bacdef.h index bc355e9b..e461a5bd 100644 --- a/src/bacnet/bacdef.h +++ b/src/bacnet/bacdef.h @@ -138,7 +138,13 @@ typedef uint32_t BACNET_ARRAY_INDEX; equal to 7 bytes. The IPv6 addresses are planned to be handled outside this area. */ /* FIXME: mac[] only needs to be as big as our local datalink MAC */ -#define MAX_MAC_LEN 7 + +#if BACDL_BIP6 + #define MAX_MAC_LEN 19 +#else + #define MAX_MAC_LEN 7 +#endif + struct BACnet_Device_Address { /* mac_len = 0 is a broadcast address */ uint8_t mac_len; diff --git a/src/bacnet/basic/bbmd/h_bbmd.c b/src/bacnet/basic/bbmd/h_bbmd.c index fd042b1b..d79a041e 100644 --- a/src/bacnet/basic/bbmd/h_bbmd.c +++ b/src/bacnet/basic/bbmd/h_bbmd.c @@ -167,7 +167,8 @@ static void debug_print_string(const char *str) * BBMD_BACKUP_FILE should be set to the file name * in which to store the BDT. */ -#ifndef BBMD_BACKUP_FILE +#if defined(BBMD_BACKUP_FILE) && (BBMD_BACKUP_FILE == 1) +#undef BBMD_BACKUP_FILE #define BBMD_BACKUP_FILE BACnet_BDT_table #endif #if defined(BBMD_BACKUP_FILE) diff --git a/test/bacnet/device/CMakeLists.txt b/test/bacnet/device/CMakeLists.txt new file mode 100644 index 00000000..31f0c38c --- /dev/null +++ b/test/bacnet/device/CMakeLists.txt @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(test_${basename} + VERSION 1.0.0 + LANGUAGES C) + + +set(TST_DIR "../..") +set(ZTST_DIR "${TST_DIR}/ztest/src") +set(SRC_DIR "${TST_DIR}/../src") + +add_compile_definitions( + BIG_ENDIAN=0 + CONFIG_ZTEST=1 + ) + +include_directories( + ${SRC_DIR} + ${TST_DIR}/ztest/include + ) + +add_executable(${PROJECT_NAME} + ${SRC_DIR}/device.c + ./src/main.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) diff --git a/test/bacnet/device/src/main.c b/test/bacnet/device/src/main.c new file mode 100644 index 00000000..788df870 --- /dev/null +++ b/test/bacnet/device/src/main.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020 Legrand North America, LLC. + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include +#include +#include /* for memmove */ +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacapp.h" +#include "bacnet/config.h" /* the custom stuff */ +#include "bacnet/datetime.h" +#include "bacnet/apdu.h" +#include "bacnet/wp.h" /* WriteProperty handling */ +#include "bacnet/rp.h" /* ReadProperty handling */ +#include "bacnet/dcc.h" /* DeviceCommunicationControl handling */ +#include "bacnet/version.h" +#include "bacnet/basic/object/device.h" /* me */ +#include "bacnet/basic/services.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/basic/binding/address.h" +/* include the device object */ +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/object/acc.h" +#include "bacnet/basic/object/ai.h" +#include "bacnet/basic/object/ao.h" +#include "bacnet/basic/object/av.h" +#include "bacnet/basic/object/bi.h" +#include "bacnet/basic/object/bo.h" +#include "bacnet/basic/object/bv.h" +#include "bacnet/basic/object/channel.h" +#include "bacnet/basic/object/command.h" +#include "bacnet/basic/object/csv.h" +#include "bacnet/basic/object/iv.h" +#include "bacnet/basic/object/lc.h" +#include "bacnet/basic/object/lsp.h" +#include "bacnet/basic/object/ms-input.h" +#include "bacnet/basic/object/mso.h" +#include "bacnet/basic/object/msv.h" + + +static void testDevice(void) +{ + const char *dev_name = "Patricia"; + + zassert_true(Device_Set_Object_Instance_Number(0), + "Device_Set_Object_Instance_Number(0) failed"); + zassert_true(Device_Object_Instance_Number()==0, + "Failed to set device object instance number to 0."); + + zassert_true(Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE), + "Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE) failed"); + zassert_true(Device_Object_Instance_Number()==BACNET_MAX_INSTANCE, + "Failed to set device object instance number to BACNET_MAX_INSTANCE"); + + zassert_true(Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE / 2), + "Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE / 2) failed"); + zassert_true(Device_Object_Instance_Number()==(BACNET_MAX_INSTANCE / 2), + "Failed to set device object instance number to BACNET_MAX_INSTANCE / 2"); + + zassert_false(Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE + 1), + "Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE + 1) uncaught"); + zassert_false(Device_Object_Instance_Number()==(BACNET_MAX_INSTANCE + 1), + "Set device object instance number to illegal value BACNET_MAX_INSTANCE + 1"); + + zassert_false(Device_Set_System_Status(STATUS_NON_OPERATIONAL, true), + "Device_Set_System_Status() failed"); + zassert_true(Device_System_Status() == STATUS_NON_OPERATIONAL, + "Failed to set device status to STATUS_NON_OPERATIONAL"); + + zassert_true(Device_Vendor_Identifier() == BACNET_VENDOR_ID, + "Incorrect BACNET_VENDOR_ID"); + + zassert_true(Device_Set_Model_Name(dev_name, strlen(dev_name)), + "Device_Set_Model_Name() failed"); + zassert_false( strcmp(Device_Model_Name(), dev_name) , + "Failed to set device model name"); +} + +void test_main(void) +{ + ztest_test_suite(device_tests, + ztest_unit_test(testDevice) + ); + + ztest_run_test_suite(device_tests); +} diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index cd9326bf..fadae1a3 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -83,8 +83,8 @@ set(BACNETSTACK_SRCS ${BACNETSTACK_SRC}/bacnet/bactext.h ${BACNETSTACK_SRC}/bacnet/bactimevalue.c ${BACNETSTACK_SRC}/bacnet/bactimevalue.h - $<$:${BACNETSTACK_SRC}/bacnet/basic/bbmd6/h_bbmd.c> - $<$:${BACNETSTACK_SRC}/bacnet/basic/bbmd6/h_bbmd.h> + $<$:${BACNETSTACK_SRC}/bacnet/basic/bbmd/h_bbmd.c> + $<$:${BACNETSTACK_SRC}/bacnet/basic/bbmd/h_bbmd.h> $<$:${BACNETSTACK_SRC}/bacnet/basic/bbmd6/h_bbmd6.c> $<$:${BACNETSTACK_SRC}/bacnet/basic/bbmd6/h_bbmd6.h> $<$:${BACNETSTACK_SRC}/bacnet/basic/bbmd6/vmac.c> @@ -95,105 +95,58 @@ set(BACNETSTACK_SRCS ${BACNETSTACK_SRC}/bacnet/basic/npdu/h_npdu.h ${BACNETSTACK_SRC}/bacnet/basic/npdu/h_routed_npdu.c ${BACNETSTACK_SRC}/bacnet/basic/npdu/h_routed_npdu.h - ${BACNETSTACK_SRC}/bacnet/basic/npdu/s_router.c ${BACNETSTACK_SRC}/bacnet/basic/npdu/s_router.h - ${BACNETSTACK_SRC}/bacnet/basic/object/access_credential.c ${BACNETSTACK_SRC}/bacnet/basic/object/access_credential.h - ${BACNETSTACK_SRC}/bacnet/basic/object/access_door.c ${BACNETSTACK_SRC}/bacnet/basic/object/access_door.h - ${BACNETSTACK_SRC}/bacnet/basic/object/access_point.c ${BACNETSTACK_SRC}/bacnet/basic/object/access_point.h - ${BACNETSTACK_SRC}/bacnet/basic/object/access_rights.c ${BACNETSTACK_SRC}/bacnet/basic/object/access_rights.h - ${BACNETSTACK_SRC}/bacnet/basic/object/access_user.c ${BACNETSTACK_SRC}/bacnet/basic/object/access_user.h - ${BACNETSTACK_SRC}/bacnet/basic/object/access_zone.c ${BACNETSTACK_SRC}/bacnet/basic/object/access_zone.h - ${BACNETSTACK_SRC}/bacnet/basic/object/acc.c - ${BACNETSTACK_SRC}/bacnet/basic/object/ai.c ${BACNETSTACK_SRC}/bacnet/basic/object/ai.h - ${BACNETSTACK_SRC}/bacnet/basic/object/ao.c ${BACNETSTACK_SRC}/bacnet/basic/object/ao.h - ${BACNETSTACK_SRC}/bacnet/basic/object/av.c ${BACNETSTACK_SRC}/bacnet/basic/object/av.h - #${BACNETSTACK_SRC}/bacnet/basic/object/bacfile.c # Build error: fseek not supported in Zephyr ${BACNETSTACK_SRC}/bacnet/basic/object/bacfile.h - ${BACNETSTACK_SRC}/bacnet/basic/object/bi.c ${BACNETSTACK_SRC}/bacnet/basic/object/bi.h - ${BACNETSTACK_SRC}/bacnet/basic/object/bo.c ${BACNETSTACK_SRC}/bacnet/basic/object/bo.h - ${BACNETSTACK_SRC}/bacnet/basic/object/bv.c ${BACNETSTACK_SRC}/bacnet/basic/object/bv.h - ${BACNETSTACK_SRC}/bacnet/basic/object/channel.c ${BACNETSTACK_SRC}/bacnet/basic/object/channel.h - #${BACNETSTACK_SRC}/bacnet/basic/object/client/device-client.c - ${BACNETSTACK_SRC}/bacnet/basic/object/command.c ${BACNETSTACK_SRC}/bacnet/basic/object/command.h - ${BACNETSTACK_SRC}/bacnet/basic/object/credential_data_input.c ${BACNETSTACK_SRC}/bacnet/basic/object/credential_data_input.h - ${BACNETSTACK_SRC}/bacnet/basic/object/csv.c ${BACNETSTACK_SRC}/bacnet/basic/object/csv.h - ${BACNETSTACK_SRC}/bacnet/basic/object/device.c ${BACNETSTACK_SRC}/bacnet/basic/object/device.h - $<$:${BACNETSTACK_SRC}/bacnet/basic/object/gateway/gw_device.c> - ${BACNETSTACK_SRC}/bacnet/basic/object/iv.c ${BACNETSTACK_SRC}/bacnet/basic/object/iv.h - ${BACNETSTACK_SRC}/bacnet/basic/object/lc.c ${BACNETSTACK_SRC}/bacnet/basic/object/lc.h - ${BACNETSTACK_SRC}/bacnet/basic/object/lo.c ${BACNETSTACK_SRC}/bacnet/basic/object/lo.h - ${BACNETSTACK_SRC}/bacnet/basic/object/lsp.c ${BACNETSTACK_SRC}/bacnet/basic/object/lsp.h - ${BACNETSTACK_SRC}/bacnet/basic/object/Makefile - ${BACNETSTACK_SRC}/bacnet/basic/object/ms-input.c ${BACNETSTACK_SRC}/bacnet/basic/object/ms-input.h - ${BACNETSTACK_SRC}/bacnet/basic/object/mso.c ${BACNETSTACK_SRC}/bacnet/basic/object/mso.h - ${BACNETSTACK_SRC}/bacnet/basic/object/msv.c ${BACNETSTACK_SRC}/bacnet/basic/object/msv.h - ${BACNETSTACK_SRC}/bacnet/basic/object/nc.c ${BACNETSTACK_SRC}/bacnet/basic/object/nc.h - ${BACNETSTACK_SRC}/bacnet/basic/object/netport.c ${BACNETSTACK_SRC}/bacnet/basic/object/netport.h - ${BACNETSTACK_SRC}/bacnet/basic/object/objects.c ${BACNETSTACK_SRC}/bacnet/basic/object/objects.h - ${BACNETSTACK_SRC}/bacnet/basic/object/osv.c ${BACNETSTACK_SRC}/bacnet/basic/object/osv.h - ${BACNETSTACK_SRC}/bacnet/basic/object/piv.c ${BACNETSTACK_SRC}/bacnet/basic/object/piv.h - ${BACNETSTACK_SRC}/bacnet/basic/object/schedule.c ${BACNETSTACK_SRC}/bacnet/basic/object/schedule.h - ${BACNETSTACK_SRC}/bacnet/basic/object/trendlog.c ${BACNETSTACK_SRC}/bacnet/basic/object/trendlog.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_alarm_ack.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_alarm_ack.h ${BACNETSTACK_SRC}/bacnet/basic/service/h_apdu.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_apdu.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_arf_a.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_arf_a.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_arf.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_arf.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_awf.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_awf.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_ccov.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_ccov.h ${BACNETSTACK_SRC}/bacnet/basic/service/h_cov.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_cov.h ${BACNETSTACK_SRC}/bacnet/basic/service/h_dcc.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_dcc.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_gas_a.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_gas_a.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_get_alarm_sum.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_get_alarm_sum.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_getevent_a.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_getevent_a.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_getevent.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_getevent.h ${BACNETSTACK_SRC}/bacnet/basic/service/h_iam.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_iam.h ${BACNETSTACK_SRC}/bacnet/basic/service/h_ihave.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_ihave.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_lso.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_lso.h ${BACNETSTACK_SRC}/bacnet/basic/service/h_noserv.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_noserv.h @@ -207,15 +160,10 @@ set(BACNETSTACK_SRCS ${BACNETSTACK_SRC}/bacnet/basic/service/h_rpm_a.h ${BACNETSTACK_SRC}/bacnet/basic/service/h_rpm.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_rpm.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_rr_a.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_rr_a.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_rr.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_rr.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_ts.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_ts.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_ucov.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_ucov.h - ${BACNETSTACK_SRC}/bacnet/basic/service/h_upt.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_upt.h ${BACNETSTACK_SRC}/bacnet/basic/service/h_whohas.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_whohas.h @@ -225,55 +173,34 @@ set(BACNETSTACK_SRCS ${BACNETSTACK_SRC}/bacnet/basic/service/h_wp.h ${BACNETSTACK_SRC}/bacnet/basic/service/h_wpm.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_wpm.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_abort.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_abort.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_ack_alarm.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_ack_alarm.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_arfs.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_arfs.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_awfs.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_awfs.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_cevent.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_cevent.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_cov.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_cov.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_dcc.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_dcc.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_error.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_error.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_get_alarm_sum.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_get_alarm_sum.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_get_event.c - ${BACNETSTACK_SRC}/bacnet/basic/service/s_getevent.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_get_event.h ${BACNETSTACK_SRC}/bacnet/basic/service/s_getevent.h ${BACNETSTACK_SRC}/bacnet/basic/service/s_iam.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_iam.h ${BACNETSTACK_SRC}/bacnet/basic/service/s_ihave.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_ihave.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_lso.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_lso.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_rd.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_rd.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_readrange.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_readrange.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_rp.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_rp.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_rpm.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_rpm.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_ts.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_ts.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_uevent.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_uevent.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_upt.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_upt.h ${BACNETSTACK_SRC}/bacnet/basic/service/s_whohas.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_whohas.h ${BACNETSTACK_SRC}/bacnet/basic/service/s_whois.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_whois.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_wp.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_wp.h - ${BACNETSTACK_SRC}/bacnet/basic/service/s_wpm.c ${BACNETSTACK_SRC}/bacnet/basic/service/s_wpm.h ${BACNETSTACK_SRC}/bacnet/basic/services.h ${BACNETSTACK_SRC}/bacnet/basic/sys/bigend.c @@ -316,8 +243,6 @@ set(BACNETSTACK_SRCS $<$:${BACNETSTACK_SRC}/bacnet/datalink/crc.c> ${BACNETSTACK_SRC}/bacnet/datalink/datalink.c ${BACNETSTACK_SRC}/bacnet/datalink/datalink.h - ${BACNETSTACK_SRC}/bacnet/datalink/dlenv.c - ${BACNETSTACK_SRC}/bacnet/datalink/dlenv.h ${BACNETSTACK_SRC}/bacnet/datalink/dlmstp.h ${BACNETSTACK_SRC}/bacnet/datalink/ethernet.h $<$:${BACNETSTACK_SRC}/bacnet/datalink/mstp.h> @@ -377,7 +302,86 @@ set(BACNETSTACK_SRCS ${BACNETSTACK_SRC}/bacnet/wp.c ${BACNETSTACK_SRC}/bacnet/wp.h ${BACNETSTACK_SRC}/bacnet/wpm.c - ${BACNETSTACK_SRC}/bacnet/wpm.h) + ${BACNETSTACK_SRC}/bacnet/wpm.h + ) + +set(BACNETSTACK_BASIC_SRCS + $<$:${BACNETSTACK_SRC}/bacnet/basic/bbmd6/h_bbmd6.c> + $<$:${BACNETSTACK_SRC}/bacnet/basic/bbmd6/vmac.c> + ${BACNETSTACK_SRC}/bacnet/basic/npdu/s_router.c + ${BACNETSTACK_SRC}/bacnet/basic/object/access_credential.c + ${BACNETSTACK_SRC}/bacnet/basic/object/access_door.c + ${BACNETSTACK_SRC}/bacnet/basic/object/access_point.c + ${BACNETSTACK_SRC}/bacnet/basic/object/access_rights.c + ${BACNETSTACK_SRC}/bacnet/basic/object/access_user.c + ${BACNETSTACK_SRC}/bacnet/basic/object/access_zone.c + ${BACNETSTACK_SRC}/bacnet/basic/object/acc.c + ${BACNETSTACK_SRC}/bacnet/basic/object/ai.c + ${BACNETSTACK_SRC}/bacnet/basic/object/ao.c + ${BACNETSTACK_SRC}/bacnet/basic/object/av.c + ${BACNETSTACK_SRC}/bacnet/basic/object/bacfile.c # Build error: fseek not supported in Zephyr + ${BACNETSTACK_SRC}/bacnet/basic/object/bi.c + ${BACNETSTACK_SRC}/bacnet/basic/object/bo.c + ${BACNETSTACK_SRC}/bacnet/basic/object/bv.c + ${BACNETSTACK_SRC}/bacnet/basic/object/channel.c + #${BACNETSTACK_SRC}/bacnet/basic/object/client/device-client.c + ${BACNETSTACK_SRC}/bacnet/basic/object/command.c + ${BACNETSTACK_SRC}/bacnet/basic/object/credential_data_input.c + ${BACNETSTACK_SRC}/bacnet/basic/object/csv.c + ${BACNETSTACK_SRC}/bacnet/basic/object/device.c + $<$:${BACNETSTACK_SRC}/bacnet/basic/object/gateway/gw_device.c> + ${BACNETSTACK_SRC}/bacnet/basic/object/iv.c + ${BACNETSTACK_SRC}/bacnet/basic/object/lc.c + ${BACNETSTACK_SRC}/bacnet/basic/object/lo.c + ${BACNETSTACK_SRC}/bacnet/basic/object/lsp.c + ${BACNETSTACK_SRC}/bacnet/basic/object/Makefile + ${BACNETSTACK_SRC}/bacnet/basic/object/ms-input.c + ${BACNETSTACK_SRC}/bacnet/basic/object/mso.c + ${BACNETSTACK_SRC}/bacnet/basic/object/msv.c + ${BACNETSTACK_SRC}/bacnet/basic/object/nc.c + ${BACNETSTACK_SRC}/bacnet/basic/object/netport.c + ${BACNETSTACK_SRC}/bacnet/basic/object/objects.c + ${BACNETSTACK_SRC}/bacnet/basic/object/osv.c + ${BACNETSTACK_SRC}/bacnet/basic/object/piv.c + ${BACNETSTACK_SRC}/bacnet/basic/object/schedule.c + ${BACNETSTACK_SRC}/bacnet/basic/object/trendlog.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_alarm_ack.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_arf_a.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_arf.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_awf.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_ccov.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_gas_a.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_get_alarm_sum.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_getevent_a.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_getevent.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_lso.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_rr_a.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_rr.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_ts.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_ucov.c + ${BACNETSTACK_SRC}/bacnet/basic/service/h_upt.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_abort.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_ack_alarm.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_arfs.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_awfs.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_cevent.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_cov.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_dcc.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_error.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_get_alarm_sum.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_get_event.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_getevent.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_lso.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_rd.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_readrange.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_rp.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_rpm.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_ts.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_uevent.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_upt.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_wp.c + ${BACNETSTACK_SRC}/bacnet/basic/service/s_wpm.c + ) # # add ports @@ -390,14 +394,23 @@ set(BACNETSTACK_PORT ${CMAKE_CURRENT_LIST_DIR}/../ports/zephyr) list( APPEND BACNETSTACK_SRCS - ${BACNETSTACK_PORT}/bacport.h - #${BACNETSTACK_PORT}/datetime-init.c - #$<$:${BACNETSTACK_PORT}/ethernet.c> + $, + ${BACNETSTACK_BASIC_SRCS} + ,#else + ${BACNETSTACK_PORT}/device.c + > + ${BACNETSTACK_PORT}/datetime-init.c + $<$:${BACNETSTACK_PORT}/bip-init.c> + $<$:${BACNETSTACK_PORT}/bip6-init.c> + $<$:${BACNETSTACK_PORT}/datalink/bvlc.c> + $<$:${BACNETSTACK_PORT}/ethernet.c> ${BACNETSTACK_PORT}/main.c - #${BACNETSTACK_PORT}/mstimer-init.c + ${BACNETSTACK_PORT}/mstimer-init.c ) +add_subdirectory(subsys) + # # library diff --git a/zephyr/Kconfig b/zephyr/Kconfig index ed34d5e0..86728963 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -10,7 +10,7 @@ menuconfig BACNETSTACK if BACNETSTACK -module = BACNET +module = BACNETSTACK module-str = Log level for BACnet module-help = Enable BACnet library to output debug messages source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config" @@ -100,6 +100,6 @@ config BACDL_BIP6_PORT help UDP port to listen on (default=47808) -#rsource "subsys/Kconfig" +rsource "subsys/Kconfig" endif # BACNETSTACK diff --git a/zephyr/samples/profiles/b-ss/CMakeLists.txt b/zephyr/samples/profiles/b-ss/CMakeLists.txt new file mode 100644 index 00000000..805316a4 --- /dev/null +++ b/zephyr/samples/profiles/b-ss/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.13.1) + +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(b-ss) + +target_sources(app PRIVATE src/main.c) diff --git a/zephyr/samples/profiles/b-ss/README.rst b/zephyr/samples/profiles/b-ss/README.rst new file mode 100644 index 00000000..8c2cb16a --- /dev/null +++ b/zephyr/samples/profiles/b-ss/README.rst @@ -0,0 +1,23 @@ +.. _b-ss_sample: + +BACnet Profile B-SS Sample +########################## + +Overview +******** + +This is a simple application demonstrating configuration of a +BACnet B-SS device profile. + +Requirements +************ + +* A board with Ethernet support, for instance: mimxrt1064_evk + +Building and Running +******************** + +This sample can be found under :bacnet_file:`samples/profiles/b-ss` in +the BACnet tree. + +The sample can be built for several platforms. diff --git a/zephyr/samples/profiles/b-ss/prj.conf b/zephyr/samples/profiles/b-ss/prj.conf new file mode 100644 index 00000000..c4488301 --- /dev/null +++ b/zephyr/samples/profiles/b-ss/prj.conf @@ -0,0 +1,83 @@ +CONFIG_BACNETSTACK=y + +# BIP Options +CONFIG_BACDL_BIP=y +CONFIG_BACDL_BIP_PORT=47808 +CONFIG_BACDL_BIP_ADDRESS_INDEX=0 + +# BIP6 Options +#CONFIG_BACDL_BIP6=y +CONFIG_BACDL_BIP6_PORT=47808 +CONFIG_BACDL_BIP6_ADDRESS_INDEX=1 +CONFIG_NET_IPV6_MLD=y +CONFIG_NET_IPV4=n +CONFIG_NET_ARP=n +CONFIG_BACDL_BIP6_ADDRESS_INDEX=0 +CONFIG_BACDL_BIP6_MCAST_ADDRESS="FE80::0020" # YABE unicast workaround + +CONFIG_NEWLIB_LIBC=y + +# pthreads +CONFIG_POSIX_API=y +CONFIG_PTHREAD_IPC=y +CONFIG_POSIX_MQUEUE=y + +# networking +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_ARP=y +CONFIG_NET_TCP=y +CONFIG_NET_UDP=y +#CONFIG_NET_DHCPV4=y + +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y +CONFIG_NET_SOCKETS=y + +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.168.10.80" +CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.168.10.1" +CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.168.10.1" + +CONFIG_NET_LLDP=y + +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=4 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 +CONFIG_NET_CONFIG_MY_IPV6_ADDR="FE80::0010" + +# Promiscuous mode on layer 2 is required to receive IPv6 multicasts. +# Ethernet hardware will not respond to MAC 33:33:x:x:x:x without this: +CONFIG_ETH_MCUX_PROMISCUOUS_MODE=y + +#CONFIG_LOG_STRDUP_BUF_COUNT=4 +#CONFIG_LOG_STRDUP_MAX_STRING=96 + +CONFIG_DNS_RESOLVER=y +CONFIG_SLIP_STATISTICS=n +CONFIG_NET_SHELL=y + +CONFIG_NET_TX_STACK_SIZE=8192 +CONFIG_NET_RX_STACK_SIZE=8192 + +CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=4 + +# logging +#CONFIG_NET_CONN_LOG_LEVEL_DEFAULT=n +#CONFIG_NET_CONN_LOG_LEVEL_DBG=y +#CONFIG_NET_IF_LOG_LEVEL_DEFAULT=n +CONFIG_NET_IF_LOG_LEVEL_DBG=y +CONFIG_NET_LOG=y +CONFIG_LOG=y +CONFIG_BACNETSTACK_LOG_LEVEL_DBG=y + +# system +CONFIG_ISR_STACK_SIZE=8192 +CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_IDLE_STACK_SIZE=2048 + +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_MINIMAL_LIBC_MALLOC_ARENA_SIZE=131072 + diff --git a/zephyr/samples/profiles/b-ss/sample.yaml b/zephyr/samples/profiles/b-ss/sample.yaml new file mode 100644 index 00000000..08d0d957 --- /dev/null +++ b/zephyr/samples/profiles/b-ss/sample.yaml @@ -0,0 +1,7 @@ +sample: + name: BACnet Profile B-SS Sample + +tests: + sample.profile.b-ss: + tags: bacnet + platform_whitelist: qemu_x86 native_posix native_posix_64 diff --git a/zephyr/samples/profiles/b-ss/src/main.c b/zephyr/samples/profiles/b-ss/src/main.c new file mode 100644 index 00000000..ad83bc8d --- /dev/null +++ b/zephyr/samples/profiles/b-ss/src/main.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2020 Legrand North America, Inc. + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/apdu.h" +#include "bacnet/dcc.h" +#include "bacnet/iam.h" +#include "bacnet/npdu.h" +#include "bacnet/getevent.h" +#include "bacnet/version.h" +#include "bacnet/basic/services.h" +#include "bacnet/datalink/dlenv.h" +#include "bacnet/basic/sys/filename.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/basic/binding/address.h" +/* include the device object */ +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/object/lc.h" +#include "bacnet/basic/object/trendlog.h" +#if defined(INTRINSIC_REPORTING) +#include "bacnet/basic/object/nc.h" +#endif /* defined(INTRINSIC_REPORTING) */ + +/* Logging module registration is already done in ports/zephyr/main.c */ +#include +LOG_MODULE_DECLARE(bacnet, CONFIG_BACNETSTACK_LOG_LEVEL); + +/** Buffer used for receiving */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/** Initialize the handlers we will utilize. + * @see Device_Init, apdu_set_unconfirmed_handler, apdu_set_confirmed_handler + */ +static void service_handlers_init(void) +{ + Device_Init(NULL); + /* we need to handle who-is to support dynamic device binding */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, + handler_who_is); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, + handler_who_has); + /* set the handler for all the services we don't implement */ + /* It is required to send the proper reject message... */ + apdu_set_unrecognized_service_handler_handler( + handler_unrecognized_service); + /* Set the handlers for any confirmed services that we support. */ + /* We must implement read property - it's required! */ + apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY, + handler_read_property); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROP_MULTIPLE, + handler_read_property_multiple); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, + handler_write_property); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, + handler_write_property_multiple); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_REINITIALIZE_DEVICE, + handler_reinitialize_device); + /* handle communication so we can shutup when asked */ + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + handler_device_communication_control); +} + +void main(void) +{ + LOG_INF("\n*** BACnet Profile B-SS Sample ***\n"); + LOG_INF("BACnet Stack Version " BACNET_VERSION_TEXT); + LOG_INF("BACnet Device ID: %u", Device_Object_Instance_Number()); + LOG_INF("BACnet Device Max APDU: %d", MAX_APDU); + + service_handlers_init(); + datalink_init(NULL); + + /* configure the timeout values */ + s64_t last_ms = k_uptime_get(); + + /* broadcast an I-Am on startup */ + Send_I_Am(&Handler_Transmit_Buffer[0]); + + s64_t address_binding_tmr = 0; +#if defined(INTRINSIC_REPORTING) + s64_t recipient_scan_tmr = 0; +#endif +#if defined(BACNET_TIME_MASTER) + BACNET_DATE_TIME bdatetime = { 0 }; +#endif + for (;;) { + k_sleep(K_MSEC(1)); /* Allows debug prints */ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + const unsigned timeout_ms = 1; + + s64_t current_ms = k_uptime_get(); + + /* returns 0 bytes on timeout */ + uint16_t const pdu_len = datalink_receive(&src, &Rx_Buf[0], + MAX_MPDU, timeout_ms); + + /* process */ + if (pdu_len > 0) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + + if (current_ms - last_ms > 1000) { + const uint32_t elapsed_milliseconds = + (uint32_t)(current_ms - last_ms); + //TODO: const uint32_t elapsed_seconds = elapsed_milliseconds / 1000UL; + last_ms = current_ms; + +//TODO: dcc_timer_seconds(elapsed_seconds); +//TODO: datalink_maintenance_timer(elapsed_seconds); +//TODO: dlenv_maintenance_timer(elapsed_seconds); + +//TODO: Load_Control_State_Machine_handler(): + +//TODO: handler_cov_timer_seconds(elapsed_seconds); +//TODO: tsm_timer_milliseconds(elapsed_milliseconds); +//TODO: trend_log_timer(elapsed_seconds); +#if defined(INTRINSIC_REPORTING) +//TODO: Device_local_reporting(); +#endif +#if defined(BACNET_TIME_MASTER) +//TODO: Device_getCurrentDateTime(&bdatetime); +//TODO: handler_Timesync_task(&bdatetime); +#endif + address_binding_tmr += elapsed_milliseconds; +#if defined(INTRINSIC_REPORTING) + recipient_scan_tmr += elapsed_milliseconds; +#endif + } + //TODO: handler_cov_task(); + /* scan cache address */ + if (address_binding_tmr >= 60 * 1000) { + //TODO: address_cache_timer(address_binding_tmr / 1000); + address_binding_tmr = 0; + } +#if defined(INTRINSIC_REPORTING) + /* try to find addresses of recipients */ + if (recipient_scan_tmr >= NC_RESCAN_RECIPIENTS_SECS * 1000) { + //TODO: Notification_Class_find_recipient(); + recipient_scan_tmr = 0; + } +#endif + /* output */ + + /* blink LEDs, Turn on or off outputs, etc */ + } +} diff --git a/zephyr/subsys/CMakeLists.txt b/zephyr/subsys/CMakeLists.txt new file mode 100644 index 00000000..14f3445c --- /dev/null +++ b/zephyr/subsys/CMakeLists.txt @@ -0,0 +1,4 @@ +# Copyright (c) 2020 Legrand North America, LLC. +# SPDX-License-Identifier: MIT + +add_subdirectory_ifdef(CONFIG_BACNETSTACK_BACNET_SERVER server) diff --git a/zephyr/subsys/Kconfig b/zephyr/subsys/Kconfig new file mode 100644 index 00000000..a020b125 --- /dev/null +++ b/zephyr/subsys/Kconfig @@ -0,0 +1,8 @@ +# Copyright (c) 2020 Legrand North America, LLC. +# SPDX-License-Identifier: MIT + +# Kconfig - Subsystem configuration options + +comment "BACnet Subsystems" + +rsource "server/Kconfig" diff --git a/zephyr/subsys/server/CMakeLists.txt b/zephyr/subsys/server/CMakeLists.txt new file mode 100644 index 00000000..dffcf1b5 --- /dev/null +++ b/zephyr/subsys/server/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2020 Legrand North America, LLC. +# SPDX-License-Identifier: MIT + +zephyr_include_directories(include) + +zephyr_interface_library_named(bac_server) + +zephyr_library() +zephyr_library_sources(server.c) + +zephyr_library_link_libraries(bac_server) diff --git a/zephyr/subsys/server/Kconfig b/zephyr/subsys/server/Kconfig new file mode 100644 index 00000000..67cdb2ee --- /dev/null +++ b/zephyr/subsys/server/Kconfig @@ -0,0 +1,35 @@ +# Copyright (c) 2020 Legrand North America, LLC. +# SPDX-License-Identifier: MIT + + +# Kconfig - + +menuconfig BACNETSTACK_BACNET_SERVER + bool "BACNETSTACK_BACNET_SERVER" + help + This option enables BACnet Server services + +if BACNETSTACK_BACNET_SERVER + + module = BACNETSTACK_BACNET_SERVER + module-str = bac_server + + config BACNETSTACK_BACNET_SERVER_APP_PRIORITY + int "App init priority" + default 0 + help + This sets the starting priority of the thread. + + config BACNETSTACK_BACNET_SERVER_PRIO + int "BACnet server thread priority" + default 50 + help + This sets the execution priority of the thread. + + config BACNETSTACK_BACNET_SERVER_STACK_SIZE + int "BACnet server stack size" + default 4096 + help + This sets the stack size of the thread. + +endif # BACNETSTACK_BACNET_SERVER diff --git a/zephyr/subsys/server/server.c b/zephyr/subsys/server/server.c new file mode 100644 index 00000000..7ea913f0 --- /dev/null +++ b/zephyr/subsys/server/server.c @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2020 Legrand North America, LLC. + * + * SPDX-License-Identifier: MIT + */ + +#include /*TODO: Not std until C11! */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* some BACnet modules we use */ +#include "bacnet/bacdef.h" +#include "bacnet/config.h" +#include "bacnet/bactext.h" +#include "bacnet/bacerror.h" +#include "bacnet/iam.h" +#include "bacnet/arf.h" +#include "bacnet/npdu.h" +#include "bacnet/apdu.h" +#include "bacnet/version.h" +/* some demo modules we use */ +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/basic/binding/address.h" +#include "bacnet/basic/services.h" +/* our datalink layers */ +#include "bacnet/datalink/bip.h" +#include "bacnet/datalink/bvlc.h" +/* include the device object */ +#include "bacnet/basic/object/device.h" + +/* Logging module registration is already done in ports/zephyr/main.c */ +#include +LOG_MODULE_DECLARE(bacnet, CONFIG_BACNETSTACK_LOG_LEVEL); + +enum bacnet_server_msg_type { + SERVER_MSG_TYPE_INVALID = 0, + SERVER_MSG_TYPE_IPV4_EVENT, +}; + +struct bacnet_server_msg { + uint8_t type; + uint8_t dummy[3]; + uint32_t parm_u32; + void *parm_ptr; +}; + +K_MSGQ_DEFINE(bacnet_server_msgq, sizeof(struct bacnet_server_msg), 8, + alignof(struct bacnet_server_msg)); + +#define SERVER_IPV4_EVENTS_MASK \ + (NET_EVENT_IPV4_ADDR_ADD | NET_EVENT_IPV4_ADDR_DEL) + +static struct k_thread server_thread_data; +static K_THREAD_STACK_DEFINE(server_thread_stack, + CONFIG_BACNETSTACK_BACNET_SERVER_STACK_SIZE); + +/* Keep a reference to the Ethernet interface */ +static struct net_mgmt_event_callback mgmt_cb; + +/* track our directly connected ports network number */ +static uint16_t BIP_Net; +/* buffer for receiving packets from the directly connected ports */ +static uint8_t BIP_Rx_Buffer[MAX_MPDU]; + +/** Initialize the handlers we will utilize. + * @see Device_Init, apdu_set_unconfirmed_handler, apdu_set_confirmed_handler + */ +static void service_handlers_init(void) +{ + Device_Init(NULL); + /* we need to handle who-is to support dynamic device binding */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, + handler_who_is); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, + handler_who_has); + /* set the handler for all the services we don't implement */ + /* It is required to send the proper reject message... */ + apdu_set_unrecognized_service_handler_handler( + handler_unrecognized_service); + /* Set the handlers for any confirmed services that we support. */ + /* We must implement read property - it's required! */ + apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY, + handler_read_property); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROP_MULTIPLE, + handler_read_property_multiple); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, + handler_write_property); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, + handler_write_property_multiple); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_REINITIALIZE_DEVICE, + handler_reinitialize_device); + /* handle communication so we can shutup when asked */ + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + handler_device_communication_control); +} + +/* TODO: Update copyright as this code pattern copied from + * conn_mgr_ipv4_events_handler() + */ +static void ipv4_events_handler(struct net_mgmt_event_callback *cb, + u32_t mgmt_event, struct net_if *iface) +{ + static int counter = 0; + printk("\n*** Handler[%d]: IPv4 event %08x received on iface %p ***\n", + ++counter, mgmt_event, iface); + + if ((mgmt_event & SERVER_IPV4_EVENTS_MASK) != mgmt_event) { + printk("\n*** Handler[%d]: ignoring event %08x on iface %p ***\n", + counter, mgmt_event, iface); + return; + } + + struct bacnet_server_msg msg = { + .type = SERVER_MSG_TYPE_IPV4_EVENT, + .parm_u32 = mgmt_event, + .parm_ptr = iface, + }; + + int ret = k_msgq_put(&bacnet_server_msgq, &msg, K_NO_WAIT); + if (ret != 0) { + printk("\n*** Handler[%d]: queue full, type %u event 0x%08x on iface %p dropped! ***\n", + counter, msg.type, msg.parm_u32, msg.parm_ptr); + } +} + +/** + * @brief BACnet Server Thread + */ +static void server_thread(void) +{ + LOG_INF("Server: started"); + service_handlers_init(); + + bip_init("Server: from init"); + BIP_Net = 1; + + net_mgmt_init_event_callback(&mgmt_cb, ipv4_events_handler, + SERVER_IPV4_EVENTS_MASK); + net_mgmt_add_event_callback(&mgmt_cb); + + while (1) { + const s32_t sleep_ms = K_FOREVER; + + struct bacnet_server_msg msg = { + .type = SERVER_MSG_TYPE_INVALID, + }; + int ret = k_msgq_get(&bacnet_server_msgq, &msg, sleep_ms); + + /* Waiting period timed out */ + if (-EAGAIN == ret) { + BACNET_ADDRESS src = { + 0 + }; /* address where message came from */ + /* input */ + /* returns 0 bytes on timeout */ + uint16_t pdu_len = bip_receive(&src, &BIP_Rx_Buffer[0], + MAX_MPDU, 5); + + /* process */ + if (pdu_len) { + LOG_INF("Server: BIP received %u bytes.", + (unsigned)pdu_len); + } + } + + /* Message received */ + else if (0 == ret) { + switch (msg.type) { +#if defined(CONFIG_NET_IPV4) + case SERVER_MSG_TYPE_IPV4_EVENT: { + LOG_INF("Server: MSG_TYPE_IPV4_EVENT u32: %08x ptr: %p", + msg.parm_u32, msg.parm_ptr); + const u32_t mgmt_event = msg.parm_u32; + //TODO: const struct net_if *iface = msg.parm_ptr; + + /* Handle events */ + if ((mgmt_event & SERVER_IPV4_EVENTS_MASK) != + mgmt_event) { + LOG_INF("Server: thread ignoring event"); + break; + } + + switch (NET_MGMT_GET_COMMAND(mgmt_event)) { +#if 0 + case NET_EVENT_IPV4_CMD_ADDR_ADD: + LOG_INF("Server: IPv4 addr activated"); + bip_init("Server: from CMD_ADDR_ADD"); + BIP_Net = bip_valid() ? 1 : 0; + break; + case NET_EVENT_IPV4_CMD_ADDR_DEL: + LOG_INF("Server: IPv4 addr deactivated"); + bip_cleanup(); + BIP_Net = 0; + break; +#endif + default: + LOG_INF("Server: Unsupported event %u", + mgmt_event); + break; + } + + } break; +#endif /* defined(CONFIG_NET_IPV4) */ + + default: + LOG_WRN("Server: Dropping unsupported type %u", + msg.type); + break; + } + } + + /* Returned without waiting and without message - why? */ + else { + LOG_WRN("Server: Msgq returned w/o timeout or msg! req = %d", + ret); + } + } +} + +static int server_init(struct device *dev) +{ + ARG_UNUSED(dev); + + k_thread_create(&server_thread_data, server_thread_stack, + K_THREAD_STACK_SIZEOF(server_thread_stack), + (k_thread_entry_t)server_thread, NULL, NULL, NULL, + K_PRIO_PREEMPT(CONFIG_BACNETSTACK_BACNET_SERVER_PRIO), 0, + K_NO_WAIT); + k_thread_name_set(&server_thread_data, "BACserver"); + + return 0; +} + +SYS_INIT(server_init, APPLICATION, CONFIG_BACNETSTACK_BACNET_SERVER_APP_PRIORITY); diff --git a/zephyr/tests/bacnet/device/CMakeLists.txt b/zephyr/tests/bacnet/device/CMakeLists.txt new file mode 100644 index 00000000..809ef0fc --- /dev/null +++ b/zephyr/tests/bacnet/device/CMakeLists.txt @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.13.1) + +# Extract module path and names +string(REGEX REPLACE + "/zephyr/tests/[a-zA-Z_/-]*$" "" + BACNET_BASE + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/zephyr/tests/" "/test/" + BACNET_TEST_PATH + ${CMAKE_CURRENT_SOURCE_DIR}) +get_filename_component(BACNET_NAME ${BACNET_BASE} NAME) + +# Update include path for this module +list(APPEND BACNET_INCLUDE ${BACNET_BASE}/src) + +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(${BACNET_NAME}) + +target_include_directories(app PRIVATE ${BACNET_INCLUDE}) +target_sources(app PRIVATE + ${BACNET_TEST_PATH}/src/main.c + ) diff --git a/zephyr/tests/bacnet/device/prj.conf b/zephyr/tests/bacnet/device/prj.conf new file mode 100644 index 00000000..4b694bfa --- /dev/null +++ b/zephyr/tests/bacnet/device/prj.conf @@ -0,0 +1,11 @@ +CONFIG_ZTEST=y +CONFIG_BACNETSTACK=y + +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_ARP=y +CONFIG_NET_TCP=y +CONFIG_NET_UDP=y + +CONFIG_NEWLIB_LIBC=y diff --git a/zephyr/tests/bacnet/device/testcase.yaml b/zephyr/tests/bacnet/device/testcase.yaml new file mode 100644 index 00000000..c384da15 --- /dev/null +++ b/zephyr/tests/bacnet/device/testcase.yaml @@ -0,0 +1,3 @@ +tests: + bacnet.device: + tags: bacnet