Added Zephyr settings and basic device in subsys. (#697)
* Added Zephyr settings subsys to enable storing of BACnet values according to BACnet object property value path. * Added BACnet Basic features to enable basic samples. Refactored the zephyr BACnet profile B-SS sample to use BACnet basic subsys.
This commit is contained in:
+56
-19
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
$<$<BOOL:${CONFIG_BACDL_NONE}>:BACDL_NONE>
|
||||
$<$<BOOL:${CONFIG_BACDL_NONE}>:BACAPP_ALL>
|
||||
|
||||
+2
-1
@@ -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.
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet Basic Stack initialization and basic task handler
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date April 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#ifndef BACNET_BASIC_H
|
||||
#define BACNET_BASIC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief The BACnet datalink tasks for handling the device specific
|
||||
* data link network port layer
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date April 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#ifndef BACNET_PORT_H
|
||||
#define BACNET_PORT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdint.h>
|
||||
/* 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
|
||||
@@ -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 <skarg@users.sourceforge.net>
|
||||
* @date April 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#ifndef BACNET_PORT_IPV4_H
|
||||
#define BACNET_PORT_IPV4_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
/* 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
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief The BACnet/IPv6 datalink tasks for handling the device specific
|
||||
* data link network port layer
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date April 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#ifndef BACNET_PORT_IPV6_H
|
||||
#define BACNET_PORT_IPV6_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
/* 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
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief API for Get/Set of BACnet application encoded settings handlers
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date May 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#ifndef BACNET_SETTINGS_H
|
||||
#define BACNET_SETTINGS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#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
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief API for the BACnet storage tasks for handling the device specific
|
||||
* non-volatile object data
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date May 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#ifndef BACNET_STORAGE_H
|
||||
#define BACNET_STORAGE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <zephyr/settings/settings.h>
|
||||
|
||||
#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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
************
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,154 +4,41 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <kernel.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 <logging/log.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -5,5 +5,7 @@
|
||||
|
||||
comment "BACnet Subsystems"
|
||||
|
||||
rsource "bacnet_settings/Kconfig"
|
||||
rsource "bacnet_basic/Kconfig"
|
||||
rsource "object/Kconfig"
|
||||
rsource "server/Kconfig"
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# CMake for BACnet settings library
|
||||
#
|
||||
# @author Steve Karg <skarg@users.sourceforge.net>
|
||||
# @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
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
# Kconfig - Subsystem configuration options
|
||||
#
|
||||
# @author Steve Karg <skarg@users.sourceforge.net>
|
||||
# @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
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet Stack initialization and task handler
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date March 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
/* 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++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief The BACnet/IPv4 datalink tasks for handling the device specific
|
||||
* data link layer
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date April 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
/* 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;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief The BACnet/IPv4 datalink tasks for handling the device specific
|
||||
* data link layer
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date April 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
/* 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
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief The BACnet datalink tasks for handling the device specific
|
||||
* data link layer
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date April 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
/* 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
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet shell commands for debugging and testing
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date May 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <zephyr/shell/shell.h>
|
||||
/* 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);
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief The BACnet shell commands for debugging and testing
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date May 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <zephyr/shell/shell.h>
|
||||
/* 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);
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief The BACnet shell commands for debugging and testing
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date May 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <zephyr/shell/shell.h>
|
||||
/* 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);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief BACnet Stack server initialization and task handler
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date March 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <stdalign.h> /*TODO: Not std until C11! */
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/init.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
#include <zephyr/random/random.h>
|
||||
#include <bacnet_settings/bacnet_storage.h>
|
||||
#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 <zephyr/logging/log.h>
|
||||
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);
|
||||
@@ -0,0 +1,17 @@
|
||||
# CMake for BACnet settings library
|
||||
#
|
||||
# @author Steve Karg <skarg@users.sourceforge.net>
|
||||
# @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
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
# Kconfig - Subsystem configuration options
|
||||
#
|
||||
# @author Steve Karg <skarg@users.sourceforge.net>
|
||||
# @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
|
||||
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief Handle Get/Set of BACnet application encoded settings
|
||||
* @date May 2024
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <bacnet_settings/bacnet_storage.h>
|
||||
#include <bacnet_settings/bacnet_settings.h>
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief The BACnet shell commands for debugging and testing
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date May 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <zephyr/shell/shell.h>
|
||||
#include <bacnet_settings/bacnet_storage.h>
|
||||
|
||||
/**
|
||||
* @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 <object-type> <instance> <property> [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);
|
||||
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief The BACnet storage tasks for handling the device specific object data
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date April 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <zephyr/settings/settings.h>
|
||||
#if defined(CONFIG_SETTINGS_FILE) && defined(CONFIG_FILE_SYSTEM_LITTLEFS)
|
||||
#include <zephyr/fs/fs.h>
|
||||
#include <zephyr/fs/littlefs.h>
|
||||
#elif defined(CONFIG_SETTINGS_FILE) && defined(CONFIG_FILE_SYSTEM_EXT2)
|
||||
#include <zephyr/fs/fs.h>
|
||||
#include <zephyr/fs/ext2.h>
|
||||
#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 <zephyr/logging/log.h>
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief The BACnet shell commands for debugging and testing
|
||||
* @author Greg Shue <greg.shue@outlook.com>
|
||||
* @date May 2024
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <zephyr/shell/shell.h>
|
||||
|
||||
SHELL_SUBCMD_SET_CREATE(sub_bacnet_cmds, (bacnet));
|
||||
|
||||
SHELL_CMD_REGISTER(bacnet, &sub_bacnet_cmds, "BACnet module", NULL);
|
||||
Reference in New Issue
Block a user