diff --git a/ports/zephyr/bip6-init.c b/ports/zephyr/bip6-init.c index fae1ed0a..c573c334 100644 --- a/ports/zephyr/bip6-init.c +++ b/ports/zephyr/bip6-init.c @@ -354,6 +354,25 @@ int bip6_send_pdu( return bvlc6_send_pdu(dest, npdu_data, pdu, pdu_len); } +/** + * @brief Generate ASCII address string from BACnet/IPv6 address + * @param s - buffer to store the string + * @param n - size of the buffer + * @param addr - BACnet/IPv6 address + */ +static int bvlc6_snprintf_addr(char *s, size_t n, BACNET_IP6_ADDRESS *addr) +{ + uint16_t addr16[8]; + + bvlc6_address_get( + addr, &addr16[0], &addr16[1], &addr16[2], &addr16[3], &addr16[4], + &addr16[5], &addr16[6], &addr16[7]); + + return snprintf(s, n, "%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X", + addr16[0], addr16[1], addr16[2], addr16[3], addr16[4], addr16[5], + addr16[6], addr16[7]); +} + /** * BACnet/IP Datalink Receive handler. * @@ -477,29 +496,33 @@ void bip6_cleanup(void) */ bool bip6_init(char *ifname) { - int sock_fd = -1; int status = -1; struct sockaddr_in6 server = { 0 }; struct in6_addr broadcast_address; struct ipv6_mreq join_request; const int sockopt = 1; + char addr6_str[40] = ""; LOG_DBG("bip6_init()"); - bip6_set_interface(ifname); if (BIP6_Addr.port == 0) { bip6_set_port(0xBAC0U); } + bip6_set_interface(ifname); LOG_INF("BIP6: IPv6 UDP port: 0x%04X", (unsigned)BIP6_Addr.port); + bvlc6_snprintf_addr(addr6_str, sizeof(addr6_str), &BIP6_Addr); + LOG_INF("BIP6: IPv6 unicast addr: %s", addr6_str); if (BIP6_Broadcast_Addr.address[0] == 0) { bvlc6_address_set( &BIP6_Broadcast_Addr, BIP6_MULTICAST_SITE_LOCAL, 0, 0, 0, 0, 0, 0, BIP6_MULTICAST_GROUP_ID); LOG_INF("BIP6: IPv6 MULTICAST_SITE_LOCAL"); } + bvlc6_snprintf_addr(addr6_str, sizeof(addr6_str), &BIP6_Broadcast_Addr); + LOG_INF("BIP6: IPv6 multicast addr: %s", addr6_str); + /* 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) { + BIP6_Socket = zsock_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (BIP6_Socket < 0) { LOG_ERR("%s:%d - Failed to create socket", THIS_FILE, __LINE__); return false; } else { @@ -509,11 +532,16 @@ bool bip6_init(char *ifname) /* 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)); + BIP6_Socket, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt)); if (status < 0) { - zsock_close(sock_fd); - BIP6_Socket = -1; - return false; + LOG_ERR("BIP6: setsockopt(SO_REUSEADDR)"); + } + /* allow us to send a broadcast */ + status = zsock_setsockopt( + BIP6_Socket, SOL_SOCKET, SO_BROADCAST, &sockopt, sizeof(sockopt)); + if (status < 0) { + /* ignored? For compatibility? Really? */ + LOG_ERR("BIP6: setsockopt(SO_BROADCAST)"); } /* subscribe to a multicast address */ memcpy( @@ -528,31 +556,40 @@ bool bip6_init(char *ifname) BIP6_Socket, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &join_request, sizeof(join_request)); if (status < 0) { - perror("BIP6: setsockopt(IPV6_ADD_MEMBERSHIP)"); + LOG_ERR("BIP6: setsockopt(IPV6_ADD_MEMBERSHIP)"); + return false; } - /* bind the socket to the local port number and IP address */ server.sin6_family = AF_INET6; - +#if 0 + uint16_t addr16[8]; + bvlc6_address_get(&BIP6_Addr, &addr16[0], &addr16[1], &addr16[2], + &addr16[3], &addr16[4], &addr16[5], &addr16[6], &addr16[7]); + server.sin6_addr.s6_addr16[0] = htons(addr16[0]); + server.sin6_addr.s6_addr16[1] = htons(addr16[1]); + server.sin6_addr.s6_addr16[2] = htons(addr16[2]); + server.sin6_addr.s6_addr16[3] = htons(addr16[3]); + server.sin6_addr.s6_addr16[4] = htons(addr16[4]); + server.sin6_addr.s6_addr16[5] = htons(addr16[5]); + server.sin6_addr.s6_addr16[6] = htons(addr16[6]); + server.sin6_addr.s6_addr16[7] = htons(addr16[7]); +#else server.sin6_addr = in6addr_any; +#endif server.sin6_port = htons(BIP6_Addr.port); - LOG_INF("BIP6: Binding to port %d", BIP6_Addr.port); - status = - zsock_bind(sock_fd, (const struct sockaddr *)&server, sizeof(server)); + zsock_bind(BIP6_Socket, (const struct sockaddr *)&server, sizeof(server)); if (status < 0) { - zsock_close(sock_fd); + zsock_close(BIP6_Socket); BIP6_Socket = -1; LOG_ERR("%s:%d - zsock_bind() failure", THIS_FILE, __LINE__); return false; } else { - LOG_INF("BIP6: Socket bound"); + LOG_INF("BIP6: Socket bound. Success!"); } - bvlc6_init(); - LOG_DBG("bip6_init() success"); return true; } diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 94541a2a..d5384864 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -440,7 +440,8 @@ list( ) -add_subdirectory(subsys) + zephyr_include_directories(include) + add_subdirectory(subsys) # # library @@ -466,6 +467,7 @@ zephyr_compile_definitions( BACNET_VENDOR_ID=${CONFIG_BACNET_VENDOR_IDENTIFIER} BACNET_VENDOR_NAME="${CONFIG_BACNET_VENDOR_NAME}" BACNET_PROTOCOL_REVISION=${CONFIG_BACNET_PROTOCOL_REVISION} + BACNET_STACK_DEPRECATED_DISABLE=1 # datalink API $<$:BACDL_NONE> $<$:BACAPP_ALL> diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 223f6000..cff95d81 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -46,6 +46,7 @@ config BACNET_PROPERTY_LISTS config BACNET_PROPERTY_ARRAY_LISTS bool "BACnetARRAY Property Lists" + default true help Enable BACnetARRAY Property Lists @@ -321,7 +322,7 @@ config BACDL_BIP6_ADDRESS_INDEX config BACDL_BIP6_MCAST_ADDRESS string "IPv6 multicast destination" - default "FF0E::BAC0" + default "FF02::BAC0" depends on BACDL_BIP6 help IPv6 multicast group address for BACNET. diff --git a/zephyr/include/bacnet_basic/bacnet_basic.h b/zephyr/include/bacnet_basic/bacnet_basic.h new file mode 100644 index 00000000..7f332161 --- /dev/null +++ b/zephyr/include/bacnet_basic/bacnet_basic.h @@ -0,0 +1,25 @@ +/** + * @file + * @brief BACnet Basic Stack initialization and basic task handler + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_BASIC_H +#define BACNET_BASIC_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void bacnet_basic_init(void); +void bacnet_basic_task(void); +unsigned long bacnet_basic_uptime_seconds(void); +unsigned long bacnet_basic_packet_count(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/zephyr/include/bacnet_basic/bacnet_port.h b/zephyr/include/bacnet_basic/bacnet_port.h new file mode 100644 index 00000000..d0059f75 --- /dev/null +++ b/zephyr/include/bacnet_basic/bacnet_port.h @@ -0,0 +1,27 @@ +/** + * @file + * @brief The BACnet datalink tasks for handling the device specific + * data link network port layer + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_PORT_H +#define BACNET_PORT_H + +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +bool bacnet_port_init(void); +void bacnet_port_task(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/zephyr/include/bacnet_basic/bacnet_port_ipv4.h b/zephyr/include/bacnet_basic/bacnet_port_ipv4.h new file mode 100644 index 00000000..eeab0a8d --- /dev/null +++ b/zephyr/include/bacnet_basic/bacnet_port_ipv4.h @@ -0,0 +1,31 @@ +/** + * @file + * @brief API for the BACnet datalink tasks for handling the device specific + * data link network port layer + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_PORT_IPV4_H +#define BACNET_PORT_IPV4_H + +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +#include "bacnet/datalink/bvlc.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void bacnet_port_ipv4_foreign_device_init( + const uint16_t ttl_seconds, + const BACNET_IP_ADDRESS *bbmd_address); +void bacnet_port_ipv4_task(uint16_t elapsed_seconds); +bool bacnet_port_ipv4_init(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/zephyr/include/bacnet_basic/bacnet_port_ipv6.h b/zephyr/include/bacnet_basic/bacnet_port_ipv6.h new file mode 100644 index 00000000..f2c075f6 --- /dev/null +++ b/zephyr/include/bacnet_basic/bacnet_port_ipv6.h @@ -0,0 +1,30 @@ +/** + * @file + * @brief The BACnet/IPv6 datalink tasks for handling the device specific + * data link network port layer + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_PORT_IPV6_H +#define BACNET_PORT_IPV6_H + +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +#include "bacnet/datalink/bvlc6.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void bacnet_port_ipv6_foreign_device_init( + const uint16_t ttl_seconds, const BACNET_IP6_ADDRESS *bbmd_address); +void bacnet_port_ipv6_task(uint16_t elapsed_seconds); +bool bacnet_port_ipv6_init(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/zephyr/include/bacnet_settings/bacnet_settings.h b/zephyr/include/bacnet_settings/bacnet_settings.h new file mode 100644 index 00000000..2e2090df --- /dev/null +++ b/zephyr/include/bacnet_settings/bacnet_settings.h @@ -0,0 +1,78 @@ +/** + * @file + * @brief API for Get/Set of BACnet application encoded settings handlers + * @author Steve Karg + * @date May 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_SETTINGS_H +#define BACNET_SETTINGS_H + +#include +#include +#include +#include "bacnet/bacdef.h" +#include "bacnet/bacapp.h" +#include "bacnet/bacstr.h" +#include "bacnet/bacint.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +int bacnet_settings_value_get(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + BACNET_APPLICATION_DATA_VALUE *value); +bool bacnet_settings_value_set(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + BACNET_APPLICATION_DATA_VALUE *value); + +int bacnet_settings_real_get(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + float default_value, float *value); +bool bacnet_settings_real_set(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + float value); + +int bacnet_settings_unsigned_get(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + BACNET_UNSIGNED_INTEGER default_value, + BACNET_UNSIGNED_INTEGER *value); +bool bacnet_settings_unsigned_set(uint16_t object_type, + uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + BACNET_UNSIGNED_INTEGER value); + +int bacnet_settings_signed_get(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + int32_t default_value, int32_t *value); +bool bacnet_settings_signed_set(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + int32_t value); + +int bacnet_settings_characterstring_get(uint16_t object_type, + uint32_t object_instance, + uint32_t property_id, + uint32_t array_index, + const char *default_value, + BACNET_CHARACTER_STRING *value); + +bool bacnet_settings_characterstring_ansi_set(uint16_t object_type, + uint32_t object_instance, + uint32_t property_id, + uint32_t array_index, + const char *cstring); + +int bacnet_settings_string_get(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + const char *default_value, char *value, + size_t value_size); + +bool bacnet_settings_string_set(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + const char *value); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/zephyr/include/bacnet_settings/bacnet_storage.h b/zephyr/include/bacnet_settings/bacnet_storage.h new file mode 100644 index 00000000..c5ebb170 --- /dev/null +++ b/zephyr/include/bacnet_settings/bacnet_storage.h @@ -0,0 +1,45 @@ +/** + * @file + * @brief API for the BACnet storage tasks for handling the device specific + * non-volatile object data + * @author Steve Karg + * @date May 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_STORAGE_H +#define BACNET_STORAGE_H + +#include +#include +#include + +#define BACNET_STORAGE_VALUE_SIZE_MAX SETTINGS_MAX_VAL_LEN +#define BACNET_STORAGE_KEY_SIZE_MAX SETTINGS_MAX_NAME_LEN +#define BACNET_STORAGE_ARRAY_INDEX_NONE UINT32_MAX + +typedef struct bacnet_storage_key_t { + uint16_t object_type; + uint32_t object_instance; + uint32_t property_id; + uint32_t array_index; +} BACNET_STORAGE_KEY; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void bacnet_storage_init(void); + +void bacnet_storage_key_init(BACNET_STORAGE_KEY *key, uint16_t object_type, + uint32_t object_instance, uint32_t property_id, + uint32_t array_index); +int bacnet_storage_key_encode(char *buffer, size_t buffer_size, + BACNET_STORAGE_KEY *key); +int bacnet_storage_set(BACNET_STORAGE_KEY *key, const void *data, + size_t data_size); +int bacnet_storage_get(BACNET_STORAGE_KEY *key, void *data, size_t data_size); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/zephyr/samples/profiles/b-ss/CMakeLists.txt b/zephyr/samples/profiles/b-ss/CMakeLists.txt index 805316a4..debb055d 100644 --- a/zephyr/samples/profiles/b-ss/CMakeLists.txt +++ b/zephyr/samples/profiles/b-ss/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.13.1) -include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) 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 index f252a052..bb506e94 100644 --- a/zephyr/samples/profiles/b-ss/README.rst +++ b/zephyr/samples/profiles/b-ss/README.rst @@ -7,7 +7,7 @@ Overview ******** This is a simple application demonstrating configuration of a -BACnet B-SS device profile. +BACnet B-SS (simple sensor) device profile. Requirements ************ diff --git a/zephyr/samples/profiles/b-ss/prj.conf b/zephyr/samples/profiles/b-ss/prj.conf index c4488301..5a4093a7 100644 --- a/zephyr/samples/profiles/b-ss/prj.conf +++ b/zephyr/samples/profiles/b-ss/prj.conf @@ -1,83 +1,130 @@ +# General config +#CONFIG_ASSERT=y +#CONFIG_ASSERT_LEVEL=2 +#CONFIG_ASSERT_VERBOSE=y + +# Compiler library config +#CONFIG_PICOLIBC=y +#CONFIG_PICOLIBC_USE_MODULE=y +#CONFIG_REQUIRES_FULL_LIBC=y +#CONFIG_MINIMAL_LIBC=y + +# system +# some heap is needed for the shell +CONFIG_HEAP_MEM_POOL_SIZE=2048 +CONFIG_KERNEL_MEM_POOL=y +CONFIG_MAIN_THREAD_PRIORITY=7 +#CONFIG_PICOLIBC=y +CONFIG_ISR_STACK_SIZE=8192 +CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_IDLE_STACK_SIZE=2048 +CONFIG_INIT_STACKS=y + +# BACnet Library CONFIG_BACNETSTACK=y +CONFIG_BACNETSTACK_BACNET_BASIC=y +# BACnet Library - options +CONFIG_BACNET_MAX_CHARACTER_STRING_BYTES=128 +CONFIG_BACAPP_MINIMAL=y +CONFIG_BACNET_PROPERTY_ARRAY_LISTS=y +# BACnet Library - objects +CONFIG_BACNET_BASIC_OBJECT_NETWORK_PORT=y +CONFIG_BACNET_BASIC_OBJECT_ANALOG_INPUT=y +# BACnet Library - shell +CONFIG_BACNET_BASIC_DEVICE_SHELL=y +# BACnet settings subsystem +#CONFIG_BACNETSTACK_BACNET_SETTINGS=y +#CONFIG_BACNET_SETTINGS=y +#CONFIG_BACNET_SETTINGS_SHELL=y # BIP Options CONFIG_BACDL_BIP=y CONFIG_BACDL_BIP_PORT=47808 CONFIG_BACDL_BIP_ADDRESS_INDEX=0 +CONFIG_NET_IPV4=y +CONFIG_NET_DHCPV4=y +CONFIG_NET_IPV4_AUTO=y +#CONFIG_NET_CONFIG_SETTINGS=y +#CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.168.1.80" +#CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0" +#CONFIG_NET_CONFIG_MY_IPV4_GW="192.168.1.1" +#CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.168.1.1" +CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=4 # 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 +#CONFIG_BACDL_BIP6_PORT=47808 +#CONFIG_BACDL_BIP6_ADDRESS_INDEX=0 +#CONFIG_BACDL_BIP6_MCAST_ADDRESS="FE80::0020" +#CONFIG_NET_IPV6=y +#CONFIG_NET_IPV6_MLD=y +#CONFIG_NET_CONFIG_MY_IPV6_ADDR="FE80::0010" +#CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=4 +#CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 # networking CONFIG_NETWORKING=y -CONFIG_NET_IPV4=y -CONFIG_NET_IPV6=y -CONFIG_NET_ARP=y -CONFIG_NET_TCP=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_SHELL=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_DNS_RESOLVER=y +#CONFIG_SLIP_STATISTICS=n CONFIG_NET_TX_STACK_SIZE=8192 CONFIG_NET_RX_STACK_SIZE=8192 -CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=4 - -# logging +# Enable Shell, Console, and Logging features +CONFIG_CONSOLE=y +CONFIG_PRINTK=y +CONFIG_SHELL=y +CONFIG_KERNEL_SHELL=y +CONFIG_THREAD_MONITOR=y +#CONFIG_BOOT_BANNER=n +CONFIG_THREAD_NAME=y +CONFIG_DEVICE_SHELL=y +#CONFIG_POSIX_CLOCK=y +#CONFIG_DATE_SHELL=y +CONFIG_LOG=y +#CONFIG_LOG_STRDUP_BUF_COUNT=4 +#CONFIG_LOG_STRDUP_MAX_STRING=96 #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 +# enable the thread, stack, and runtime stats +#CONFIG_THREAD_ANALYZER=y +#CONFIG_THREAD_STACK_INFO=y +#CONFIG_THREAD_RUNTIME_STATS=y +#CONFIG_THREAD_RUNTIME_STATS_USE_TIMING_FUNCTIONS=n +#CONFIG_SCHED_THREAD_USAGE=y +#CONFIG_SCHED_THREAD_USAGE_ANALYSIS=y +#CONFIG_STATS=y +#CONFIG_STATS_SHELL=y + +# +# Storage +# +#CONFIG_FLASH=y +#CONFIG_FLASH_MAP=y +#CONFIG_FLASH_PAGE_LAYOUT=y +# native_posix uses a simulated flash +#CONFIG_FLASH_SIMULATOR=y +#CONFIG_DISK_ACCESS=y. +#CONFIG_FILE_SYSTEM=y +#CONFIG_FILE_SYSTEM_LITTLEFS=y +#CONFIG_FILE_SYSTEM_SHELL=y +#CONFIG_NVS=y +#CONFIG_SETTINGS=y +#CONFIG_SETTINGS_NVS=y +#CONFIG_SETTINGS_RUNTIME=y +#CONFIG_SETTINGS_FILE=y +#CONFIG_SETTINGS_SHELL=y 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 index 16b29fc5..97072ff6 100644 --- a/zephyr/samples/profiles/b-ss/sample.yaml +++ b/zephyr/samples/profiles/b-ss/sample.yaml @@ -4,4 +4,4 @@ sample: tests: bacnet-stack.sample.profile.b-ss: tags: bacnet - platform_allow: qemu_x86 native_posix native_posix_64 + platform_allow: qemu_x86 native_posix native_posix_64 native_sim diff --git a/zephyr/samples/profiles/b-ss/src/main.c b/zephyr/samples/profiles/b-ss/src/main.c index c94d7c18..8ed5e8ec 100644 --- a/zephyr/samples/profiles/b-ss/src/main.c +++ b/zephyr/samples/profiles/b-ss/src/main.c @@ -4,154 +4,41 @@ * SPDX-License-Identifier: MIT */ -#include +#include #include -#include "bacnet/config.h" +/* BACnet Stack defines - first */ #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" +/* BACnet Stack core API */ #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 */ +/* BACnet Stack basic device API - see bacnet_basic/device.c for details */ #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) */ +/* BACnet Stack basic objects - also enable in prj.conf */ +#include "bacnet/basic/object/ai.h" +#if (BACNET_PROTOCOL_REVISION >= 17) +#include "bacnet/basic/object/netport.h" +#endif +#include "bacnet_basic/bacnet_basic.h" /* Logging module registration is already done in ports/zephyr/main.c */ -#include +#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) +int main(void) { LOG_INF("\n*** BACnet Profile B-SS Sample ***\n"); LOG_INF("BACnet Stack Version " BACNET_VERSION_TEXT); + + /* initialize objects for this basic sample */ + Device_Init(NULL); + Device_Set_Object_Instance_Number(260123); + Analog_Input_Create(1); + Analog_Input_Name_Set(1, "Sensor"); + 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); + /* work happens in server module */ - /* configure the timeout values */ - int64_t last_ms = k_uptime_get(); - - /* broadcast an I-Am on startup */ - Send_I_Am(&Handler_Transmit_Buffer[0]); - - int64_t address_binding_tmr = 0; -#if defined(INTRINSIC_REPORTING) - int64_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; - - int64_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 */ - } + return 0; } diff --git a/zephyr/subsys/CMakeLists.txt b/zephyr/subsys/CMakeLists.txt index 7ff964df..577a68e8 100644 --- a/zephyr/subsys/CMakeLists.txt +++ b/zephyr/subsys/CMakeLists.txt @@ -1,5 +1,8 @@ # Copyright (c) 2020 Legrand North America, LLC. # SPDX-License-Identifier: MIT +zephyr_sources(bacnet_shell.c) +add_subdirectory_ifdef(CONFIG_BACNETSTACK_BACNET_SETTINGS bacnet_settings) +add_subdirectory_ifdef(CONFIG_BACNETSTACK_BACNET_BASIC bacnet_basic) add_subdirectory_ifdef(CONFIG_BACNETSTACK_BACNET_SERVER server) -add_subdirectory(object) +add_subdirectory_ifdef(CONFIG_BACNETSTACK_BACNET_OBJECT object) diff --git a/zephyr/subsys/Kconfig b/zephyr/subsys/Kconfig index ef726996..086adfe9 100644 --- a/zephyr/subsys/Kconfig +++ b/zephyr/subsys/Kconfig @@ -5,5 +5,7 @@ comment "BACnet Subsystems" +rsource "bacnet_settings/Kconfig" +rsource "bacnet_basic/Kconfig" rsource "object/Kconfig" rsource "server/Kconfig" diff --git a/zephyr/subsys/bacnet_basic/CMakeLists.txt b/zephyr/subsys/bacnet_basic/CMakeLists.txt new file mode 100644 index 00000000..7470f622 --- /dev/null +++ b/zephyr/subsys/bacnet_basic/CMakeLists.txt @@ -0,0 +1,23 @@ +# CMake for BACnet settings library +# +# @author Steve Karg +# @date May 2024 +# @copyright SPDX-License-Identifier: MIT +zephyr_library(bacnet_basic) + +zephyr_library_include_directories(include) + +zephyr_library_sources( + bacnet_port.c + bacnet_port_ipv4.c + bacnet_port_ipv6.c + bacnet_basic.c + device.c + server.c + ) + + zephyr_library_sources_ifdef(CONFIG_BACNET_BASIC_DEVICE_SHELL + bacnet_shell_objects.c + bacnet_shell_packets.c + bacnet_shell_uptime.c + ) diff --git a/zephyr/subsys/bacnet_basic/Kconfig b/zephyr/subsys/bacnet_basic/Kconfig new file mode 100644 index 00000000..afa8f9d6 --- /dev/null +++ b/zephyr/subsys/bacnet_basic/Kconfig @@ -0,0 +1,34 @@ +# Kconfig - Subsystem configuration options +# +# @author Steve Karg +# @date May 2024 +# @copyright SPDX-License-Identifier: MIT +menuconfig BACNETSTACK_BACNET_BASIC + bool "BACNETSTACK_BACNET_BASIC" + default n + help + This option enables a basic BACnet Device object and tasking + +if BACNETSTACK_BACNET_BASIC + + module = BACNETSTACK_BACNET_BASIC + module-str = bacnet_basic + + config BACNET_BASIC_DEVICE_OBJECT_NAME + string "BACnet device object default name" + default "Basic Server" + help + BACnet device object default name + + config BACNET_BASIC_DEVICE_OBJECT_VERSION + string "BACnet device object default application version string" + default "1.0.0" + help + BACnet device object default application version string + + config BACNET_BASIC_DEVICE_SHELL + bool "BACnet Basic Device subsystem shell" + depends on BACNETSTACK + default y if SHELL + +endif # BACNETSTACK_BACNET_SETTINGS diff --git a/zephyr/subsys/bacnet_basic/bacnet_basic.c b/zephyr/subsys/bacnet_basic/bacnet_basic.c new file mode 100644 index 00000000..b28f72a4 --- /dev/null +++ b/zephyr/subsys/bacnet_basic/bacnet_basic.c @@ -0,0 +1,142 @@ +/** + * @file + * @brief BACnet Stack initialization and task handler + * @author Steve Karg + * @date March 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack core API */ +#include "bacnet/npdu.h" +#include "bacnet/dcc.h" +#include "bacnet/iam.h" +/* BACnet Stack basic services */ +#include "bacnet/basic/sys/mstimer.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datalink/datalink.h" +/* BACnet Stack basic objects */ +#include "bacnet/basic/object/device.h" +/* objects */ +#if (BACNET_PROTOCOL_REVISION >= 17) +#include "bacnet/basic/object/netport.h" +#endif +#include "bacnet/basic/object/device.h" +/* me */ +#include "bacnet_basic/bacnet_basic.h" + +/* 1s timer for basic non-critical timed tasks */ +static struct mstimer BACnet_Task_Timer; +/* task timer for object functionality */ +static struct mstimer BACnet_Object_Timer; +/* uptimer for BACnet task */ +static unsigned long BACnet_Uptime_Seconds; +/* packet counter for BACnet task */ +static unsigned long BACnet_Packet_Count; +/* local Device ID to track changes */ +static uint32_t Device_ID = 0xFFFFFFFF; + +/** + * @brief Get the BACnet device uptime in seconds + * @return The number of seconds the BACnet device has been running + */ +unsigned long bacnet_basic_uptime_seconds(void) +{ + return BACnet_Uptime_Seconds; +} + +/** + * @brief Get the BACnet device uptime in seconds + * @return The number of seconds the BACnet device has been running + */ +unsigned long bacnet_basic_packet_count(void) +{ + return BACnet_Packet_Count; +} + +/** + * @brief Initialize the BACnet device object, the service handlers, and timers + */ +void bacnet_basic_init(void) +{ + /* set up our confirmed service unrecognized service handler - required! */ + apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service); + /* 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 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_SUBSCRIBE_COV, handler_cov_subscribe); + /* handle communication so we can shutup when asked, or restart */ + apdu_set_confirmed_handler(SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + handler_device_communication_control); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_REINITIALIZE_DEVICE, handler_reinitialize_device); + /* start the 1 second timer for non-critical cyclic tasks */ + mstimer_set(&BACnet_Task_Timer, 1000L); + /* start the timer for more time sensitive object specific cyclic tasks */ + mstimer_set(&BACnet_Object_Timer, 100UL); +} + +/* local buffer for incoming PDUs to process */ +static uint8_t PDUBuffer[MAX_MPDU]; + +/** + * @brief non-blocking BACnet task + */ +void bacnet_basic_task(void) +{ + bool hello_world = false; + uint16_t pdu_len = 0; + BACNET_ADDRESS src = { 0 }; + uint32_t elapsed_milliseconds = 0; + uint32_t elapsed_seconds = 0; + + /* hello, World! */ + if (Device_ID != Device_Object_Instance_Number()) { + Device_ID = Device_Object_Instance_Number(); + hello_world = true; + } + if (hello_world) { + Send_I_Am(&Handler_Transmit_Buffer[0]); + } + /* handle non-time-critical cyclic tasks */ + if (mstimer_expired(&BACnet_Task_Timer)) { + /* 1 second tasks */ + mstimer_reset(&BACnet_Task_Timer); + /* presume that the elapsed time is the interval time */ + elapsed_milliseconds = mstimer_interval(&BACnet_Task_Timer); + elapsed_seconds = elapsed_milliseconds/1000; + BACnet_Uptime_Seconds += elapsed_seconds; + dcc_timer_seconds(elapsed_seconds); + datalink_maintenance_timer(elapsed_seconds); + handler_cov_timer_seconds(elapsed_seconds); + } + while (!handler_cov_fsm()) { + /* waiting for COV processing to be IDLE */ + } + /* object specific cyclic tasks */ + if (mstimer_expired(&BACnet_Object_Timer)) { + mstimer_reset(&BACnet_Object_Timer); + elapsed_milliseconds = mstimer_interval(&BACnet_Object_Timer); + Device_Timer(elapsed_milliseconds); + } + /* handle the messaging */ + pdu_len = datalink_receive(&src, &PDUBuffer[0], sizeof(PDUBuffer), 0); + if (pdu_len) { + npdu_handler(&src, &PDUBuffer[0], pdu_len); + BACnet_Packet_Count++; + } +} diff --git a/zephyr/subsys/bacnet_basic/bacnet_port.c b/zephyr/subsys/bacnet_basic/bacnet_port.c new file mode 100644 index 00000000..82f87ddf --- /dev/null +++ b/zephyr/subsys/bacnet_basic/bacnet_port.c @@ -0,0 +1,66 @@ +/** + * @file + * @brief The BACnet/IPv4 datalink tasks for handling the device specific + * data link layer + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +/* BACnet definitions */ +#include "bacnet/bacdef.h" +/* BACnet library API */ +#include "bacnet/basic/sys/mstimer.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/basic/object/netport.h" +#if defined(BACDL_BIP) +#include "bacnet_basic/bacnet_port_ipv4.h" +#elif defined(BACDL_BIP6) +#include "bacnet_basic/bacnet_port_ipv6.h" +#endif +/* me! */ +#include "bacnet_basic/bacnet_port.h" + +/* timer used to renew Foreign Device Registration */ +static struct mstimer BACnet_Task_Timer; + +/** + * @brief Periodic tasks for the BACnet datalink layer + */ +void bacnet_port_task(void) +{ + uint32_t elapsed_milliseconds = 0; + uint32_t elapsed_seconds = 0; + + if (mstimer_expired(&BACnet_Task_Timer)) { + /* 1 second tasks */ + mstimer_reset(&BACnet_Task_Timer); + /* presume that the elapsed time is the interval time */ + elapsed_milliseconds = mstimer_interval(&BACnet_Task_Timer); + elapsed_seconds = elapsed_milliseconds / 1000; +#if defined(BACDL_BIP) + bacnet_port_ipv4_task(elapsed_seconds); +#elif defined(BACDL_BIP6) + bacnet_port_ipv6_task(elapsed_seconds); +#endif + } +} + +/** + * @brief Initialize the datalink network port +*/ +bool bacnet_port_init(void) +{ + bool status = false; + /* start the 1 second timer for non-critical cyclic tasks */ + mstimer_set(&BACnet_Task_Timer, 1000L); +#if defined(BACDL_BIP) + status = bacnet_port_ipv4_init(); +#elif defined(BACDL_BIP6) + status = bacnet_port_ipv6_init(); +#endif + return status; +} diff --git a/zephyr/subsys/bacnet_basic/bacnet_port_ipv4.c b/zephyr/subsys/bacnet_basic/bacnet_port_ipv4.c new file mode 100644 index 00000000..a481a139 --- /dev/null +++ b/zephyr/subsys/bacnet_basic/bacnet_port_ipv4.c @@ -0,0 +1,101 @@ +/** + * @file + * @brief The BACnet/IPv4 datalink tasks for handling the device specific + * data link layer + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +/* BACnet definitions */ +#include "bacnet/bacdef.h" +/* BACnet library API */ +#include "bacnet/basic/object/netport.h" +#include "bacnet/basic/bbmd/h_bbmd.h" +#include "bacnet/datalink/bip.h" +#include "bacnet/datalink/bvlc.h" +#include "bacnet/datalink/datalink.h" +/* me! */ +#include "bacnet_basic/bacnet_port_ipv4.h" + +#if defined(BACDL_BIP) + +/* timer used to renew Foreign Device Registration */ +static uint16_t BBMD_Timer_Seconds; +static uint16_t BBMD_TTL_Seconds = 60000; +static BACNET_IP_ADDRESS BBMD_Address; + +/** + * @brief Initialize the datalink network port + * @param ttl_seconds [in] The time-to-live in seconds for the Foreign Device Registration + * @param bbmd_address [in] The address of the BBMD + */ +void bacnet_port_ipv4_foreign_device_init( + const uint16_t ttl_seconds, + const BACNET_IP_ADDRESS *bbmd_address) +{ + BBMD_TTL_Seconds = ttl_seconds; + if (bbmd_address) { + memcpy(&BBMD_Address, bbmd_address, sizeof(BACNET_IP_ADDRESS)); + } +} + +/** + * @brief Renew the Foreign Device Registration + */ +void bacnet_port_ipv4_task(uint16_t elapsed_seconds) +{ + if (BBMD_Timer_Seconds) { + if (BBMD_Timer_Seconds <= elapsed_seconds) { + BBMD_Timer_Seconds = 0; + } else { + BBMD_Timer_Seconds -= elapsed_seconds; + } + if (BBMD_Timer_Seconds == 0) { + if (BBMD_Address.port > 0) { + (void)bvlc_register_with_bbmd(&BBMD_Address, + BBMD_TTL_Seconds); + } + BBMD_Timer_Seconds = (uint16_t)BBMD_TTL_Seconds; + } + } +} + +/** + * Initialize the network port object. +*/ +bool bacnet_port_ipv4_init(void) +{ + const uint32_t instance = 1; + BACNET_IP_ADDRESS addr = { 0 }; + uint8_t prefix = 0; + + if (!bip_init(NULL)) { + return false; + } + Network_Port_Object_Instance_Number_Set(0, instance); + Network_Port_Name_Set(instance, "BACnet/IP Port"); + Network_Port_Type_Set(instance, PORT_TYPE_BIP); + bip_get_addr(&addr); + prefix = bip_get_subnet_prefix(); + Network_Port_BIP_Port_Set(instance, addr.port); + Network_Port_IP_Address_Set(instance, addr.address[0], addr.address[1], + addr.address[2], addr.address[3]); + Network_Port_IP_Subnet_Prefix_Set(instance, prefix); + Network_Port_Link_Speed_Set(instance, 0.0); + /* common NP data */ + Network_Port_Reliability_Set(instance, RELIABILITY_NO_FAULT_DETECTED); + Network_Port_Out_Of_Service_Set(instance, false); + Network_Port_Quality_Set(instance, PORT_QUALITY_UNKNOWN); + Network_Port_APDU_Length_Set(instance, MAX_APDU); + Network_Port_Network_Number_Set(instance, 0); + /* last thing - clear pending changes - we don't want to set these + since they are already set */ + Network_Port_Changes_Pending_Set(instance, false); + + return true; +} +#endif diff --git a/zephyr/subsys/bacnet_basic/bacnet_port_ipv6.c b/zephyr/subsys/bacnet_basic/bacnet_port_ipv6.c new file mode 100644 index 00000000..91f52f2a --- /dev/null +++ b/zephyr/subsys/bacnet_basic/bacnet_port_ipv6.c @@ -0,0 +1,104 @@ +/** + * @file + * @brief The BACnet datalink tasks for handling the device specific + * data link layer + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +/* BACnet definitions */ +#include "bacnet/bacdef.h" +/* BACnet library API */ +#include "bacnet/basic/object/netport.h" +#include "bacnet/basic/bbmd6/h_bbmd6.h" +#include "bacnet/datalink/bip6.h" +#include "bacnet/datalink/bvlc6.h" +#include "bacnet/datalink/datalink.h" +/* me! */ +#include "bacnet_basic/bacnet_port_ipv6.h" + +#if defined(BACDL_BIP6) + +/* timer used to renew Foreign Device Registration */ +static uint16_t BBMD_Timer_Seconds; +static uint16_t BBMD_TTL_Seconds = 60000; +static BACNET_IP6_ADDRESS BBMD_Address; + +/** + * @brief Initialize the datalink network port + * @param ttl_seconds [in] The time-to-live in seconds for the Foreign Device Registration + * @param bbmd_address [in] The address of the BBMD + */ +void bacnet_port_ipv6_foreign_device_init( + const uint16_t ttl_seconds, const BACNET_IP6_ADDRESS *bbmd_address) +{ + BBMD_TTL_Seconds = ttl_seconds; + if (bbmd_address) { + memcpy(&BBMD_Address, bbmd_address, sizeof(BACNET_IP6_ADDRESS)); + } +} + +/** + * @brief Renew the Foreign Device Registration + */ +void bacnet_port_ipv6_task(uint16_t elapsed_seconds) +{ + if (BBMD_Timer_Seconds) { + if (BBMD_Timer_Seconds <= elapsed_seconds) { + BBMD_Timer_Seconds = 0; + } else { + BBMD_Timer_Seconds -= elapsed_seconds; + } + if (BBMD_Timer_Seconds == 0) { + if (BBMD_Address.port > 0) { + (void)bvlc6_register_with_bbmd(&BBMD_Address, + BBMD_TTL_Seconds); + } + BBMD_Timer_Seconds = BBMD_TTL_Seconds; + } + } +} + +/** + * Initialize the network port object. + * @return true if successful +*/ +bool bacnet_port_ipv6_init(void) +{ + uint32_t instance = 1; + uint8_t prefix = 0; + BACNET_ADDRESS addr = { 0 }; + BACNET_IP6_ADDRESS addr6 = { 0 }; + + if (!bip6_init(NULL)) { + return false; + } + Network_Port_Object_Instance_Number_Set(0, instance); + Network_Port_Name_Set(instance, "BACnet/IPv6 Port"); + Network_Port_Type_Set(instance, PORT_TYPE_BIP6); + Network_Port_BIP6_Port_Set(instance, bip6_get_port()); + bip6_get_my_address(&addr); + Network_Port_MAC_Address_Set(instance, &addr.mac[0], addr.mac_len); + bip6_get_addr(&addr6); + Network_Port_IPv6_Address_Set(instance, &addr6.address[0]); + bip6_get_broadcast_addr(&addr6); + Network_Port_IPv6_Multicast_Address_Set(instance, &addr6.address[0]); + Network_Port_IPv6_Subnet_Prefix_Set(instance, prefix); + + Network_Port_Reliability_Set(instance, RELIABILITY_NO_FAULT_DETECTED); + Network_Port_Link_Speed_Set(instance, 0.0); + Network_Port_Out_Of_Service_Set(instance, false); + Network_Port_Quality_Set(instance, PORT_QUALITY_UNKNOWN); + Network_Port_APDU_Length_Set(instance, MAX_APDU); + Network_Port_Network_Number_Set(instance, 0); + /* last thing - clear pending changes - we don't want to set these + since they are already set */ + Network_Port_Changes_Pending_Set(instance, false); + + return true; +} +#endif diff --git a/zephyr/subsys/bacnet_basic/bacnet_shell_objects.c b/zephyr/subsys/bacnet_basic/bacnet_shell_objects.c new file mode 100644 index 00000000..5621bb88 --- /dev/null +++ b/zephyr/subsys/bacnet_basic/bacnet_shell_objects.c @@ -0,0 +1,57 @@ +/** + * @file + * @brief BACnet shell commands for debugging and testing + * @author Steve Karg + * @date May 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +/* BACnet definitions */ +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bactext.h" +#include "bacnet/bacapp.h" +/* BACnet objects API */ +#include "bacnet/basic/object/device.h" +/* Basic BACnet */ +#include "bacnet_basic/bacnet_basic.h" + +/** + * @brief List all BACnet objects in this device + * @param sh Shell + * @param argc Number of arguments + * @param argv Argument list + * @return 0 on success, negative on failure + */ +static int cmd_objects(const struct shell *sh, size_t argc, char **argv) +{ + int count; + BACNET_OBJECT_TYPE object_type; + uint32_t instance; + uint32_t array_index; + bool found; + + (void)argc; + (void)argv; + shell_print(sh, "List of BACnet Objects: [{"); + count = Device_Object_List_Count(); + for (array_index = 1; array_index <= count; array_index++) { + found = Device_Object_List_Identifier(array_index, &object_type, + &instance); + if (found) { + shell_print(sh, " \"%s-%u\"%c", + bactext_object_type_name(object_type), + instance, + (array_index == count) ? ' ' : ','); + } + } + shell_print(sh, "}] -- %d objects found", count); + + return 0; +} + +SHELL_SUBCMD_ADD((bacnet), objects, NULL, "list of BACnet objects", cmd_objects, + 1, 0); diff --git a/zephyr/subsys/bacnet_basic/bacnet_shell_packets.c b/zephyr/subsys/bacnet_basic/bacnet_shell_packets.c new file mode 100644 index 00000000..1b708f9a --- /dev/null +++ b/zephyr/subsys/bacnet_basic/bacnet_shell_packets.c @@ -0,0 +1,38 @@ +/** + * @file + * @brief The BACnet shell commands for debugging and testing + * @author Steve Karg + * @date May 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +/* BACnet definitions */ +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bactext.h" +#include "bacnet/bacapp.h" +/* Basic BACnet */ +#include "bacnet_basic/bacnet_basic.h" + +/** + * @brief Print BACnet packet statistics + * @param sh Shell + * @param argc Number of arguments + * @param argv Argument list + * @return 0 on success, negative on failure + */ +static int cmd_packets(const struct shell *sh, size_t argc, char **argv) +{ + (void)argc; + (void)argv; + shell_print(sh, "BACnet thread packets received: %ld", + bacnet_basic_packet_count()); + + return 0; +} + +SHELL_SUBCMD_ADD((bacnet), packets, NULL, "BACnet task packet stats", cmd_packets, + 1, 0); diff --git a/zephyr/subsys/bacnet_basic/bacnet_shell_uptime.c b/zephyr/subsys/bacnet_basic/bacnet_shell_uptime.c new file mode 100644 index 00000000..8552bd26 --- /dev/null +++ b/zephyr/subsys/bacnet_basic/bacnet_shell_uptime.c @@ -0,0 +1,38 @@ +/** + * @file + * @brief The BACnet shell commands for debugging and testing + * @author Steve Karg + * @date May 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +/* BACnet definitions */ +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bactext.h" +#include "bacnet/bacapp.h" +/* Basic BACnet */ +#include "bacnet_basic/bacnet_basic.h" + +/** + * @brief Print BACnet uptime statistics + * @param sh Shell + * @param argc Number of arguments + * @param argv Argument list + * @return 0 on success, negative on failure + */ +static int cmd_uptime(const struct shell *sh, size_t argc, char **argv) +{ + (void)argc; + (void)argv; + shell_print(sh, "BACnet thread uptime: %ld seconds", + bacnet_basic_uptime_seconds()); + + return 0; +} + +SHELL_SUBCMD_ADD((bacnet), uptime, NULL, "BACnet task uptime", cmd_uptime, + 1, 0); diff --git a/zephyr/subsys/bacnet_basic/device.c b/zephyr/subsys/bacnet_basic/device.c new file mode 100644 index 00000000..2c08a073 --- /dev/null +++ b/zephyr/subsys/bacnet_basic/device.c @@ -0,0 +1,1460 @@ +/** + * @file + * @brief Base "class" for handling all BACnet objects belonging + * to a BACnet device, as well as Device-specific properties. + * @author Steve Karg + * @date March 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacstr.h" +#include "bacnet/bacenum.h" +#include "bacnet/apdu.h" +#include "bacnet/dcc.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/property.h" +#include "bacnet/version.h" +#include "bacnet/basic/services.h" +/* objects */ +#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/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/device.h" + +static object_functions_t Object_Table[] = { + { OBJECT_DEVICE, NULL, /* don't init - recursive! */ + 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, + NULL /* ReadRangeInfo */, NULL /* Iterator */, + NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, + NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, + NULL /* Remove_List_Element */, NULL /* Create */, NULL /* Delete */, + NULL /* Timer */ }, +#if defined (CONFIG_BACNET_BASIC_OBJECT_ANALOG_INPUT) + { 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, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Analog_Input_Create, Analog_Input_Delete, NULL /* Timer */ }, +#endif +#if defined (CONFIG_BACNET_BASIC_OBJECT_ANALOG_OUTPUT) + { 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 */, + Analog_Output_Encode_Value_List, Analog_Output_Change_Of_Value, + Analog_Output_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Analog_Output_Create, Analog_Output_Delete, NULL /* Timer */ }, +#endif +#if defined (CONFIG_BACNET_BASIC_OBJECT_ANALOG_VALUE) + { 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, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Analog_Value_Create, Analog_Value_Delete, NULL /* Timer */ }, +#endif +#if defined (CONFIG_BACNET_BASIC_OBJECT_BINARY_INPUT) + { 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 */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Binary_Input_Create, Binary_Input_Delete, NULL /* Timer */ }, +#endif +#if defined (CONFIG_BACNET_BASIC_OBJECT_BINARY_OUTPUT) + { 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 */, + Binary_Output_Encode_Value_List, Binary_Output_Change_Of_Value, + Binary_Output_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Binary_Output_Create, Binary_Output_Delete, NULL /* Timer */ }, +#endif +#if defined (CONFIG_BACNET_BASIC_OBJECT_BINARY_VALUE) + { 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 */, + Binary_Value_Encode_Value_List, Binary_Value_Change_Of_Value, + Binary_Value_Change_Of_Value_Clear, + NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Binary_Value_Create, Binary_Value_Delete, NULL /* Timer */ }, +#endif +#if defined (CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_INPUT) + { 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 */, + Multistate_Input_Encode_Value_List, Multistate_Input_Change_Of_Value, + Multistate_Input_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Multistate_Input_Create, Multistate_Input_Delete, NULL /* Timer */ }, +#endif +#if defined (CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_OUTPUT) + { 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 */, + Multistate_Output_Encode_Value_List, Multistate_Output_Change_Of_Value, + Multistate_Output_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Multistate_Output_Create, Multistate_Output_Delete, NULL /* Timer */ }, +#endif +#if defined (CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_VALUE) + { 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 */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Multistate_Value_Create, Multistate_Value_Delete, NULL /* Timer */ }, +#endif +#if defined (CONFIG_BACNET_BASIC_OBJECT_NETWORK_PORT) +#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 */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, +#else +#warning "Network Port is configured, but BACnet Protocol Revision < 17" +#endif +#endif + { 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 */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ } +}; + +/* local data */ +static const char *Application_Software_Version = + CONFIG_BACNET_BASIC_DEVICE_OBJECT_VERSION; +static uint32_t Object_Instance_Number = BACNET_MAX_INSTANCE; +static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL; +static BACNET_CHARACTER_STRING My_Object_Name; +static const char *Device_Name_Default = "Basic Device"; +static const char *Device_Description_Default = "BACnet Basic Device"; +static const char *Model_Name = "Basic Device"; +static uint32_t Database_Revision; +static BACNET_REINITIALIZED_STATE Reinitialize_State = BACNET_REINIT_IDLE; +static BACNET_CHARACTER_STRING Reinit_Password; +static const char *BACnet_Version = BACNET_VERSION_TEXT; + +/* 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[] = { PROP_DESCRIPTION, + PROP_LOCATION, +#if defined(BACDL_MSTP) + PROP_MAX_MASTER, PROP_MAX_INFO_FRAMES, +#endif + -1 }; + +static const int Device_Properties_Proprietary[] = { -1 }; + +/** 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[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + /* handle each object type */ + if (pObject->Object_Type == Object_Type) { + return (pObject); + } + + pObject++; + } + + return (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; +} + +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; +} + +/** + * @brief Determine if the object property is a member of this object instance + * @param object_instance - object-instance number of the object + * @param object_property - object-property to be checked + * @return true if the property is a member of this object instance + */ +static bool Property_List_Member( + uint32_t object_instance, int object_property) +{ + bool found = false; + const int *pRequired = NULL; + const int *pOptional = NULL; + const int *pProprietary = NULL; + + (void)object_instance; + Device_Property_Lists( + &pRequired, &pOptional, &pProprietary); + found = property_list_member(pRequired, object_property); + if (!found) { + found = property_list_member(pOptional, object_property); + } + if (!found) { + found = property_list_member(pProprietary, object_property); + } + + return found; +} + +/** + * @brief Determine if the object property is a member of this object instance + * @param object_type - object type of the object + * @param object_instance - object-instance number of the object + * @param object_property - object-property to be checked + * @return true if the property is a member of this object instance + */ +bool Device_Objects_Property_List_Member(BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + int object_property) +{ + bool found = false; + struct special_property_list_t property_list = { 0 }; + + Device_Objects_Property_List(object_type, object_instance, &property_list); + found = property_list_member(property_list.Required.pList, object_property); + if (!found) { + found = + property_list_member(property_list.Optional.pList, object_property); + } + if (!found) { + found = property_list_member(property_list.Proprietary.pList, + object_property); + } + + return found; +} + +/** + * @brief Sets the ReinitializeDevice password + * + * The password shall be a null terminated C string of up to + * 20 ASCII characters for those devices that require the password. + * + * For those devices that do not require a password, set to NULL or + * point to a zero length C string (null terminated). + * + * @param the ReinitializeDevice password; can be NULL or empty string + */ +bool Device_Reinitialize_Password_Set(const char *password) +{ + characterstring_init_ansi(&Reinit_Password, password); + + return true; +} + +/** + * @brief 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; + bool password_success = false; + + /* From 16.4.1.1.2 Password + This optional parameter shall be a CharacterString of up to + 20 characters. For those devices that require the password as a + protection, the service request shall be denied if the parameter + is absent or if the password is incorrect. For those devices that + do not require a password, this parameter shall be ignored.*/ + if (characterstring_length(&Reinit_Password) > 0) { + if (characterstring_length(&rd_data->password) > 20) { + rd_data->error_class = ERROR_CLASS_SERVICES; + rd_data->error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; + } else if (characterstring_same( + &rd_data->password, &Reinit_Password)) { + password_success = true; + } else { + rd_data->error_class = ERROR_CLASS_SECURITY; + rd_data->error_code = ERROR_CODE_PASSWORD_FAILURE; + } + } else { + password_success = true; + } + if (password_success) { + switch (rd_data->state) { + case BACNET_REINIT_COLDSTART: + case BACNET_REINIT_WARMSTART: + dcc_set_status_duration(COMMUNICATION_ENABLE, 0); + /* Note: you could use a mix of state + and password to multiple things */ + /* 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; + } + } + + 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; +} + +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; +} + +/* 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) +{ + return Object_Instance_Number; +} + +bool Device_Set_Object_Instance_Number(uint32_t object_id) +{ + bool status = true; /* return value */ + + if (object_id <= BACNET_MAX_INSTANCE) { + Object_Instance_Number = object_id; + } else + status = false; + + return status; +} + +bool Device_Valid_Object_Instance_Number(uint32_t object_id) +{ + return (Object_Instance_Number == object_id); +} + +BACNET_DEVICE_STATUS Device_System_Status(void) +{ + return System_Status; +} + +int Device_Set_System_Status(BACNET_DEVICE_STATUS status, bool local) +{ + /*return value - 0 = ok, -1 = bad value, -2 = not allowed */ + int result = -1; + + (void)local; + if (status < MAX_DEVICE_STATUS) { + System_Status = status; + result = 0; + } + + return result; +} + +uint16_t Device_Vendor_Identifier(void) +{ + return BACNET_VENDOR_ID; +} + +BACNET_SEGMENTATION Device_Segmentation_Supported(void) +{ + return SEGMENTATION_NONE; +} + +/** + * @brief Get the Database Revision property of the Device Object + * @return The Database Revision property of the Device Object + */ +uint32_t Device_Database_Revision(void) +{ + return Database_Revision; +} + +/** + * @brief Set the Database Revision property of the Device Object + * @param revision [in] The new value for the Database Revision property + */ +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[0]; + 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; + 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[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count && pObject->Object_Index_To_Instance) { + object_index -= count; + count = pObject->Object_Count(); + if (object_index < count) { + *object_type = pObject->Object_Type; + *instance = pObject->Object_Index_To_Instance(object_index); + status = true; + break; + } + } + pObject++; + } + + return status; +} + +/** + * @brief Encode a BACnetARRAY property element + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index requested: + * 0 to N for individual array members + * @param apdu [out] Buffer in which the APDU contents are built, or NULL to + * return the length of buffer if it had been built + * @return The length of the apdu encoded or + * BACNET_STATUS_ERROR for ERROR_CODE_INVALID_ARRAY_INDEX + */ +int Device_Object_List_Element_Encode( + uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu) +{ + int apdu_len = BACNET_STATUS_ERROR; + BACNET_OBJECT_TYPE object_type; + uint32_t instance; + bool found; + + if (object_instance == Device_Object_Instance_Number()) { + /* single element is zero based, add 1 for BACnetARRAY which is one + * based */ + array_index++; + found = + Device_Object_List_Identifier(array_index, &object_type, &instance); + if (found) { + apdu_len = + encode_application_object_id(apdu, object_type, instance); + } + } + + return apdu_len; +} + +/** 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((BACNET_OBJECT_TYPE)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((BACNET_OBJECT_TYPE)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; +} + +/* 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 */ + BACNET_BIT_STRING bit_string = { 0 }; + BACNET_CHARACTER_STRING char_string = { 0 }; + uint32_t i = 0; + uint32_t count = 0; + uint8_t *apdu = NULL; + struct object_functions *pObject = NULL; + 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 ((int)rpdata->object_property) { + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, Device_Description_Default); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_LOCATION: + characterstring_init_ansi(&char_string, "USA"); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_SYSTEM_STATUS: + apdu_len = + encode_application_enumerated(&apdu[0], Device_System_Status()); + break; + case PROP_VENDOR_NAME: + characterstring_init_ansi(&char_string, BACNET_VENDOR_NAME); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_VENDOR_IDENTIFIER: + apdu_len = encode_application_unsigned(&apdu[0], BACNET_VENDOR_ID); + 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_PROTOCOL_VERSION: + apdu_len = + encode_application_unsigned(&apdu[0], BACNET_PROTOCOL_VERSION); + break; + case PROP_PROTOCOL_REVISION: + apdu_len = + encode_application_unsigned(&apdu[0], BACNET_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, pObject->Object_Type, true); + } + pObject++; + } + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + case PROP_OBJECT_LIST: + count = Device_Object_List_Count(); + apdu_len = bacnet_array_encode(rpdata->object_instance, + rpdata->array_index, Device_Object_List_Element_Encode, count, + apdu, apdu_max); + if (apdu_len == BACNET_STATUS_ABORT) { + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + } else if (apdu_len == BACNET_STATUS_ERROR) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + } + 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: + /* FIXME: encode the list here, if it exists */ + break; + case PROP_DATABASE_REVISION: + apdu_len = encode_application_unsigned( + &apdu[0], Device_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 + 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 common Object and Property, and encodes its Value in an + * APDU. Sets the error class and code if request is not appropriate. + * @param pObject - object table + * @param rpdata [in,out] Structure with the requested Object & Property info + * on entry, and APDU message on return. + * @return The length of the APDU on success, else BACNET_STATUS_ERROR + */ +static int Read_Property_Common( + struct object_functions *pObject, BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = BACNET_STATUS_ERROR; + BACNET_CHARACTER_STRING char_string; + uint8_t *apdu = NULL; +#if (BACNET_PROTOCOL_REVISION >= 14) + struct special_property_list_t property_list; +#endif + + if ((rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + apdu = rpdata->application_data; + switch (rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + /* only array properties can have array options */ + if (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; + } else { + /* Device Object exception: requested instance + may not match our instance if a wildcard */ + if (rpdata->object_type == OBJECT_DEVICE) { + rpdata->object_instance = Object_Instance_Number; + } + apdu_len = encode_application_object_id( + &apdu[0], rpdata->object_type, rpdata->object_instance); + } + break; + case PROP_OBJECT_NAME: + /* only array properties can have array options */ + if (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; + } else { + characterstring_init_ansi(&char_string, ""); + if (pObject->Object_Name) { + (void)pObject->Object_Name( + rpdata->object_instance, &char_string); + } + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + } + break; + case PROP_OBJECT_TYPE: + /* only array properties can have array options */ + if (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; + } else { + apdu_len = encode_application_enumerated( + &apdu[0], rpdata->object_type); + } + break; +#if (BACNET_PROTOCOL_REVISION >= 14) + case 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); + break; +#endif + default: + if (pObject->Object_Read_Property) { + apdu_len = pObject->Object_Read_Property(rpdata); + } + break; + } + + 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; + + /* 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) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(rpdata->object_instance)) { + apdu_len = Read_Property_Common(pObject, rpdata); + } else { + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + bool status = false; /* return value - false=error */ + int len = 0; + uint8_t encoding = 0; + size_t length = 0; + BACNET_APPLICATION_DATA_VALUE value; + + /* decode the some of the request */ + len = bacapp_decode_application_data( + wp_data->application_data, wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + 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; + } + switch ((int)wp_data->object_property) { + case PROP_OBJECT_IDENTIFIER: + if (value.tag == BACNET_APPLICATION_TAG_OBJECT_ID) { + if ((value.type.Object_Id.type == OBJECT_DEVICE) && + (Device_Set_Object_Instance_Number( + value.type.Object_Id.instance))) { + /* we could send an I-Am broadcast to let the world know */ + 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: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if (value.type.Unsigned_Int <= 255) { + dlmstp_set_max_info_frames(value.type.Unsigned_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; + case PROP_MAX_MASTER: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if ((value.type.Unsigned_Int > 0) && + (value.type.Unsigned_Int <= 127)) { + dlmstp_set_max_master(value.type.Unsigned_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; +#endif + case PROP_OBJECT_NAME: + if (value.tag == BACNET_APPLICATION_TAG_CHARACTER_STRING) { + length = characterstring_length(&value.type.Character_String); + if (length < 1) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } else if (length < characterstring_capacity(&My_Object_Name)) { + encoding = + characterstring_encoding(&value.type.Character_String); + if (encoding < MAX_CHARACTER_STRING_ENCODING) { + /* All the object names in a device must be unique. */ + if (Device_Valid_Object_Name( + &value.type.Character_String, NULL, NULL)) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_DUPLICATE_NAME; + } else { + Device_Set_Object_Name( + &value.type.Character_String); + status = true; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = + ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + if (Property_List_Member( + wp_data->object_instance, wp_data->object_property)) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + } + break; + } + /* not using len at this time */ + (void)len; + + return status; +} + +/** + * @brief Handles the writing of the object name property + * @param wp_data [in,out] WriteProperty data structure + * @param Object_Write_Property object specific function to write the property + * @return True on success, else False if there is an error. + */ +static bool Device_Write_Property_Object_Name( + BACNET_WRITE_PROPERTY_DATA *wp_data, + write_property_function Object_Write_Property) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_CHARACTER_STRING value; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t object_instance = 0; + int apdu_size = 0; + uint8_t *apdu = NULL; + + if (!wp_data) { + return false; + } + if (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; + } + apdu = wp_data->application_data; + apdu_size = wp_data->application_data_len; + len = bacnet_character_string_application_decode(apdu, apdu_size, &value); + if (len > 0) { + if ((characterstring_encoding(&value) != CHARACTER_ANSI_X34) || + (characterstring_length(&value) == 0) || + (!characterstring_printable(&value))) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } else { + status = true; + } + } else if (len == 0) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + if (status) { + /* All the object names in a device must be unique */ + if (Device_Valid_Object_Name(&value, &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 { + /* name already exists in some object */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_DUPLICATE_NAME; + status = false; + } + } else { + status = Object_Write_Property(wp_data); + } + } + + return status; +} + +void Device_Write_Property_Store(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + BACNET_ARRAY_INDEX array_index; + BACNET_PROPERTY_ID object_property; + + if (property_list_bacnet_array_member(wp_data->object_type, + wp_data->object_property)) { + array_index = wp_data->array_index; + object_property = wp_data->object_property; + } else if (wp_data->object_property == PROP_PRESENT_VALUE) { + /* indirect Priority_Array write */ + if (Device_Objects_Property_List_Member(wp_data->object_type, + wp_data->object_instance, + PROP_PRIORITY_ARRAY)) { + array_index = wp_data->priority; + object_property = PROP_PRIORITY_ARRAY; + } else { + object_property = wp_data->object_property; + array_index = BACNET_ARRAY_ALL; + } + } else { + object_property = wp_data->object_property; + array_index = wp_data->array_index; + } + //bacnet_data_store(wp_data->object_type, wp_data->object_instance, + // object_property, array_index, wp_data->application_data, + // wp_data->application_data_len); +} + +/** 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; + 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) { + 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; + return status; + } +#endif + if (wp_data->object_property == PROP_OBJECT_NAME) { + status = Device_Write_Property_Object_Name( + wp_data, pObject->Object_Write_Property); + } else { + status = pObject->Object_Write_Property(wp_data); + } + if (status) { + Device_Write_Property_Store(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 was encoded correctly + */ +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); + } + } + } +} + +/** + * @brief Updates all the object timers with elapsed milliseconds + * @param milliseconds - number of milliseconds elapsed + */ +void Device_Timer(uint16_t milliseconds) +{ + struct object_functions *pObject; + unsigned count = 0; + uint32_t instance; + + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count) { + count = pObject->Object_Count(); + } + while (count) { + count--; + if ((pObject->Object_Timer) && + (pObject->Object_Index_To_Instance)) { + instance = pObject->Object_Index_To_Instance(count); + pObject->Object_Timer(instance, milliseconds); + } + } + pObject++; + } +} + +/** 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; + + /* we don't use the object table passed in + since there is extra stuff we don't need in there. */ + (void)object_table; + /* our local object table */ + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Init) { + pObject->Object_Init(); + } + pObject++; + } + dcc_set_status_duration(COMMUNICATION_ENABLE, 0); + if (Object_Instance_Number > BACNET_MAX_INSTANCE) { + Object_Instance_Number = BACNET_MAX_INSTANCE; + } + characterstring_init_ansi(&My_Object_Name, Device_Name_Default); +} diff --git a/zephyr/subsys/bacnet_basic/server.c b/zephyr/subsys/bacnet_basic/server.c new file mode 100644 index 00000000..2c6ee4cb --- /dev/null +++ b/zephyr/subsys/bacnet_basic/server.c @@ -0,0 +1,91 @@ +/** + * @file + * @brief BACnet Stack server initialization and task handler + * @author Steve Karg + * @date March 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include /*TODO: Not std until C11! */ +#include +#include +#include +#include +#include +#include +#include "bacnet/datalink/datalink.h" +#include "bacnet_basic/bacnet_basic.h" +#include "bacnet_basic/bacnet_port.h" +#if defined(CONFIG_BACNETSTACK_BACNET_SETTINGS) +#include "bacnet_settings/bacnet_storage.h" +#endif + +/* note: stack is minimally 2x to 3x of MAX_APDU */ +#ifndef CONFIG_BACNETSTACK_BACNET_SERVER_STACK_SIZE +#define CONFIG_BACNETSTACK_BACNET_SERVER_STACK_SIZE 4096 +#endif + +#ifndef CONFIG_BACNETSTACK_BACNET_SERVER_PRIO +#define CONFIG_BACNETSTACK_BACNET_SERVER_PRIO 10 +#endif + +#ifndef CONFIG_BACNETSTACK_BACNET_SERVER_APP_PRIORITY +#define CONFIG_BACNETSTACK_BACNET_SERVER_APP_PRIORITY 90 +#endif + +#ifndef CONFIG_BACNETSTACK_LOG_LEVEL +#define CONFIG_BACNETSTACK_LOG_LEVEL LOG_LEVEL_INF +#endif + +/* Logging module registration is already done in ports/zephyr/main.c */ +#include +LOG_MODULE_DECLARE(bacnet, CONFIG_BACNETSTACK_LOG_LEVEL); + +static struct k_thread server_thread_data; +static K_THREAD_STACK_DEFINE(server_thread_stack, + CONFIG_BACNETSTACK_BACNET_SERVER_STACK_SIZE); + +/** + * @brief BACnet Server Thread + */ +static void server_thread(void) +{ + LOG_INF("BACnet Server: started"); + +#if defined(CONFIG_BACNETSTACK_BACNET_SETTINGS) + bacnet_storage_init(); +#endif + bacnet_basic_init(); + for (;;) { + if (bacnet_port_init()) { + break; + } else { + LOG_ERR("BACnet Server: port initialization failed"); + k_sleep(K_MSEC(1000)); + } + } + LOG_INF("BACnet Server: initialized"); + for (;;) { + k_sleep(K_MSEC(10)); + bacnet_basic_task(); + bacnet_port_task(); + } +} + +/** + * @brief BACnet Server Thread initialization + */ +static int server_init(void) +{ + 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, "bacnet_server"); + + return 0; +} + +SYS_INIT(server_init, APPLICATION, + CONFIG_BACNETSTACK_BACNET_SERVER_APP_PRIORITY); diff --git a/zephyr/subsys/bacnet_settings/CMakeLists.txt b/zephyr/subsys/bacnet_settings/CMakeLists.txt new file mode 100644 index 00000000..6d5596eb --- /dev/null +++ b/zephyr/subsys/bacnet_settings/CMakeLists.txt @@ -0,0 +1,17 @@ +# CMake for BACnet settings library +# +# @author Steve Karg +# @date May 2024 +# @copyright SPDX-License-Identifier: MIT +zephyr_library(bacnet_settings) + +zephyr_library_include_directories(include) + +zephyr_library_sources( + bacnet_storage.c + bacnet_settings.c + ) + +zephyr_library_sources_ifdef(CONFIG_BACNET_SETTINGS_SHELL + bacnet_shell.c + ) diff --git a/zephyr/subsys/bacnet_settings/Kconfig b/zephyr/subsys/bacnet_settings/Kconfig new file mode 100644 index 00000000..64687903 --- /dev/null +++ b/zephyr/subsys/bacnet_settings/Kconfig @@ -0,0 +1,28 @@ +# Kconfig - Subsystem configuration options +# +# @author Steve Karg +# @date May 2024 +# @copyright SPDX-License-Identifier: MIT +menuconfig BACNETSTACK_BACNET_SETTINGS + bool "BACNETSTACK_BACNET_SETTINGS" + default y if BACNETSTACK && SETTINGS + help + This option enables BACnet Settings services + +if BACNETSTACK_BACNET_SETTINGS + + module = BACNETSTACK_BACNET_SETTINGS + module-str = bac_settings + + config BACNET_SETTINGS_BASE_NAME + string "BACnet object path base name for every setting" + default ".bacnet" + help + BACnet object path base name for every setting" + + config BACNET_SETTINGS_SHELL + bool "BACnet settings subsystem shell" + depends on BACNETSTACK + default y if SHELL && SETTINGS + +endif # BACNETSTACK_BACNET_SETTINGS diff --git a/zephyr/subsys/bacnet_settings/bacnet_settings.c b/zephyr/subsys/bacnet_settings/bacnet_settings.c new file mode 100644 index 00000000..d6bba678 --- /dev/null +++ b/zephyr/subsys/bacnet_settings/bacnet_settings.c @@ -0,0 +1,379 @@ +/** + * @file + * @brief Handle Get/Set of BACnet application encoded settings + * @date May 2024 + * @author Steve Karg + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include "bacnet/bacdef.h" +#include "bacnet/bacapp.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacstr.h" +#include "bacnet/bacint.h" + +/** + * @brief Get a BACnet SIGNED INTEGER value from non-volatile storage + * @param object_type [in] The BACnet object type + * @param object_instance [in] The BACnet object instance + * @param property_id [in] The BACnet property id + * @param array_index [in] The BACnet array index + * @param default_value [in] The default value if not found + * @return stored data length on success 0..N, negative on failure. + */ +int bacnet_settings_value_get(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + BACNET_APPLICATION_DATA_VALUE *value) +{ + uint8_t name[BACNET_STORAGE_VALUE_SIZE_MAX + 1] = { 0 }; + BACNET_STORAGE_KEY key = { 0 }; + int stored_len, len; + + bacnet_storage_key_init(&key, object_type, object_instance, property_id, + array_index); + stored_len = bacnet_storage_get(&key, name, sizeof(name)); + if (stored_len > 0) { + len = bacapp_decode_application_data(name, stored_len, value); + if (len <= 0) { + if (value) { + value->tag = MAX_BACNET_APPLICATION_TAG; + } + } + } + + return stored_len; +} + +/** + * @brief Store a BACnet SIGNED INTEGER value in non-volatile storage + * @param object_type [in] The BACnet object type + * @param object_instance [in] The BACnet object instance + * @param property_id [in] The BACnet property id + * @param array_index [in] The BACnet array index + * @param value [in] The value to store + * @return true on success, false on failure. + */ +bool bacnet_settings_value_set(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + BACNET_APPLICATION_DATA_VALUE *value) +{ + uint8_t name[BACNET_STORAGE_VALUE_SIZE_MAX] = { 0 }; + BACNET_STORAGE_KEY key = { 0 }; + int rc, len; + + bacnet_storage_key_init(&key, object_type, object_instance, property_id, + array_index); + len = bacapp_encode_application_data(NULL, value); + if (len <= 0) { + return false; + } else if (len > sizeof(name)) { + return false; + } + len = bacapp_encode_application_data(name, value); + rc = bacnet_storage_set(&key, name, len); + + return rc == 0; +} + +/** + * @brief Get a BACnet REAL value from non-volatile storage + * @param object_type [in] The BACnet object type + * @param object_instance [in] The BACnet object instance + * @param property_id [in] The BACnet property id + * @param array_index [in] The BACnet array index + * @param default_value [in] The default value if not found + * @return stored data length on success 0..N, negative on failure. + */ +int bacnet_settings_real_get(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + float default_value, float *value) +{ + int stored_len; + BACNET_APPLICATION_DATA_VALUE bvalue = { 0 }; + + stored_len = + bacnet_settings_value_get(object_type, object_instance, + property_id, array_index, &bvalue); + if ((stored_len >= 0) && (bvalue.tag == BACNET_APPLICATION_TAG_REAL)) { + if (value) { + *value = bvalue.type.Real; + } + } else { + if (value) { + *value = default_value; + } + } + + return stored_len; +} + +/** + * @brief Store a BACnet REAL value in non-volatile storage + * @param object_type [in] The BACnet object type + * @param object_instance [in] The BACnet object instance + * @param property_id [in] The BACnet property id + * @param array_index [in] The BACnet array index + * @param value [in] The value to store + * @return true on success, false on failure. + */ +bool bacnet_settings_real_set(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + float value) +{ + BACNET_APPLICATION_DATA_VALUE bvalue = { 0 }; + + bvalue.context_specific = false; + bvalue.tag = BACNET_APPLICATION_TAG_REAL; + bvalue.type.Real = value; + + return bacnet_settings_value_set(object_type, object_instance, + property_id, array_index, &bvalue); +} + +/** + * @brief Get a BACnet UNSIGNED value from non-volatile storage + * @param object_type [in] The BACnet object type + * @param object_instance [in] The BACnet object instance + * @param property_id [in] The BACnet property id + * @param default_value [in] The default value if not found + * @return stored data length on success 0..N, negative on failure. + */ +int bacnet_settings_unsigned_get(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + BACNET_UNSIGNED_INTEGER default_value, + BACNET_UNSIGNED_INTEGER *value) +{ + uint8_t name[BACNET_STORAGE_VALUE_SIZE_MAX + 1] = { 0 }; + BACNET_STORAGE_KEY key = { 0 }; + int stored_len, len; + + bacnet_storage_key_init(&key, object_type, object_instance, property_id, + array_index); + stored_len = bacnet_storage_get(&key, name, sizeof(name)); + if (stored_len > 0) { + len = bacnet_unsigned_application_decode(name, stored_len, + value); + if (len <= 0) { + if (value) { + *value = default_value; + } + } + } else { + if (value) { + *value = default_value; + } + } + + return stored_len; +} + +/** + * @brief Store a BACnet UNSIGNED value in non-volatile storage + * @param object_type [in] The BACnet object type + * @param object_instance [in] The BACnet object instance + * @param property_id [in] The BACnet property id + * @param value [int] The value to store + * @return true on success, false on failure. + */ +bool bacnet_settings_unsigned_set(uint16_t object_type, + uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + BACNET_UNSIGNED_INTEGER value) +{ + BACNET_APPLICATION_DATA_VALUE bvalue = { 0 }; + + bvalue.context_specific = false; + bvalue.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + bvalue.type.Unsigned_Int = value; + + return bacnet_settings_value_set(object_type, object_instance, + property_id, array_index, &bvalue); +} + +/** + * @brief Get a BACnet SIGNED INTEGER value from non-volatile storage + * @param object_type [in] The BACnet object type + * @param object_instance [in] The BACnet object instance + * @param property_id [in] The BACnet property id + * @param array_index [in] The BACnet array index + * @param default_value [in] The default value if not found + * @return stored data length on success 0..N, negative on failure. + */ +int bacnet_settings_signed_get(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + int32_t default_value, int32_t *value) +{ + uint8_t name[BACNET_STORAGE_VALUE_SIZE_MAX + 1] = { 0 }; + BACNET_STORAGE_KEY key = { 0 }; + int stored_len, len; + + bacnet_storage_key_init(&key, object_type, object_instance, property_id, + array_index); + stored_len = bacnet_storage_get(&key, name, sizeof(name)); + if (stored_len > 0) { + len = bacnet_signed_application_decode(name, stored_len, value); + if (len <= 0) { + if (value) { + *value = default_value; + } + } + } else { + if (value) { + *value = default_value; + } + } + + return stored_len; +} + +/** + * @brief Store a BACnet SIGNED INTEGER value in non-volatile storage + * @param object_type [in] The BACnet object type + * @param object_instance [in] The BACnet object instance + * @param property_id [in] The BACnet property id + * @param array_index [in] The BACnet array index + * @param value [in] The value to store + * @return true on success, false on failure. + */ +bool bacnet_settings_signed_set(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + int32_t value) +{ + BACNET_APPLICATION_DATA_VALUE bvalue = { 0 }; + + bvalue.context_specific = false; + bvalue.tag = BACNET_APPLICATION_TAG_SIGNED_INT; + bvalue.type.Signed_Int = value; + + return bacnet_settings_value_set(object_type, object_instance, + property_id, array_index, &bvalue); +} + +/** + * @brief Get a BACnet CHARACTER_STRING value from non-volatile storage + * @param object_type [in] The BACnet object type + * @param object_instance [in] The BACnet object instance + * @param property_id [in] The BACnet property id + * @param default_value [in] The default value if not found + * @param value [out] The character string value + * @return stored data length on success 0..N, negative on failure. + */ +int bacnet_settings_characterstring_get(uint16_t object_type, + uint32_t object_instance, + uint32_t property_id, + uint32_t array_index, + const char *default_value, + BACNET_CHARACTER_STRING *value) +{ + uint8_t name[BACNET_STORAGE_VALUE_SIZE_MAX + 1] = { 0 }; + BACNET_STORAGE_KEY key = { 0 }; + int stored_len, len; + + bacnet_storage_key_init(&key, object_type, object_instance, property_id, + array_index); + stored_len = bacnet_storage_get(&key, name, sizeof(name)); + if (stored_len > 0) { + len = bacnet_character_string_application_decode( + name, stored_len, value); + if (len <= 0) { + characterstring_init_ansi(value, default_value); + } + } else { + characterstring_init_ansi(value, default_value); + } + + return stored_len; +} + +/** + * @brief Store a BACnet CHARACTER_STRING value to non-volatile storage + * @param object_type [in] The BACnet object type + * @param object_instance [in] The BACnet object instance + * @param property_id [in] The BACnet property id + * @param default_value [in] The default value if not found + * @param value [out] The character string value + * @return true on success, false on failure. + */ +bool bacnet_settings_characterstring_ansi_set(uint16_t object_type, + uint32_t object_instance, + uint32_t property_id, + uint32_t array_index, + const char *cstring) +{ + BACNET_APPLICATION_DATA_VALUE bvalue = { 0 }; + bool status; + + bvalue.context_specific = false; + bvalue.tag = BACNET_APPLICATION_TAG_CHARACTER_STRING; + status = characterstring_init_ansi(&bvalue.type.Character_String, + cstring); + if (!status) { + status = bacnet_settings_value_set(object_type, object_instance, + property_id, array_index, + &bvalue); + } + + return status; +} + +/** + * @brief Get a C-string value from non-volatile storage + * @param object_type [in] The BACnet object type + * @param object_instance [in] The BACnet object instance + * @param property_id [in] The BACnet property id + * @param default_value [in] The default value if not found + * @param value [out] The string value + * @param value_size [in] The size of the string value + * @return stored data length on success 0..N, negative on failure. + */ +int bacnet_settings_string_get(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + const char *default_value, char *value, + size_t value_size) +{ + BACNET_STORAGE_KEY key = { 0 }; + int rc; + + bacnet_storage_key_init(&key, object_type, object_instance, property_id, + array_index); + rc = bacnet_storage_get(&key, value, value_size); + if (rc <= 0) { + if (default_value) { + strncpy(value, default_value, value_size); + rc = strlen(default_value); + } + } + + return rc; +} + +/** + * @brief Get a C-string value from non-volatile storage + * @param object_type [in] The BACnet object type + * @param object_instance [in] The BACnet object instance + * @param property_id [in] The BACnet property id + * @param default_value [in] The default value if not found + * @param value [in] The character string value + * @return true on success, false on failure. + */ +bool bacnet_settings_string_set(uint16_t object_type, uint32_t object_instance, + uint32_t property_id, uint32_t array_index, + const char *value) +{ + BACNET_STORAGE_KEY key = { 0 }; + int rc; + + if (!value) { + return false; + } + bacnet_storage_key_init(&key, object_type, object_instance, property_id, + array_index); + rc = bacnet_storage_set(&key, (const char *)value, strlen(value) + 1); + + return rc == 0; +} diff --git a/zephyr/subsys/bacnet_settings/bacnet_shell.c b/zephyr/subsys/bacnet_settings/bacnet_shell.c new file mode 100644 index 00000000..0db7e540 --- /dev/null +++ b/zephyr/subsys/bacnet_settings/bacnet_shell.c @@ -0,0 +1,116 @@ +/** + * @file + * @brief The BACnet shell commands for debugging and testing + * @author Steve Karg + * @date May 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include + +/** + * @brief Get or set a string using BACnet storage subsystem + * @param sh Shell + * @param argc Number of arguments + * @param argv Argument list + * @return 0 on success, negative on failure + */ +static int cmd_key(BACNET_STORAGE_KEY *key, const struct shell *sh, size_t argc, + char **argv) +{ + uint16_t object_type; + uint32_t object_instance; + uint32_t property_id = 77; + uint32_t array_index = BACNET_STORAGE_ARRAY_INDEX_NONE; + long value = 0; + + if (argc < 3) { + shell_error( + sh, + "Usage: %s [value]", + argv[0]); + return -EINVAL; + } + value = strtoul(argv[1], NULL, 0); + if ((value < 0) || (value >= UINT16_MAX)) { + shell_error(sh, "Invalid object-type: %s. Must be 0-65535.", + argv[1]); + return -EINVAL; + } + object_type = (uint16_t)value; + value = strtoul(argv[2], NULL, 0); + if (value > 4194303) { + shell_error(sh, + "Invalid object-instance: %s. Must be 0-4194303.", + argv[2]); + return -EINVAL; + } + object_instance = (uint32_t)value; + value = strtoul(argv[3], NULL, 0); + if (value > UINT32_MAX) { + shell_error(sh, "Invalid property: %s. Must be 0-4294967295.", + argv[3]); + return -EINVAL; + } + property_id = (uint32_t)value; + /* setup the storage key */ + bacnet_storage_key_init(key, object_type, object_instance, property_id, + array_index); + + return 0; +} + +/** + * @brief Get or set a string using BACnet storage subsystem + * @param sh Shell + * @param argc Number of arguments + * @param argv Argument list + * @return 0 on success, negative on failure + */ +static int cmd_string(const struct shell *sh, size_t argc, char **argv) +{ + char key_name[BACNET_STORAGE_KEY_SIZE_MAX + 1] = { 0 }; + uint8_t data[BACNET_STORAGE_VALUE_SIZE_MAX + 1] = { 0 }; + BACNET_STORAGE_KEY key = { 0 }; + size_t arg_len = 0; + int rc; + + rc = cmd_key(&key, sh, argc, argv); + if (rc < 0) { + return rc; + } + /* convert the key to a string for the shell */ + (void)bacnet_storage_key_encode(key_name, sizeof(key_name), &key); + if (argc > 4) { + arg_len = strlen(argv[4]); + rc = bacnet_storage_set(&key, argv[4], arg_len); + if (rc == 0) { + shell_print(sh, "Set %s = %s", key_name, argv[4]); + } else { + shell_error(sh, "Unable to set %s = %s", key_name, + argv[4]); + return -EINVAL; + } + } else { + rc = bacnet_storage_get(&key, data, sizeof(data)); + if (rc < 0) { + shell_error(sh, "Unable to get %s", key_name); + return -EINVAL; + } + shell_print(sh, "Get %s = %s", key_name, data); + } + + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_bacnet_settings_cmds, + SHELL_CMD(string, NULL, + "get or set BACnet storage string", + cmd_string), + SHELL_SUBCMD_SET_END); + +SHELL_SUBCMD_ADD((bacnet), settings, &sub_bacnet_settings_cmds, + "BACnet settings commands", NULL, 1, 0); diff --git a/zephyr/subsys/bacnet_settings/bacnet_storage.c b/zephyr/subsys/bacnet_settings/bacnet_storage.c new file mode 100644 index 00000000..b1d83625 --- /dev/null +++ b/zephyr/subsys/bacnet_settings/bacnet_storage.c @@ -0,0 +1,266 @@ +/** + * @file + * @brief The BACnet storage tasks for handling the device specific object data + * @author Steve Karg + * @date April 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_SETTINGS_FILE) && defined(CONFIG_FILE_SYSTEM_LITTLEFS) +#include +#include +#elif defined(CONFIG_SETTINGS_FILE) && defined(CONFIG_FILE_SYSTEM_EXT2) +#include +#include +#endif +/* me! */ +#include "bacnet_settings/bacnet_storage.h" + +#ifdef CONFIG_BACNET_SETTINGS_BASE_NAME +#define BACNET_STORAGE_BASE_NAME CONFIG_BACNET_SETTINGS_BASE_NAME +#else +#define BACNET_STORAGE_BASE_NAME ".bacnet" +#endif + +/* Logging module registration is already done in bacnet/ports/zephyr/main.c */ +#include +LOG_MODULE_DECLARE(bacnet, CONFIG_BACNETSTACK_LOG_LEVEL); +#define FAIL_MSG "fail (err %d)" + +#define STORAGE_PARTITION storage_partition +#define STORAGE_PARTITION_ID FIXED_PARTITION_ID(STORAGE_PARTITION) + +/** + * @brief Initialize the non-volatile data +*/ +void bacnet_storage_init(void) +{ + int rc; + +#if defined(CONFIG_SETTINGS_FILE) && defined(CONFIG_FILE_SYSTEM_LITTLEFS) + FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(cstorage); + + /* mounting info */ + static struct fs_mount_t littlefs_mnt = { + .type = FS_LITTLEFS, + .fs_data = &cstorage, + .storage_dev = (void *)STORAGE_PARTITION_ID, + .mnt_point = "/ff" + }; + + rc = fs_mount(&littlefs_mnt); + if (rc != 0) { + LOG_INF("mounting littlefs error: [%d]", rc); + } else { + rc = fs_unlink(CONFIG_SETTINGS_FILE_PATH); + if ((rc != 0) && (rc != -ENOENT)) { + H("can't delete config file%d", rc); + } else { + LOG_INF("FS initialized: OK"); + } + } +#endif + rc = settings_subsys_init(); + if (rc) { + LOG_INF("settings subsys initialization: fail (err %d)", rc); + return; + } + + LOG_INF("settings subsys initialization: OK."); +} + +/** + * @brief Initialize a BACnet key object with optional array + * @param key BACnet key (type, instance, property, array index) + * @param object_type BACnet object type + * @param object_instance BACnet object instance + * @param property_id BACnet property id + * @param array_index BACnet array index + */ +void bacnet_storage_key_init(BACNET_STORAGE_KEY *key, uint16_t object_type, + uint32_t object_instance, uint32_t property_id, + uint32_t array_index) +{ + if (key) { + key->object_type = object_type; + key->object_instance = object_instance; + key->property_id = property_id; + key->array_index = array_index; + } +} + +/** + * @brief Create a storage key string for a BACnet object property + * @param buffer buffer to store key string + * @param buffer_size size of key buffer + * @param key BACnet key (type, instance, property, array index) + * @return length of the string + */ +int bacnet_storage_key_encode(char *buffer, size_t buffer_size, + BACNET_STORAGE_KEY *key) +{ + int rc = 0; + const char base_name[] = CONFIG_BACNET_STORAGE_BASE_NAME; + + if (buffer) { + memset(buffer, 0, buffer_size); + } + if (key->array_index == BACNET_STORAGE_ARRAY_INDEX_NONE) { + rc = snprintf(buffer, buffer_size, "%s%c%u%c%lu%c%lu", + base_name, SETTINGS_NAME_SEPARATOR, + (unsigned short)key->object_type, + SETTINGS_NAME_SEPARATOR, + (unsigned long)key->object_instance, + SETTINGS_NAME_SEPARATOR, + (unsigned long)key->property_id); + } else { + rc = snprintf(buffer, buffer_size, "%s%c%u%c%lu%c%lu%c%lu", + base_name, SETTINGS_NAME_SEPARATOR, + (unsigned short)key->object_type, + SETTINGS_NAME_SEPARATOR, + (unsigned long)key->object_instance, + SETTINGS_NAME_SEPARATOR, + (unsigned long)key->property_id, + SETTINGS_NAME_SEPARATOR, + (unsigned long)key->array_index); + } + + return rc; +} + +/** + * @brief Set a value with a specific key to non-volatile storage + * @param key [in] Key in string format. + * @param data [in] one or more bytes of data + * @param data_len [in] Value length in bytes. + * @return 0 on success, non-zero on failure. + */ +int bacnet_storage_set(BACNET_STORAGE_KEY *key, const void *data, + size_t data_len) +{ + char name[SETTINGS_MAX_NAME_LEN + 1] = { 0 }; + int rc; + + rc = bacnet_storage_key_encode(name, sizeof(name), key); + LOG_INF("Set a key-value pair. Key=%s", name); + rc = settings_save_one(name, data, data_len); + if (rc) { + LOG_INF(FAIL_MSG, rc); + } else { + LOG_HEXDUMP_INF(data, data_len, "value"); + } + + return rc; +} + +/** + * @brief Structure to hold immediate values + */ +struct direct_immediate_value { + size_t value_size; + size_t value_len; + void *value; + bool fetched; +}; + +/** + * @brief Direct loader for immediate values + * @param name [in] Key in string format. + * @param len [in] Length of the key + * @param read_cb [in] Callback to read the value + * @param cb_arg [in] Callback argument + * @param param [in] Callback parameter + * @return 0 on success, non-zero on failure. + */ +static int direct_loader_immediate_value(const char *name, size_t len, + settings_read_cb read_cb, void *cb_arg, + void *param) +{ + const char *next; + size_t name_len; + int rc; + struct direct_immediate_value *context = + (struct direct_immediate_value *)param; + + /* only the exact match and ignore descendants of the searched name */ + name_len = settings_name_next(name, &next); + if (name_len == 0) { + rc = read_cb(cb_arg, context->value, len); + if ((rc >= 0) && (rc <= context->value_size)) { + context->fetched = true; + context->value_len = rc; + LOG_INF("immediate load: OK."); + return 0; + } + return -EINVAL; + } + + /* other keys aren't served by the callback + * Return success in order to skip them + * and keep storage processing. + */ + return 0; +} + +/** + * @brief Load an immediate value from non-volatile storage + * @param name [in] Key in string format. + * @param value [out] Buffer to store the value + * @param value_size [in] size of the buffer + * @return value length in bytes on success 0..N, negative on failure. + */ +static int load_immediate_value(const char *name, void *value, + size_t value_size) +{ + int rc; + struct direct_immediate_value context; + + context.fetched = false; + context.value_size = value_size; + context.value_len = 0; + context.value = value; + + rc = settings_load_subtree_direct(name, direct_loader_immediate_value, + (void *)&context); + if (rc == 0) { + if (!context.fetched) { + rc = -ENOENT; + } + } + + return context.value_len; +} + +/** + * @brief Get a value with a specific key to non-volatile storage + * @param key [in] Key in string format. + * @param data [out] Binary value. + * @param data_size [in] requested value length in bytes + * @return data length on success 0..N, negative on failure. + */ +int bacnet_storage_get(BACNET_STORAGE_KEY *key, void *data, size_t data_size) +{ + char name[SETTINGS_MAX_NAME_LEN + 1] = { 0 }; + int rc; + + rc = bacnet_storage_key_encode(name, sizeof(name), key); + LOG_INF("Get a key-value pair. Key=<%s>", name); + rc = load_immediate_value(name, data, data_size); + if (rc == 0) { + LOG_INF("empty entry"); + } else if (rc > 0) { + LOG_HEXDUMP_INF(data, rc, "value"); + } else if (rc == -ENOENT) { + LOG_INF("no entry"); + } else { + LOG_INF("unexpected" FAIL_MSG, rc); + } + + return rc; +} diff --git a/zephyr/subsys/bacnet_shell.c b/zephyr/subsys/bacnet_shell.c new file mode 100644 index 00000000..4a35ccbf --- /dev/null +++ b/zephyr/subsys/bacnet_shell.c @@ -0,0 +1,12 @@ +/** + * @file + * @brief The BACnet shell commands for debugging and testing + * @author Greg Shue + * @date May 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include + +SHELL_SUBCMD_SET_CREATE(sub_bacnet_cmds, (bacnet)); + +SHELL_CMD_REGISTER(bacnet, &sub_bacnet_cmds, "BACnet module", NULL);