Feature/create object initial value backup (#1223)
* Added example Device object and property value backup using create-object list-of-initial-values and writable properties. Initiated using ReinitializeDevice with STARTBACKUP command. Added backup and restore required properties to the Device object. * Fixed fwrite parameters in bacfile_posix_write_stream_data to return the correct number of bytes written. * Added Device Management-Backup and Restore-B (DM-BR-B) conversion to Wireshark PCAP format command line utility in apps/dmbrcap which decodes and provides a view of the binary backup file.
This commit is contained in:
@@ -32,6 +32,12 @@ The git repositories are hosted at the following sites:
|
||||
|
||||
### Added
|
||||
|
||||
* Added Device Management-Backup and Restore-B to example object/device.c
|
||||
and basic/server/bacnet-device.c so that when ReinitializeDevice STARTBACKUP
|
||||
is requested a backup file is stored in CreateObject format for all the
|
||||
writable properties in the device. (#1223)
|
||||
* Added apps/dmbrcap for Device Management-Backup and Restore to convert
|
||||
a backup file encoded with CreateObject to Wireshark PCAP format. (#1223)
|
||||
* Added segmentation support functions and example changes, but
|
||||
no support for segmentation in the TSM or APDU handlers. (#1218)
|
||||
* Added channel and timer object write-property observers in blinkt app
|
||||
@@ -122,6 +128,7 @@ The git repositories are hosted at the following sites:
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed bacfile-posix file write to return the number of bytes written. (#1223)
|
||||
* Fixed Event parsing and help text for the example uevent and event apps.
|
||||
Fixed initialization of event data by adding static CharacterString for
|
||||
message text. Fixed the event parsing to start at argument zero. (#1221)
|
||||
|
||||
@@ -105,6 +105,16 @@ option(
|
||||
"build with uci"
|
||||
OFF)
|
||||
|
||||
option(
|
||||
BACFILE
|
||||
"enable bacnet file support"
|
||||
ON)
|
||||
|
||||
option(
|
||||
BACNET_BACKUP_RESTORE
|
||||
"enable backup and restore support"
|
||||
ON)
|
||||
|
||||
set(BACNET_PROTOCOL_REVISION 28)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
@@ -744,6 +754,8 @@ target_compile_definitions(
|
||||
$<$<BOOL:${BACNET_PROPERTY_LISTS}>:BACNET_PROPERTY_LISTS=1>
|
||||
$<$<BOOL:${BAC_ROUTING}>:BAC_ROUTING>
|
||||
$<$<BOOL:${BACNET_SEGMENTATION_ENABLED}>:BACNET_SEGMENTATION_ENABLED=1>
|
||||
$<$<BOOL:${BACFILE}>:BACFILE>
|
||||
$<$<BOOL:${BACNET_BACKUP_RESTORE}>:BACNET_BACKUP_RESTORE>
|
||||
$<$<NOT:$<BOOL:${BUILD_SHARED_LIBS}>>:BACNET_STACK_STATIC_DEFINE>
|
||||
PRIVATE
|
||||
PRINT_ENABLED=1)
|
||||
@@ -1018,6 +1030,9 @@ if(BACNET_STACK_BUILD_APPS)
|
||||
add_executable(initrouter apps/initrouter/main.c)
|
||||
target_link_libraries(initrouter PRIVATE ${PROJECT_NAME})
|
||||
|
||||
add_executable(dmbrcap apps/dmbrcap/main.c)
|
||||
target_link_libraries(dmbrcap PRIVATE ${PROJECT_NAME})
|
||||
|
||||
if(BACDL_MSTP)
|
||||
add_executable(mstpcap apps/mstpcap/main.c)
|
||||
target_link_libraries(mstpcap PRIVATE ${PROJECT_NAME})
|
||||
@@ -1311,3 +1326,5 @@ message(STATUS "BACNET: BACDL_MSTP:.....................\"${BACDL_MSTP}\"")
|
||||
message(STATUS "BACNET: BACDL_ZIGBEE:...................\"${BACDL_ZIGBEE}\"")
|
||||
message(STATUS "BACNET: BACDL_ETHERNET:.................\"${BACDL_ETHERNET}\"")
|
||||
message(STATUS "BACNET: BACNET_SEGMENTATION_ENABLED:....\"${BACNET_SEGMENTATION_ENABLED}\"")
|
||||
message(STATUS "BACNET: BACNET_BACKUP_RESTORE:..........\"${BACNET_BACKUP_RESTORE}\"")
|
||||
message(STATUS "BACNET: BACFILE:........................\"${BACFILE}\"")
|
||||
|
||||
@@ -236,6 +236,10 @@ sc-hub:
|
||||
sc-hub-debug:
|
||||
$(MAKE) LEGACY=true BACDL=bsc BUILD=debug -s -C apps sc-hub
|
||||
|
||||
.PHONY: dmbrcap
|
||||
dmbrcap:
|
||||
$(MAKE) -s -C apps $@
|
||||
|
||||
.PHONY: mstpcap
|
||||
mstpcap:
|
||||
$(MAKE) -s -C apps $@
|
||||
|
||||
+6
-1
@@ -237,6 +237,7 @@ endif
|
||||
BACNET_DEFINES += -DPRINT_ENABLED=1
|
||||
BACNET_DEFINES += -DBACAPP_ALL
|
||||
BACNET_DEFINES += -DBACNET_TIME_MASTER
|
||||
BACNET_DEFINES += -DBACNET_BACKUP_RESTORE
|
||||
BACNET_DEFINES += -DBACNET_PROPERTY_LISTS=1
|
||||
BACNET_DEFINES += -DBACNET_PROTOCOL_REVISION=28
|
||||
|
||||
@@ -266,7 +267,7 @@ SUBDIRS = lib readprop writeprop readfile writefile reinit server dcc \
|
||||
whohas whois iam ucov scov timesync epics readpropm readrange \
|
||||
writepropm uptransfer getevent uevent abort error event ack-alarm \
|
||||
server-client add-list-element remove-list-element create-object \
|
||||
who-am-i you-are apdu writegroup \
|
||||
who-am-i you-are apdu writegroup dmbrcap \
|
||||
delete-object server-discover server-basic server-mini
|
||||
|
||||
ifneq (,$(filter $(BACDL),bip all))
|
||||
@@ -401,6 +402,10 @@ whatisnetnum: $(BACNET_LIB_TARGET)
|
||||
netnumis: $(BACNET_LIB_TARGET)
|
||||
$(MAKE) -B -C $@
|
||||
|
||||
.PHONY: dmbrcap
|
||||
dmbrcap:
|
||||
$(MAKE) -B -C $@
|
||||
|
||||
.PHONY: mstpcap
|
||||
mstpcap:
|
||||
$(MAKE) -B -C $@
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#Makefile to build BACnet Application using GCC compiler
|
||||
|
||||
# Executable file name
|
||||
TARGET = dmbrcap
|
||||
# BACnet objects that are used with this app
|
||||
BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object
|
||||
SRC = main.c \
|
||||
$(BACNET_OBJECT_DIR)/client/device-client.c \
|
||||
$(BACNET_OBJECT_DIR)/netport.c
|
||||
|
||||
# TARGET_EXT is defined in apps/Makefile as .exe or nothing
|
||||
TARGET_BIN = ${TARGET}$(TARGET_EXT)
|
||||
|
||||
OBJS += ${SRC:.c=.o}
|
||||
|
||||
all: ${BACNET_LIB_TARGET} Makefile ${TARGET_BIN}
|
||||
|
||||
${TARGET_BIN}: ${OBJS} Makefile ${BACNET_LIB_TARGET}
|
||||
${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@
|
||||
size $@
|
||||
cp $@ ../../bin
|
||||
|
||||
${BACNET_LIB_TARGET}:
|
||||
( cd ${BACNET_LIB_DIR} ; $(MAKE) clean ; $(MAKE) -s )
|
||||
|
||||
.c.o:
|
||||
${CC} -c ${CFLAGS} $*.c -o $@
|
||||
|
||||
.PHONY: depend
|
||||
depend:
|
||||
rm -f .depend
|
||||
${CC} -MM ${CFLAGS} *.c >> .depend
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f core ${TARGET_BIN} ${OBJS} $(TARGET).map ${BACNET_LIB_TARGET}
|
||||
|
||||
.PHONY: include
|
||||
include: .depend
|
||||
@@ -0,0 +1,4 @@
|
||||
K.5.18 BIBB - Device Management-Backup and Restore-B (DM-BR-B)
|
||||
|
||||
This tool generates a Wireshark PCAP format file from a CreateObject services
|
||||
encoded backup file.
|
||||
@@ -0,0 +1,356 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief Device Management-Backup and Restore tool that generates
|
||||
* a Wireshark PCAP format file from a CreateObject services encoded file.
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date February 2026
|
||||
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
/* BACnet Stack defines - first */
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/bacint.h"
|
||||
#include "bacnet/create_object.h"
|
||||
#include "bacnet/datetime.h"
|
||||
#include "bacnet/npdu.h"
|
||||
#include "bacnet/basic/sys/mstimer.h"
|
||||
#include "bacnet/basic/sys/filename.h"
|
||||
|
||||
/* define our Data Link Type for libPCAP */
|
||||
#define DLT_CAPTURE_TYPE (1)
|
||||
|
||||
static uint8_t MTU_Buffer[1501];
|
||||
static char Capture_Filename[64] = "dmbr_20260209012345.cap";
|
||||
static FILE *Capture_File_Handle = NULL; /* stream pointer */
|
||||
static FILE *Backup_File_Handle = NULL; /* stream pointer */
|
||||
static long Backup_File_Start_Position;
|
||||
static long Backup_File_Packet_Counter;
|
||||
|
||||
/**
|
||||
* @brief Write data to the capture file.
|
||||
* @param ptr Pointer to the data to be written
|
||||
* @param size Size of each element to write
|
||||
* @param nitems Number of items to write
|
||||
* @return Number of items successfully written to the capture file
|
||||
*/
|
||||
static size_t data_write(const void *ptr, size_t size, size_t nitems)
|
||||
{
|
||||
size_t written = 0;
|
||||
|
||||
if (Capture_File_Handle) {
|
||||
written = fwrite(ptr, size, nitems, Capture_File_Handle);
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write header data to the capture file.
|
||||
* @param ptr Pointer to the header data to be written
|
||||
* @param size Size of each element to write
|
||||
* @param nitems Number of items to write
|
||||
* @return Number of items successfully written to the capture file
|
||||
*/
|
||||
static size_t data_write_header(const void *ptr, size_t size, size_t nitems)
|
||||
{
|
||||
size_t written = 0;
|
||||
|
||||
if (Capture_File_Handle) {
|
||||
written = fwrite(ptr, size, nitems, Capture_File_Handle);
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a new capture filename based on the current date and time.
|
||||
*
|
||||
* Closes any existing capture file, generates a new filename using the
|
||||
* current local date and time, and opens a new capture file for writing.
|
||||
*/
|
||||
static void filename_create_new(void)
|
||||
{
|
||||
BACNET_DATE bdate;
|
||||
BACNET_TIME btime;
|
||||
char *filename = &Capture_Filename[0];
|
||||
size_t filename_size = sizeof(Capture_Filename);
|
||||
|
||||
if (Capture_File_Handle) {
|
||||
fclose(Capture_File_Handle);
|
||||
}
|
||||
Capture_File_Handle = NULL;
|
||||
datetime_local(&bdate, &btime, NULL, NULL);
|
||||
snprintf(
|
||||
filename, filename_size, "dmbr_%04d%02d%02d%02d%02d%02d.cap",
|
||||
(int)bdate.year, (int)bdate.month, (int)bdate.day, (int)btime.hour,
|
||||
(int)btime.min, (int)btime.sec);
|
||||
Capture_File_Handle = fopen(filename, "wb");
|
||||
if (Capture_File_Handle) {
|
||||
fprintf(stdout, "dmbrcap: saving capture to %s\n", filename);
|
||||
} else {
|
||||
fprintf(
|
||||
stderr, "dmbrcap: failed to open %s: %s\n", filename,
|
||||
strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write the global header to the capture file in libpcap format.
|
||||
*
|
||||
* Writes the standard libpcap global header including magic number,
|
||||
* version information, timezone, timestamp accuracy, snapshot length,
|
||||
* and data link type.
|
||||
*/
|
||||
static void write_global_header(void)
|
||||
{
|
||||
uint32_t magic_number = 0xa1b2c3d4; /* magic number */
|
||||
uint16_t version_major = 2; /* major version number */
|
||||
uint16_t version_minor = 4; /* minor version number */
|
||||
int32_t thiszone = 0; /* GMT to local correction */
|
||||
uint32_t sigfigs = 0; /* accuracy of timestamps */
|
||||
uint32_t snaplen = 65535; /* max length of captured packet, in octets */
|
||||
uint32_t network = DLT_CAPTURE_TYPE; /* data link type */
|
||||
|
||||
/* create a new file. */
|
||||
(void)data_write_header(&magic_number, sizeof(magic_number), 1);
|
||||
(void)data_write_header(&version_major, sizeof(version_major), 1);
|
||||
(void)data_write_header(&version_minor, sizeof(version_minor), 1);
|
||||
(void)data_write_header(&thiszone, sizeof(thiszone), 1);
|
||||
(void)data_write_header(&sigfigs, sizeof(sigfigs), 1);
|
||||
(void)data_write_header(&snaplen, sizeof(snaplen), 1);
|
||||
(void)data_write_header(&network, sizeof(network), 1);
|
||||
fflush(Capture_File_Handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write a packet record to the capture file in libpcap format.
|
||||
*
|
||||
* Writes a packet record with timestamp and length information followed by
|
||||
* the packet data to the capture file in libpcap format.
|
||||
* @param packet Pointer to the packet data to write
|
||||
* @param packet_len Length of the packet data in bytes
|
||||
*/
|
||||
static void write_received_packet(uint8_t *packet, size_t packet_len)
|
||||
{
|
||||
uint32_t ts_msec = 0; /* timestamp milliseconds */
|
||||
uint32_t ts_sec = 0; /* timestamp seconds */
|
||||
uint32_t ts_usec = 0; /* timestamp microseconds */
|
||||
uint32_t incl_len = 0; /* number of octets of packet saved in file */
|
||||
uint32_t orig_len = 0; /* actual length of packet */
|
||||
|
||||
ts_msec = mstimer_now();
|
||||
ts_sec = ts_msec / 1000;
|
||||
ts_usec = (ts_msec % 1000) * 1000;
|
||||
(void)data_write(&ts_sec, sizeof(ts_sec), 1);
|
||||
(void)data_write(&ts_usec, sizeof(ts_usec), 1);
|
||||
incl_len = packet_len;
|
||||
(void)data_write(&incl_len, sizeof(incl_len), 1);
|
||||
orig_len = packet_len;
|
||||
(void)data_write(&orig_len, sizeof(orig_len), 1);
|
||||
(void)data_write(packet, packet_len, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open a backup file for reading.
|
||||
*
|
||||
* Opens an existing backup file for reading and initializes the file
|
||||
* position tracking for packet extraction.
|
||||
* @param filename Path to the backup file to open
|
||||
* @return true if the file was successfully opened, false otherwise
|
||||
*/
|
||||
static bool open_backup_file(const char *filename)
|
||||
{
|
||||
/* open existing file. */
|
||||
Backup_File_Handle = fopen(filename, "rb");
|
||||
if (Backup_File_Handle) {
|
||||
fprintf(stdout, "dmbrcap: reading backup from %s\n", filename);
|
||||
Backup_File_Start_Position = 0;
|
||||
return true;
|
||||
}
|
||||
fprintf(
|
||||
stderr, "dmbrcap: failed to open %s: %s\n", filename, strerror(errno));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Extract and convert the next packet from the backup file.
|
||||
*
|
||||
* Reads the next CreateObject service request from the backup file,
|
||||
* encapsulates it in BACnet NPDU and APDU headers with Ethernet framing,
|
||||
* and writes the complete packet to the capture file. Advances the file
|
||||
* position for the next packet extraction.
|
||||
* @return Length of the packet written, or 0 if no more packets are available
|
||||
*/
|
||||
static size_t backup_file_packet(void)
|
||||
{
|
||||
BACNET_NPDU_DATA npdu_data = { 0 };
|
||||
size_t len = 0, packet_len = 0, apdu_len = 0;
|
||||
int decoded_len = 0;
|
||||
uint8_t apdu[1500] = { 0 };
|
||||
|
||||
/* Ethernet SNAP Encoding*/
|
||||
/* dest MAC */
|
||||
MTU_Buffer[0] = 0xFF;
|
||||
MTU_Buffer[1] = 0xFF;
|
||||
MTU_Buffer[2] = 0xFF;
|
||||
MTU_Buffer[3] = 0xFF;
|
||||
MTU_Buffer[4] = 0xFF;
|
||||
MTU_Buffer[5] = 0xFF;
|
||||
/* source MAC */
|
||||
MTU_Buffer[6] = 0xFF;
|
||||
MTU_Buffer[7] = 0xFF;
|
||||
MTU_Buffer[8] = 0xFF;
|
||||
MTU_Buffer[9] = 0xFF;
|
||||
MTU_Buffer[10] = 0xFF;
|
||||
MTU_Buffer[11] = 0xFF;
|
||||
/* length - 12, 13 */
|
||||
/* Logical-Link Control SNAP */
|
||||
/* DSAP for SNAP */
|
||||
MTU_Buffer[14] = 0x82;
|
||||
/* SSAP for SNAP */
|
||||
MTU_Buffer[15] = 0x82;
|
||||
/* Control Field for SNAP */
|
||||
MTU_Buffer[16] = 0x03;
|
||||
/* BACnet NPDU */
|
||||
len = npdu_encode_pdu(&MTU_Buffer[17], NULL, NULL, &npdu_data);
|
||||
packet_len = 17 + len;
|
||||
/* BACnet APDU header */
|
||||
MTU_Buffer[packet_len++] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
|
||||
MTU_Buffer[packet_len++] = encode_max_segs_max_apdu(0, MAX_APDU);
|
||||
MTU_Buffer[packet_len++] = Backup_File_Packet_Counter % 256;
|
||||
MTU_Buffer[packet_len++] = SERVICE_CONFIRMED_CREATE_OBJECT;
|
||||
/* BACnet APDU service data */
|
||||
if (Backup_File_Handle) {
|
||||
(void)fseek(Backup_File_Handle, Backup_File_Start_Position, SEEK_SET);
|
||||
apdu_len =
|
||||
fread(apdu, sizeof(uint8_t), sizeof(apdu), Backup_File_Handle);
|
||||
if (apdu_len > 0) {
|
||||
decoded_len =
|
||||
create_object_decode_service_request(apdu, apdu_len, NULL);
|
||||
if (decoded_len > 0) {
|
||||
apdu_len = decoded_len;
|
||||
Backup_File_Start_Position += decoded_len;
|
||||
if ((packet_len + apdu_len) > sizeof(MTU_Buffer)) {
|
||||
apdu_len = sizeof(MTU_Buffer) - packet_len;
|
||||
}
|
||||
memcpy(&MTU_Buffer[packet_len], apdu, apdu_len);
|
||||
packet_len += apdu_len;
|
||||
/* Ethernet length is data only - not address or length bytes */
|
||||
encode_unsigned16(&MTU_Buffer[12], packet_len - 14);
|
||||
write_received_packet(MTU_Buffer, packet_len);
|
||||
} else {
|
||||
packet_len = 0;
|
||||
}
|
||||
} else {
|
||||
packet_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return packet_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clean up and close all open file handles.
|
||||
*
|
||||
* Flushes and closes the capture file and backup file handles, preparing
|
||||
* the program for exit. This function is registered as an exit handler.
|
||||
*/
|
||||
static void cleanup(void)
|
||||
{
|
||||
if (Capture_File_Handle) {
|
||||
fflush(Capture_File_Handle);
|
||||
fclose(Capture_File_Handle);
|
||||
}
|
||||
Capture_File_Handle = NULL;
|
||||
if (Backup_File_Handle) {
|
||||
fclose(Backup_File_Handle);
|
||||
}
|
||||
Backup_File_Handle = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print the command-line usage information.
|
||||
* @param filename The name of the program (typically argv[0])
|
||||
*/
|
||||
static void print_usage(const char *filename)
|
||||
{
|
||||
printf("Usage: %s <filename>", filename);
|
||||
printf(" [--version][--help]\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print detailed help information for the program.
|
||||
* @param filename The name of the program (typically argv[0])
|
||||
*/
|
||||
static void print_help(const char *filename)
|
||||
{
|
||||
printf(
|
||||
"%s <filename>\n"
|
||||
"convert a backup file into a capture file.\n",
|
||||
filename);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main entry point for the dmbrcap utility.
|
||||
*
|
||||
* Processes command-line arguments, opens the backup file, initializes the
|
||||
* timer system, creates a new capture file with libpcap headers, reads all
|
||||
* packets from the backup file and converts them to libpcap format, then
|
||||
* closes all files and exits.
|
||||
* @param argc Number of command-line arguments
|
||||
* @param argv Array of command-line argument strings
|
||||
* @return 0 on success, 1 if backup file cannot be opened or is not provided
|
||||
*/
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
const char *filename = NULL;
|
||||
int argi = 0;
|
||||
|
||||
/* decode any command line parameters */
|
||||
filename = filename_remove_path(argv[0]);
|
||||
for (argi = 1; argi < argc; argi++) {
|
||||
if (strcmp(argv[argi], "--help") == 0) {
|
||||
print_usage(filename);
|
||||
print_help(filename);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(argv[argi], "--version") == 0) {
|
||||
printf("dmbrcap 1.0.0\n");
|
||||
printf("Copyright (C) 2026 by Steve Karg\n"
|
||||
"This is free software; see the source for copying "
|
||||
"conditions.\n"
|
||||
"There is NO warranty; not even for MERCHANTABILITY or\n"
|
||||
"FITNESS FOR A PARTICULAR PURPOSE.\n");
|
||||
return 0;
|
||||
}
|
||||
if (!open_backup_file(argv[argi])) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (!Backup_File_Handle) {
|
||||
print_usage(filename);
|
||||
return 1;
|
||||
}
|
||||
atexit(cleanup);
|
||||
mstimer_init();
|
||||
filename_create_new();
|
||||
write_global_header();
|
||||
while (backup_file_packet() > 0) {
|
||||
Backup_File_Packet_Counter++;
|
||||
}
|
||||
if (Backup_File_Packet_Counter) {
|
||||
fprintf(
|
||||
stdout, "dmbrcap: wrote %u packets\n",
|
||||
(unsigned)Backup_File_Packet_Counter);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -174,6 +174,21 @@ static void Init_Service_Handlers(void)
|
||||
(unsigned)object_data.object_instance);
|
||||
}
|
||||
}
|
||||
#if defined(BACFILE)
|
||||
/* file for backup and restore example */
|
||||
object_data.object_instance = bacfile_create(BACNET_MAX_INSTANCE);
|
||||
if (object_data.object_instance != BACNET_MAX_INSTANCE) {
|
||||
bacfile_pathname_set(object_data.object_instance, "backup_1.bin");
|
||||
#if defined(BACNET_BACKUP_RESTORE)
|
||||
Device_Configuration_File_Set(0, object_data.object_instance);
|
||||
#endif
|
||||
printf(
|
||||
"Created %s-%u path=%s for backup and restore (%u files).\n",
|
||||
bactext_object_type_name(OBJECT_FILE),
|
||||
(unsigned)object_data.object_instance,
|
||||
bacfile_pathname(object_data.object_instance), bacfile_count());
|
||||
}
|
||||
#endif
|
||||
/* we need to handle who-is to support dynamic device binding */
|
||||
apdu_set_unconfirmed_handler(
|
||||
SERVICE_UNCONFIRMED_WHO_IS, handler_who_is_who_am_i_unicast);
|
||||
|
||||
@@ -151,7 +151,7 @@ size_t bacfile_posix_write_stream_data(
|
||||
if (fileStartPosition != -1) {
|
||||
(void)fseek(pFile, fileStartPosition, SEEK_SET);
|
||||
}
|
||||
bytes_written = fwrite(fileData, fileDataLen, 1, pFile);
|
||||
bytes_written = fwrite(fileData, 1, fileDataLen, pFile);
|
||||
fclose(pFile);
|
||||
} else {
|
||||
debug_printf_stderr("Failed to open %s for writing!\n", pathname);
|
||||
|
||||
@@ -533,7 +533,7 @@ void bacfile_file_size_set_callback_set(bool (*callback)(const char *, size_t))
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param buffer - data store from the file
|
||||
* @param buffer_size - in bytes
|
||||
* @return file size in bytes
|
||||
* @return number of bytes read, or 0 if not successful
|
||||
*/
|
||||
uint32_t
|
||||
bacfile_read(uint32_t object_instance, uint8_t *buffer, uint32_t buffer_size)
|
||||
@@ -555,7 +555,7 @@ bacfile_read(uint32_t object_instance, uint8_t *buffer, uint32_t buffer_size)
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param buffer - data store for the file
|
||||
* @param buffer_size - in bytes
|
||||
* @return file size in bytes
|
||||
* @return number of bytes written, or 0 if not successful
|
||||
*/
|
||||
uint32_t bacfile_write(
|
||||
uint32_t object_instance, const uint8_t *buffer, uint32_t buffer_size)
|
||||
@@ -572,6 +572,32 @@ uint32_t bacfile_write(
|
||||
return (uint32_t)file_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write to the file from a buffer at a given offset
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param offset - offset in bytes from the beginning of the file
|
||||
* @param buffer - data store for the file
|
||||
* @param buffer_size - in bytes
|
||||
* @return number of bytes written, or 0 if not successful
|
||||
*/
|
||||
uint32_t bacfile_write_offset(
|
||||
uint32_t object_instance,
|
||||
int32_t offset,
|
||||
const uint8_t *buffer,
|
||||
uint32_t buffer_size)
|
||||
{
|
||||
const char *pathname = NULL;
|
||||
long file_size = 0;
|
||||
|
||||
pathname = bacfile_pathname(object_instance);
|
||||
if (pathname) {
|
||||
file_size = bacfile_write_stream_data_callback(
|
||||
pathname, offset, buffer, buffer_size);
|
||||
}
|
||||
|
||||
return (uint32_t)file_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determines the file size for a given file
|
||||
* @param pFile - file handle
|
||||
|
||||
@@ -121,6 +121,12 @@ bacfile_read(uint32_t object_instance, uint8_t *buffer, uint32_t buffer_size);
|
||||
BACNET_STACK_EXPORT
|
||||
uint32_t bacfile_write(
|
||||
uint32_t object_instance, const uint8_t *buffer, uint32_t buffer_size);
|
||||
BACNET_STACK_EXPORT
|
||||
uint32_t bacfile_write_offset(
|
||||
uint32_t object_instance,
|
||||
int32_t offset,
|
||||
const uint8_t *buffer,
|
||||
uint32_t buffer_size);
|
||||
|
||||
BACNET_STACK_EXPORT
|
||||
void bacfile_write_stream_data_callback_set(
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/**
|
||||
* @file
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2005
|
||||
* @brief Base "class" for handling all BACnet objects belonging
|
||||
* to a BACnet device, as well as Device-specific properties.
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2005
|
||||
* @copyright SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
/* BACnet Stack defines - first */
|
||||
#include "bacnet/bacdef.h"
|
||||
@@ -34,10 +35,10 @@
|
||||
#include "bacnet/basic/object/access_rights.h"
|
||||
#include "bacnet/basic/object/access_user.h"
|
||||
#include "bacnet/basic/object/access_zone.h"
|
||||
#include "bacnet/basic/object/auditlog.h"
|
||||
#include "bacnet/basic/object/ai.h"
|
||||
#include "bacnet/basic/object/ao.h"
|
||||
#include "bacnet/basic/object/av.h"
|
||||
#include "bacnet/basic/object/auditlog.h"
|
||||
#include "bacnet/basic/object/bi.h"
|
||||
#include "bacnet/basic/object/bo.h"
|
||||
#include "bacnet/basic/object/bv.h"
|
||||
@@ -75,6 +76,9 @@
|
||||
#include "bacnet/basic/object/color_object.h"
|
||||
#include "bacnet/basic/object/color_temperature.h"
|
||||
#include "bacnet/basic/object/program.h"
|
||||
/* for testing */
|
||||
#include "bacnet/basic/sys/debug.h"
|
||||
#include "bacnet/bactext.h"
|
||||
|
||||
/* external prototypes */
|
||||
extern int Routed_Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata);
|
||||
@@ -698,11 +702,21 @@ static const int32_t Device_Properties_Optional[] = {
|
||||
PROP_TIME_SYNCHRONIZATION_INTERVAL,
|
||||
PROP_ALIGN_INTERVALS,
|
||||
PROP_INTERVAL_OFFSET,
|
||||
#endif
|
||||
#if defined(BACNET_BACKUP_RESTORE)
|
||||
PROP_CONFIGURATION_FILES,
|
||||
PROP_BACKUP_FAILURE_TIMEOUT,
|
||||
PROP_BACKUP_PREPARATION_TIME,
|
||||
PROP_RESTORE_PREPARATION_TIME,
|
||||
PROP_BACKUP_AND_RESTORE_STATE,
|
||||
#endif
|
||||
-1
|
||||
};
|
||||
|
||||
static const int32_t Device_Properties_Proprietary[] = { -1 };
|
||||
static const int32_t Device_Properties_Proprietary[] = {
|
||||
/* List of Proprietary properties in this object */
|
||||
-1
|
||||
};
|
||||
|
||||
/* Every object shall have a Writable Property_List property
|
||||
which is a BACnetARRAY of property identifiers,
|
||||
@@ -717,8 +731,6 @@ static const int32_t Writable_Properties[] = {
|
||||
PROP_MODEL_NAME,
|
||||
PROP_LOCATION,
|
||||
PROP_DESCRIPTION,
|
||||
PROP_PROTOCOL_SERVICES_SUPPORTED,
|
||||
PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED,
|
||||
PROP_APDU_TIMEOUT,
|
||||
PROP_NUMBER_OF_APDU_RETRIES,
|
||||
PROP_UTC_OFFSET,
|
||||
@@ -730,6 +742,12 @@ static const int32_t Writable_Properties[] = {
|
||||
#if defined(BACDL_MSTP)
|
||||
PROP_MAX_INFO_FRAMES,
|
||||
PROP_MAX_MASTER,
|
||||
#endif
|
||||
#if defined(BACNET_BACKUP_RESTORE)
|
||||
PROP_CONFIGURATION_FILES,
|
||||
PROP_BACKUP_FAILURE_TIMEOUT,
|
||||
PROP_BACKUP_PREPARATION_TIME,
|
||||
PROP_RESTORE_PREPARATION_TIME,
|
||||
#endif
|
||||
PROP_TIME_OF_DEVICE_RESTART,
|
||||
-1
|
||||
@@ -824,6 +842,7 @@ static char Location[MAX_DEV_LOC_LEN + 1] = "USA";
|
||||
static char Description[MAX_DEV_DESC_LEN + 1] = "server";
|
||||
static char Serial_Number[MAX_DEV_DESC_LEN + 1] =
|
||||
"BACnetDMcN56RBkeDJuNfxn3M44tfC2Y";
|
||||
static uint8_t Device_UUID[16];
|
||||
/* static uint8_t Protocol_Version = 1; - constant, not settable */
|
||||
/* static uint8_t Protocol_Revision = 4; - constant, not settable */
|
||||
/* Protocol_Services_Supported - dynamically generated */
|
||||
@@ -853,9 +872,6 @@ static uint32_t Interval_Offset_Minutes;
|
||||
/* Max_Info_Frames - rely on MS/TP subsystem, if there is one */
|
||||
/* Device_Address_Binding - required, but relies on binding cache */
|
||||
static uint32_t Database_Revision = 0;
|
||||
/* Configuration_Files */
|
||||
/* Last_Restore_Time */
|
||||
/* Backup_Failure_Timeout */
|
||||
/* Active_COV_Subscriptions */
|
||||
/* Slave_Proxy_Enable */
|
||||
/* Manual_Slave_Address_Binding */
|
||||
@@ -867,6 +883,58 @@ static const char *Reinit_Password = "filister";
|
||||
static write_property_function Device_Write_Property_Store_Callback;
|
||||
static list_element_function Device_Add_List_Element_Callback;
|
||||
static list_element_function Device_Remove_List_Element_Callback;
|
||||
/* backup and restore */
|
||||
#if defined BACNET_BACKUP_RESTORE
|
||||
/* number of backup files */
|
||||
#ifndef BACNET_BACKUP_FILE_COUNT
|
||||
#if defined(BACFILE)
|
||||
#define BACNET_BACKUP_FILE_COUNT 1
|
||||
#else
|
||||
#define BACNET_BACKUP_FILE_COUNT 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BACNET_BACKUP_FILE_COUNT
|
||||
/* device A will read the Configuration_Files property of the Device object.
|
||||
This property will be used to determine the files to read and in what
|
||||
order the files will be read. The value of the Configuration_Files
|
||||
property is not guaranteed to contain a complete or correct set of
|
||||
configuration File object references before the backup request is
|
||||
accepted by device B. */
|
||||
static uint32_t Configuration_Files[BACNET_BACKUP_FILE_COUNT];
|
||||
#endif
|
||||
/* If the restore is successful, no other actions by device A shall
|
||||
be required, and device B will update the Last_Restore_Time property
|
||||
in its Device object. */
|
||||
static BACNET_TIMESTAMP Last_Restore_Time;
|
||||
/* If device B does not receive any messages related to the restore
|
||||
procedure from device A for the number of seconds specified
|
||||
in the Backup_Failure_Timeout property of its Device object,
|
||||
device B should assume that the restore procedure has been aborted,
|
||||
and device B should exit restore mode.*/
|
||||
static uint16_t Backup_Failure_Timeout;
|
||||
/* This property indicates the amount of time in seconds
|
||||
that the device might remain unresponsive after the sending
|
||||
of a ReinitializeDevice-ACK at the start of a backup procedure.
|
||||
The device that initiated the backup shall either wait the
|
||||
period of time specified by this property or be prepared
|
||||
to encounter communication timeouts during this period. */
|
||||
static uint16_t Backup_Preparation_Time;
|
||||
/* This property indicates the amount of time in seconds that the device
|
||||
is allowed to remain unresponsive after the sending of a
|
||||
ReinitializeDevice-ACK at the start of a restore procedure.
|
||||
The restoring device shall either wait or be prepared to
|
||||
encounter communication timeouts during this period.*/
|
||||
static uint16_t Restore_Preparation_Time;
|
||||
/* This property indicates the amount of time in seconds that
|
||||
the device is allowed to remain unresponsive after the sending
|
||||
of a ReinitializeDevice-ACK at the end of a restore procedure.
|
||||
The restoring device shall either wait or be prepared to
|
||||
encounter communication timeouts during this period. */
|
||||
static uint16_t Restore_Completion_Time;
|
||||
/* This property indicates a server device's backup and restore state. */
|
||||
static BACNET_BACKUP_STATE Backup_State = BACKUP_STATE_IDLE;
|
||||
#endif
|
||||
|
||||
#ifdef BAC_ROUTING
|
||||
static bool Device_Router_Mode = false;
|
||||
@@ -957,9 +1025,27 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data)
|
||||
Reinitialize_State = rd_data->state;
|
||||
status = true;
|
||||
break;
|
||||
#if defined BACNET_BACKUP_RESTORE
|
||||
case BACNET_REINIT_STARTBACKUP:
|
||||
case BACNET_REINIT_ENDBACKUP:
|
||||
Device_Start_Backup();
|
||||
Reinitialize_State = rd_data->state;
|
||||
status = true;
|
||||
break;
|
||||
case BACNET_REINIT_STARTRESTORE:
|
||||
Device_Start_Restore();
|
||||
Reinitialize_State = rd_data->state;
|
||||
status = true;
|
||||
break;
|
||||
case BACNET_REINIT_ENDBACKUP:
|
||||
case BACNET_REINIT_ENDRESTORE:
|
||||
case BACNET_REINIT_ABORTRESTORE:
|
||||
Reinitialize_State = rd_data->state;
|
||||
status = true;
|
||||
break;
|
||||
#else
|
||||
case BACNET_REINIT_STARTBACKUP:
|
||||
case BACNET_REINIT_STARTRESTORE:
|
||||
case BACNET_REINIT_ENDBACKUP:
|
||||
case BACNET_REINIT_ENDRESTORE:
|
||||
case BACNET_REINIT_ABORTRESTORE:
|
||||
if (dcc_communication_disabled()) {
|
||||
@@ -971,6 +1057,7 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data)
|
||||
ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case BACNET_REINIT_ACTIVATE_CHANGES:
|
||||
/* note: activate changes *after* the simple ack is sent */
|
||||
for (i = 0; i < Network_Port_Count(); i++) {
|
||||
@@ -1012,8 +1099,6 @@ uint32_t Device_Index_To_Instance(unsigned index)
|
||||
return Object_Instance_Number;
|
||||
}
|
||||
|
||||
/* methods to manipulate the data */
|
||||
|
||||
/** Return the Object Instance number for our (single) Device Object.
|
||||
* This is a key function, widely invoked by the handler code, since
|
||||
* it provides "our" (ie, local) address.
|
||||
@@ -1097,6 +1182,71 @@ char *Device_Object_Name_ANSI(void)
|
||||
return (char *)characterstring_value(&My_Object_Name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize a UUID for storing the unique identifier of this device
|
||||
* @note A Universally Unique IDentifier (UUID) - also called a
|
||||
* Global Unique IDentifier (GUID) - is a 128-bit value, see RFC 4122.
|
||||
*
|
||||
* 4.4. Algorithms for Creating a UUID from Truly Random or
|
||||
* Pseudo-Random Numbers
|
||||
*
|
||||
* The version 4 UUID is meant for generating UUIDs from truly-random or
|
||||
* pseudo-random numbers.
|
||||
*
|
||||
* The algorithm is as follows:
|
||||
*
|
||||
* o Set the two most significant bits (bits 6 and 7) of the
|
||||
* clock_seq_hi_and_reserved to zero and one, respectively.
|
||||
*
|
||||
* o Set the four most significant bits (bits 12 through 15) of the
|
||||
* time_hi_and_version field to the 4-bit version number from
|
||||
* Section 4.1.3.
|
||||
*
|
||||
* o Set all the other bits to randomly (or pseudo-randomly) chosen
|
||||
* values.
|
||||
*/
|
||||
void Device_UUID_Init(void)
|
||||
{
|
||||
unsigned i = 0;
|
||||
|
||||
/* 1. Generate 16 random bytes = 128 bits */
|
||||
for (i = 0; i < sizeof(Device_UUID); i++) {
|
||||
Device_UUID[i] = rand() % 256;
|
||||
}
|
||||
/* 2. Adjust certain bits according to RFC 4122 section 4.4.
|
||||
This just means do the following
|
||||
(a) set the high nibble of the 7th byte equal to 4 and
|
||||
(b) set the two most significant bits of the 9th byte to 10'B,
|
||||
so the high nibble will be one of {8,9,A,B}.
|
||||
From http://www.cryptosys.net/pki/Uuid.c.html */
|
||||
Device_UUID[6] = 0x40 | (Device_UUID[6] & 0x0f);
|
||||
Device_UUID[8] = 0x80 | (Device_UUID[8] & 0x3f);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the UUID for this device
|
||||
* @param new_uuid [in] The new UUID to set
|
||||
* @param length [in] The length of the new UUID
|
||||
*/
|
||||
void Device_UUID_Set(const uint8_t *new_uuid, size_t length)
|
||||
{
|
||||
if (new_uuid && (length == sizeof(Device_UUID))) {
|
||||
memcpy(Device_UUID, new_uuid, sizeof(Device_UUID));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the UUID for this device
|
||||
* @param uuid [out] The UUID of this device
|
||||
* @param length [in] The length of the UUID
|
||||
*/
|
||||
void Device_UUID_Get(uint8_t *uuid, size_t length)
|
||||
{
|
||||
if (uuid && (length == sizeof(Device_UUID))) {
|
||||
memcpy(uuid, Device_UUID, sizeof(Device_UUID));
|
||||
}
|
||||
}
|
||||
|
||||
BACNET_DEVICE_STATUS Device_System_Status(void)
|
||||
{
|
||||
return System_Status;
|
||||
@@ -1115,47 +1265,40 @@ int Device_Set_System_Status(BACNET_DEVICE_STATUS status, bool local)
|
||||
case STATUS_DOWNLOAD_REQUIRED:
|
||||
case STATUS_DOWNLOAD_IN_PROGRESS:
|
||||
case STATUS_NON_OPERATIONAL:
|
||||
#if defined BACNET_BACKUP_RESTORE
|
||||
case STATUS_BACKUP_IN_PROGRESS:
|
||||
#endif
|
||||
System_Status = status;
|
||||
break;
|
||||
|
||||
/* Don't support backup at present so don't allow setting */
|
||||
#ifndef BACNET_BACKUP_RESTORE
|
||||
case STATUS_BACKUP_IN_PROGRESS:
|
||||
result = -2;
|
||||
break;
|
||||
|
||||
#endif
|
||||
default:
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (status) {
|
||||
/* Allow these for the moment as a way to easily alter
|
||||
* overall device operation. The lack of password protection
|
||||
* or other authentication makes allowing writes to this
|
||||
* property a risky facility to provide.
|
||||
*/
|
||||
case STATUS_OPERATIONAL:
|
||||
case STATUS_OPERATIONAL_READ_ONLY:
|
||||
case STATUS_NON_OPERATIONAL:
|
||||
/* Allow these for the moment as a way to easily alter
|
||||
* overall device operation. The lack of password protection
|
||||
* or other authentication makes allowing writes to this
|
||||
* property a risky facility to provide. */
|
||||
System_Status = status;
|
||||
break;
|
||||
|
||||
/* Don't allow outsider set this - it should probably
|
||||
case STATUS_DOWNLOAD_REQUIRED:
|
||||
case STATUS_DOWNLOAD_IN_PROGRESS:
|
||||
case STATUS_BACKUP_IN_PROGRESS:
|
||||
/* Don't allow outsider set these - they should probably
|
||||
* be set if the device config is incomplete or
|
||||
* corrupted or perhaps after some sort of operator
|
||||
* wipe operation.
|
||||
*/
|
||||
case STATUS_DOWNLOAD_REQUIRED:
|
||||
/* Don't allow outsider set this - it should be set
|
||||
* internally at the start of a multi packet download
|
||||
* perhaps indirectly via PT or WF to a config file.
|
||||
*/
|
||||
case STATUS_DOWNLOAD_IN_PROGRESS:
|
||||
/* Don't support backup at present so don't allow setting */
|
||||
case STATUS_BACKUP_IN_PROGRESS:
|
||||
* wipe operation. */
|
||||
result = -2;
|
||||
break;
|
||||
|
||||
default:
|
||||
result = -1;
|
||||
break;
|
||||
@@ -1170,9 +1313,17 @@ const char *Device_Vendor_Name(void)
|
||||
return Vendor_Name;
|
||||
}
|
||||
|
||||
bool Device_Set_Vendor_Name(const char *name, size_t length)
|
||||
{
|
||||
(void)length;
|
||||
Vendor_Name = name;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Returns the Vendor ID for this Device.
|
||||
* See the assignments at
|
||||
* http://www.bacnet.org/VendorID/BACnet%20Vendor%20IDs.htm
|
||||
* https://bacnet.org/assigned-vendor-ids/
|
||||
* @return The Vendor ID of this Device.
|
||||
*/
|
||||
uint16_t Device_Vendor_Identifier(void)
|
||||
@@ -1678,8 +1829,197 @@ uint32_t Device_Interval_Offset(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* return the length of the apdu encoded or BACNET_STATUS_ERROR for error or
|
||||
BACNET_STATUS_ABORT for abort message */
|
||||
bool Device_Configuration_File_Set(unsigned index, uint32_t instance)
|
||||
{
|
||||
bool status = false;
|
||||
#if BACNET_BACKUP_FILE_COUNT
|
||||
if (index < BACNET_BACKUP_FILE_COUNT) {
|
||||
Configuration_Files[index] = instance;
|
||||
status = true;
|
||||
}
|
||||
#else
|
||||
(void)index;
|
||||
(void)instance;
|
||||
#endif
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
uint32_t Device_Configuration_File(unsigned index)
|
||||
{
|
||||
uint32_t instance = BACNET_MAX_INSTANCE + 1;
|
||||
|
||||
#if BACNET_BACKUP_FILE_COUNT
|
||||
if (index < BACNET_BACKUP_FILE_COUNT) {
|
||||
instance = Configuration_Files[index];
|
||||
}
|
||||
#else
|
||||
(void)index;
|
||||
#endif
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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_Configuration_File_Encode(
|
||||
uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu)
|
||||
{
|
||||
int apdu_len = BACNET_STATUS_ERROR;
|
||||
|
||||
(void)object_instance;
|
||||
#if BACNET_BACKUP_FILE_COUNT
|
||||
if (array_index < BACNET_BACKUP_FILE_COUNT) {
|
||||
apdu_len = encode_application_object_id(
|
||||
apdu, OBJECT_FILE, Configuration_Files[array_index]);
|
||||
}
|
||||
#else
|
||||
(void)array_index;
|
||||
(void)apdu;
|
||||
#endif
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
#if defined(BACNET_BACKUP_RESTORE)
|
||||
/**
|
||||
* @brief Decode a BACnetLIST property element to determine the element length
|
||||
* @param object_instance [in] BACnet object instance number
|
||||
* @param apdu [in] Buffer in which the APDU contents are extracted
|
||||
* @param apdu_size [in] The size of the APDU buffer
|
||||
* @return The length of the decoded apdu, or BACNET_STATUS_ERROR on error
|
||||
*/
|
||||
static int Device_Configuration_File_Length(
|
||||
uint32_t object_instance, uint8_t *apdu, size_t apdu_size)
|
||||
{
|
||||
(void)object_instance;
|
||||
return bacnet_object_id_application_decode(apdu, apdu_size, NULL, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write a value to a BACnetLIST property element value
|
||||
* using a BACnetARRAY write utility function
|
||||
* @param object_instance [in] BACnet object instance number
|
||||
* @param array_index [in] array index to write:
|
||||
* 0=array size, 1 to N for individual array members
|
||||
* @param application_data [in] encoded element value
|
||||
* @param application_data_len [in] The size of the encoded element value
|
||||
* @return BACNET_ERROR_CODE value
|
||||
*/
|
||||
static BACNET_ERROR_CODE Device_Configuration_File_Write(
|
||||
uint32_t object_instance,
|
||||
BACNET_ARRAY_INDEX array_index,
|
||||
uint8_t *application_data,
|
||||
size_t application_data_len)
|
||||
{
|
||||
BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
uint32_t instance = 0;
|
||||
BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
|
||||
int len = 0;
|
||||
|
||||
(void)object_instance;
|
||||
if (array_index == 0) {
|
||||
/* This array is not required to be resizable
|
||||
through BACnet write services */
|
||||
error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
|
||||
} else if (array_index <= BACNET_BACKUP_FILE_COUNT) {
|
||||
len = bacnet_object_id_application_decode(
|
||||
application_data, application_data_len, &object_type, &instance);
|
||||
if (len > 0) {
|
||||
if ((object_type == OBJECT_FILE) &&
|
||||
Device_Valid_Object_Id(object_type, instance)) {
|
||||
if (Device_Configuration_File_Set(array_index - 1, instance)) {
|
||||
error_code = ERROR_CODE_SUCCESS;
|
||||
} else {
|
||||
error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
} else {
|
||||
error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
} else {
|
||||
error_code = ERROR_CODE_INVALID_DATA_TYPE;
|
||||
}
|
||||
} else {
|
||||
error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
|
||||
}
|
||||
|
||||
return error_code;
|
||||
}
|
||||
|
||||
uint16_t Device_Backup_Failure_Timeout(void)
|
||||
{
|
||||
return Backup_Failure_Timeout;
|
||||
}
|
||||
|
||||
bool Device_Backup_Failure_Timeout_Set(uint16_t timeout)
|
||||
{
|
||||
Backup_Failure_Timeout = timeout;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t Device_Backup_Preparation_Time(void)
|
||||
{
|
||||
return Backup_Preparation_Time;
|
||||
}
|
||||
|
||||
bool Device_Backup_Preparation_Time_Set(uint16_t time)
|
||||
{
|
||||
Backup_Preparation_Time = time;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t Device_Restore_Preparation_Time(void)
|
||||
{
|
||||
return Restore_Preparation_Time;
|
||||
}
|
||||
|
||||
bool Device_Restore_Preparation_Time_Set(uint16_t time)
|
||||
{
|
||||
Restore_Preparation_Time = time;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t Device_Restore_Completion_Time(void)
|
||||
{
|
||||
return Restore_Completion_Time;
|
||||
}
|
||||
|
||||
bool Device_Restore_Completion_Time_Set(uint16_t time)
|
||||
{
|
||||
Restore_Completion_Time = time;
|
||||
return true;
|
||||
}
|
||||
|
||||
BACNET_BACKUP_STATE Device_Backup_And_Restore_State(void)
|
||||
{
|
||||
return Backup_State;
|
||||
}
|
||||
|
||||
bool Device_Backup_And_Restore_State_Set(BACNET_BACKUP_STATE state)
|
||||
{
|
||||
Backup_State = state;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* ReadProperty handler for this object. For the given ReadProperty
|
||||
* data, the application_data is loaded or the error flags are set.
|
||||
*
|
||||
* @param rpdata - BACNET_READ_PROPERTY_DATA data, including
|
||||
* requested data and space for the reply, or error response.
|
||||
*
|
||||
* @return number of APDU bytes in the response, zero if no data, or
|
||||
* BACNET_STATUS_ERROR on error.
|
||||
*/
|
||||
int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
{
|
||||
int apdu_len = 0; /* return value */
|
||||
@@ -1869,6 +2209,45 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
apdu_len =
|
||||
encode_application_unsigned(&apdu[0], Device_Interval_Offset());
|
||||
break;
|
||||
#endif
|
||||
#if defined(BACNET_BACKUP_RESTORE)
|
||||
case PROP_CONFIGURATION_FILES:
|
||||
apdu_len = bacnet_array_encode(
|
||||
rpdata->object_instance, rpdata->array_index,
|
||||
Device_Configuration_File_Encode, BACNET_BACKUP_FILE_COUNT,
|
||||
&apdu[0], apdu_max);
|
||||
if (apdu_len == BACNET_STATUS_ABORT) {
|
||||
#if BACNET_SEGMENTATION_ENABLED
|
||||
rpdata->error_code = ERROR_CODE_ABORT_BUFFER_OVERFLOW;
|
||||
#else
|
||||
rpdata->error_code =
|
||||
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
|
||||
#endif
|
||||
} else if (apdu_len == BACNET_STATUS_ERROR) {
|
||||
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
||||
rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
|
||||
}
|
||||
break;
|
||||
case PROP_BACKUP_FAILURE_TIMEOUT:
|
||||
apdu_len = encode_application_unsigned(
|
||||
&apdu[0], Device_Backup_Failure_Timeout());
|
||||
break;
|
||||
case PROP_BACKUP_PREPARATION_TIME:
|
||||
apdu_len = encode_application_unsigned(
|
||||
&apdu[0], Device_Backup_Preparation_Time());
|
||||
break;
|
||||
case PROP_RESTORE_PREPARATION_TIME:
|
||||
apdu_len = encode_application_unsigned(
|
||||
&apdu[0], Device_Restore_Preparation_Time());
|
||||
break;
|
||||
case PROP_RESTORE_COMPLETION_TIME:
|
||||
apdu_len = encode_application_unsigned(
|
||||
&apdu[0], Device_Restore_Completion_Time());
|
||||
break;
|
||||
case PROP_BACKUP_AND_RESTORE_STATE:
|
||||
apdu_len = encode_application_enumerated(
|
||||
&apdu[0], Device_Backup_And_Restore_State());
|
||||
break;
|
||||
#endif
|
||||
case PROP_ACTIVE_COV_SUBSCRIPTIONS:
|
||||
if ((apdu_len = handler_cov_encode_subscriptions(
|
||||
@@ -1902,7 +2281,8 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
* @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
|
||||
* @return number of APDU bytes in the response, zero if no data, or
|
||||
* BACNET_STATUS_ERROR on error.
|
||||
*/
|
||||
static int Read_Property_Common(
|
||||
const struct object_functions *pObject, BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
@@ -1914,7 +2294,7 @@ static int Read_Property_Common(
|
||||
struct special_property_list_t property_list;
|
||||
#endif
|
||||
|
||||
if ((rpdata->application_data == NULL) ||
|
||||
if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
|
||||
(rpdata->application_data_len == 0)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -1956,6 +2336,9 @@ int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
int apdu_len = BACNET_STATUS_ERROR;
|
||||
struct object_functions *pObject = NULL;
|
||||
|
||||
if (!rpdata) {
|
||||
return 0;
|
||||
}
|
||||
/* initialize the default return values */
|
||||
rpdata->error_class = ERROR_CLASS_OBJECT;
|
||||
rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
@@ -2147,6 +2530,60 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#if defined(BACNET_BACKUP_RESTORE)
|
||||
case PROP_BACKUP_FAILURE_TIMEOUT:
|
||||
status = write_property_type_valid(
|
||||
wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
|
||||
if (status) {
|
||||
if (value.type.Unsigned_Int <= UINT16_MAX) {
|
||||
Device_Backup_Failure_Timeout_Set(
|
||||
(uint16_t)value.type.Unsigned_Int);
|
||||
status = true;
|
||||
} else {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PROP_BACKUP_PREPARATION_TIME:
|
||||
status = write_property_type_valid(
|
||||
wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
|
||||
if (status) {
|
||||
if (value.type.Unsigned_Int <= UINT16_MAX) {
|
||||
Device_Backup_Preparation_Time_Set(
|
||||
(uint16_t)value.type.Unsigned_Int);
|
||||
status = true;
|
||||
} else {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PROP_RESTORE_PREPARATION_TIME:
|
||||
status = write_property_type_valid(
|
||||
wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
|
||||
if (status) {
|
||||
if (value.type.Unsigned_Int <= UINT16_MAX) {
|
||||
Device_Restore_Preparation_Time_Set(
|
||||
(uint16_t)value.type.Unsigned_Int);
|
||||
status = true;
|
||||
} else {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PROP_CONFIGURATION_FILES:
|
||||
wp_data->error_code = bacnet_array_write(
|
||||
wp_data->object_instance, wp_data->array_index,
|
||||
Device_Configuration_File_Length,
|
||||
Device_Configuration_File_Write, BACNET_BACKUP_FILE_COUNT,
|
||||
wp_data->application_data, wp_data->application_data_len);
|
||||
if (wp_data->error_code == ERROR_CODE_SUCCESS) {
|
||||
status = true;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case PROP_UTC_OFFSET:
|
||||
status = write_property_type_valid(
|
||||
@@ -2387,7 +2824,9 @@ int Device_Add_List_Element(BACNET_LIST_ELEMENT_DATA *list_element)
|
||||
if (pObject->Object_Add_List_Element) {
|
||||
status = pObject->Object_Add_List_Element(list_element);
|
||||
if (status) {
|
||||
Device_Add_List_Element_Callback(list_element);
|
||||
if (Device_Add_List_Element_Callback) {
|
||||
(void)Device_Add_List_Element_Callback(list_element);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
list_element->error_class = ERROR_CLASS_PROPERTY;
|
||||
@@ -2433,7 +2872,9 @@ int Device_Remove_List_Element(BACNET_LIST_ELEMENT_DATA *list_element)
|
||||
if (pObject->Object_Remove_List_Element) {
|
||||
status = pObject->Object_Remove_List_Element(list_element);
|
||||
if (status) {
|
||||
Device_Remove_List_Element_Callback(list_element);
|
||||
if (Device_Remove_List_Element_Callback) {
|
||||
(void)Device_Remove_List_Element_Callback(list_element);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
list_element->error_class = ERROR_CLASS_PROPERTY;
|
||||
@@ -2457,7 +2898,8 @@ int Device_Remove_List_Element(BACNET_LIST_ELEMENT_DATA *list_element)
|
||||
* @param [in] The object type to be looked up.
|
||||
* @param [in] The object instance number to be looked up.
|
||||
* @param [out] The value list
|
||||
* @return True if the object instance supports this feature and value changed.
|
||||
* @return True if the object instance supports this feature
|
||||
* and was encoded correctly
|
||||
*/
|
||||
bool Device_Encode_Value_List(
|
||||
BACNET_OBJECT_TYPE object_type,
|
||||
@@ -2605,6 +3047,74 @@ bool Device_Delete_Object(BACNET_DELETE_OBJECT_DATA *data)
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loop through the Device object-list property and export to
|
||||
* a file as BACnet CreateObject services with List of Initial Values
|
||||
* for every writable property
|
||||
*/
|
||||
void Device_Start_Backup(void)
|
||||
{
|
||||
#if defined BACNET_BACKUP_RESTORE
|
||||
size_t i = 0;
|
||||
uint32_t object_count = 0;
|
||||
BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
|
||||
uint32_t object_instance = 0;
|
||||
const int32_t *writable_properties = NULL;
|
||||
struct special_property_list_t property_list = { 0 };
|
||||
uint8_t object_apdu[MAX_APDU] = { 0 };
|
||||
BACNET_CREATE_OBJECT_DATA create_data = { 0 };
|
||||
bool status = false;
|
||||
int32_t len = 0, offset = 0;
|
||||
|
||||
Backup_State = BACKUP_STATE_PREPARING_FOR_BACKUP;
|
||||
object_count = Device_Object_List_Count();
|
||||
Backup_State = BACKUP_STATE_PERFORMING_A_BACKUP;
|
||||
for (i = 0; i < object_count; i++) {
|
||||
/* get the object type and instance from the device object list */
|
||||
status = Device_Object_List_Identifier(
|
||||
(uint32_t)(i + 1), &object_type, &object_instance);
|
||||
if (status) {
|
||||
Device_Objects_Property_List(
|
||||
object_type, object_instance, &property_list);
|
||||
(void)Device_Objects_Writable_Property_List(
|
||||
object_type, object_instance, &writable_properties);
|
||||
create_data.object_type = object_type;
|
||||
create_data.object_instance = object_instance;
|
||||
create_data.application_data_len = 0;
|
||||
len = create_object_writable_properties_encode(
|
||||
object_apdu, sizeof(object_apdu), &create_data,
|
||||
property_list.Required.pList, property_list.Optional.pList,
|
||||
property_list.Proprietary.pList, writable_properties,
|
||||
Device_Read_Property);
|
||||
if (len > 0) {
|
||||
#if defined(BACFILE)
|
||||
(void)bacfile_write_offset(
|
||||
Configuration_Files[0], offset, &object_apdu[0],
|
||||
(uint32_t)len);
|
||||
#endif
|
||||
offset += len;
|
||||
}
|
||||
}
|
||||
}
|
||||
Backup_State = BACKUP_STATE_IDLE;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loop through the Device object-list property and export to
|
||||
* a file as BACnet CreateObject services with List of Initial Values
|
||||
* for every writable property
|
||||
*/
|
||||
void Device_Start_Restore(void)
|
||||
{
|
||||
#if defined BACNET_BACKUP_RESTORE
|
||||
BACNET_DATE_TIME bdateTime = { 0 };
|
||||
datetime_local(&bdateTime.date, &bdateTime.time, NULL, NULL);
|
||||
bacapp_timestamp_datetime_set(&Last_Restore_Time, &bdateTime);
|
||||
Backup_State = BACKUP_STATE_PREPARING_FOR_RESTORE;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(INTRINSIC_REPORTING)
|
||||
void Device_local_reporting(void)
|
||||
{
|
||||
|
||||
@@ -241,6 +241,31 @@ uint32_t Device_Interval_Offset(void);
|
||||
BACNET_STACK_EXPORT
|
||||
bool Device_Interval_Offset_Set(uint32_t value);
|
||||
|
||||
BACNET_STACK_EXPORT
|
||||
bool Device_Configuration_File_Set(unsigned index, uint32_t instance);
|
||||
BACNET_STACK_EXPORT
|
||||
uint32_t Device_Configuration_File(unsigned index);
|
||||
BACNET_STACK_EXPORT
|
||||
uint16_t Device_Backup_Failure_Timeout(void);
|
||||
BACNET_STACK_EXPORT
|
||||
bool Device_Backup_Failure_Timeout_Set(uint16_t timeout);
|
||||
BACNET_STACK_EXPORT
|
||||
uint16_t Device_Backup_Preparation_Time(void);
|
||||
BACNET_STACK_EXPORT
|
||||
bool Device_Backup_Preparation_Time_Set(uint16_t time);
|
||||
BACNET_STACK_EXPORT
|
||||
uint16_t Device_Restore_Preparation_Time(void);
|
||||
BACNET_STACK_EXPORT
|
||||
bool Device_Restore_Preparation_Time_Set(uint16_t time);
|
||||
BACNET_STACK_EXPORT
|
||||
uint16_t Device_Restore_Completion_Time(void);
|
||||
BACNET_STACK_EXPORT
|
||||
bool Device_Restore_Completion_Time_Set(uint16_t time);
|
||||
BACNET_STACK_EXPORT
|
||||
BACNET_BACKUP_STATE Device_Backup_And_Restore_State(void);
|
||||
BACNET_STACK_EXPORT
|
||||
bool Device_Backup_And_Restore_State_Set(BACNET_BACKUP_STATE state);
|
||||
|
||||
BACNET_STACK_EXPORT
|
||||
void Device_Property_Lists(
|
||||
const int32_t **pRequired,
|
||||
@@ -305,6 +330,11 @@ bool Device_Create_Object(BACNET_CREATE_OBJECT_DATA *data);
|
||||
BACNET_STACK_EXPORT
|
||||
bool Device_Delete_Object(BACNET_DELETE_OBJECT_DATA *data);
|
||||
|
||||
BACNET_STACK_EXPORT
|
||||
void Device_Start_Backup(void);
|
||||
BACNET_STACK_EXPORT
|
||||
void Device_Start_Restore(void);
|
||||
|
||||
BACNET_STACK_EXPORT
|
||||
unsigned Device_Count(void);
|
||||
BACNET_STACK_EXPORT
|
||||
|
||||
@@ -1091,14 +1091,21 @@ static const int32_t Device_Properties_Optional[] = {
|
||||
#if (BACNET_COV_SUBSCRIPTIONS_SIZE > 0)
|
||||
PROP_ACTIVE_COV_SUBSCRIPTIONS,
|
||||
#endif
|
||||
PROP_SERIAL_NUMBER,
|
||||
PROP_TIME_OF_DEVICE_RESTART,
|
||||
#if defined(BACNET_TIME_MASTER)
|
||||
PROP_TIME_SYNCHRONIZATION_RECIPIENTS,
|
||||
PROP_TIME_SYNCHRONIZATION_INTERVAL,
|
||||
PROP_ALIGN_INTERVALS,
|
||||
PROP_INTERVAL_OFFSET,
|
||||
#endif
|
||||
PROP_SERIAL_NUMBER,
|
||||
PROP_TIME_OF_DEVICE_RESTART,
|
||||
#if defined(BACNET_BACKUP_RESTORE)
|
||||
PROP_CONFIGURATION_FILES,
|
||||
PROP_BACKUP_FAILURE_TIMEOUT,
|
||||
PROP_BACKUP_PREPARATION_TIME,
|
||||
PROP_RESTORE_PREPARATION_TIME,
|
||||
PROP_BACKUP_AND_RESTORE_STATE,
|
||||
#endif
|
||||
-1
|
||||
};
|
||||
|
||||
@@ -1114,23 +1121,29 @@ static const int32_t Device_Properties_Proprietary[] = {
|
||||
static const int32_t Writable_Properties[] = {
|
||||
/* unordered list of writable properties */
|
||||
PROP_OBJECT_IDENTIFIER,
|
||||
PROP_NUMBER_OF_APDU_RETRIES,
|
||||
PROP_APDU_TIMEOUT,
|
||||
PROP_VENDOR_IDENTIFIER,
|
||||
PROP_SYSTEM_STATUS,
|
||||
PROP_OBJECT_NAME,
|
||||
PROP_SYSTEM_STATUS,
|
||||
PROP_VENDOR_IDENTIFIER,
|
||||
PROP_MODEL_NAME,
|
||||
PROP_LOCATION,
|
||||
PROP_DESCRIPTION,
|
||||
PROP_MODEL_NAME,
|
||||
PROP_APDU_TIMEOUT,
|
||||
PROP_NUMBER_OF_APDU_RETRIES,
|
||||
PROP_UTC_OFFSET,
|
||||
#if defined(BACNET_TIME_MASTER)
|
||||
PROP_TIME_SYNCHRONIZATION_INTERVAL,
|
||||
PROP_ALIGN_INTERVALS,
|
||||
PROP_INTERVAL_OFFSET,
|
||||
#endif
|
||||
PROP_UTC_OFFSET,
|
||||
#if defined(BACDL_MSTP)
|
||||
PROP_MAX_INFO_FRAMES,
|
||||
PROP_MAX_MASTER,
|
||||
#endif
|
||||
#if defined(BACNET_BACKUP_RESTORE)
|
||||
PROP_CONFIGURATION_FILES,
|
||||
PROP_BACKUP_FAILURE_TIMEOUT,
|
||||
PROP_BACKUP_PREPARATION_TIME,
|
||||
PROP_RESTORE_PREPARATION_TIME,
|
||||
#endif
|
||||
PROP_TIME_OF_DEVICE_RESTART,
|
||||
-1
|
||||
@@ -1249,6 +1262,59 @@ static uint32_t Interval_Offset_Minutes;
|
||||
/* Time_Synchronization_Recipients */
|
||||
#endif
|
||||
|
||||
/* backup and restore */
|
||||
#if defined BACNET_BACKUP_RESTORE
|
||||
/* number of backup files */
|
||||
#ifndef BACNET_BACKUP_FILE_COUNT
|
||||
#if defined(BACFILE)
|
||||
#define BACNET_BACKUP_FILE_COUNT 1
|
||||
#else
|
||||
#define BACNET_BACKUP_FILE_COUNT 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BACNET_BACKUP_FILE_COUNT
|
||||
/* device A will read the Configuration_Files property of the Device object.
|
||||
This property will be used to determine the files to read and in what
|
||||
order the files will be read. The value of the Configuration_Files
|
||||
property is not guaranteed to contain a complete or correct set of
|
||||
configuration File object references before the backup request is
|
||||
accepted by device B. */
|
||||
static uint32_t Configuration_Files[BACNET_BACKUP_FILE_COUNT];
|
||||
#endif
|
||||
/* If the restore is successful, no other actions by device A shall
|
||||
be required, and device B will update the Last_Restore_Time property
|
||||
in its Device object. */
|
||||
static BACNET_TIMESTAMP Last_Restore_Time;
|
||||
/* If device B does not receive any messages related to the restore
|
||||
procedure from device A for the number of seconds specified
|
||||
in the Backup_Failure_Timeout property of its Device object,
|
||||
device B should assume that the restore procedure has been aborted,
|
||||
and device B should exit restore mode.*/
|
||||
static uint16_t Backup_Failure_Timeout;
|
||||
/* This property indicates the amount of time in seconds
|
||||
that the device might remain unresponsive after the sending
|
||||
of a ReinitializeDevice-ACK at the start of a backup procedure.
|
||||
The device that initiated the backup shall either wait the
|
||||
period of time specified by this property or be prepared
|
||||
to encounter communication timeouts during this period. */
|
||||
static uint16_t Backup_Preparation_Time;
|
||||
/* This property indicates the amount of time in seconds that the device
|
||||
is allowed to remain unresponsive after the sending of a
|
||||
ReinitializeDevice-ACK at the start of a restore procedure.
|
||||
The restoring device shall either wait or be prepared to
|
||||
encounter communication timeouts during this period.*/
|
||||
static uint16_t Restore_Preparation_Time;
|
||||
/* This property indicates the amount of time in seconds that
|
||||
the device is allowed to remain unresponsive after the sending
|
||||
of a ReinitializeDevice-ACK at the end of a restore procedure.
|
||||
The restoring device shall either wait or be prepared to
|
||||
encounter communication timeouts during this period. */
|
||||
static uint16_t Restore_Completion_Time;
|
||||
/* This property indicates a server device's backup and restore state. */
|
||||
static BACNET_BACKUP_STATE Backup_State = BACKUP_STATE_IDLE;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Sets the ReinitializeDevice password
|
||||
*
|
||||
@@ -1286,6 +1352,7 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data)
|
||||
bool status = false;
|
||||
bool password_success = false;
|
||||
unsigned i;
|
||||
size_t length;
|
||||
|
||||
/* From 16.4.1.1.2 Password
|
||||
This optional parameter shall be a CharacterString of up to
|
||||
@@ -1294,7 +1361,12 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data)
|
||||
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) {
|
||||
if (characterstring_encoding(&rd_data->password) == CHARACTER_UTF8) {
|
||||
length = characterstring_utf8_length(&rd_data->password);
|
||||
} else {
|
||||
length = characterstring_length(&rd_data->password);
|
||||
}
|
||||
if (length > 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)) {
|
||||
@@ -1309,19 +1381,46 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data)
|
||||
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_WARMSTART:
|
||||
dcc_set_status_duration(COMMUNICATION_ENABLE, 0);
|
||||
for (i = 0; i < Network_Port_Count(); i++) {
|
||||
Network_Port_Changes_Pending_Activate(
|
||||
Network_Port_Index_To_Instance(i));
|
||||
}
|
||||
/* 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;
|
||||
#if defined BACNET_BACKUP_RESTORE
|
||||
case BACNET_REINIT_STARTBACKUP:
|
||||
case BACNET_REINIT_ENDBACKUP:
|
||||
Device_Start_Backup();
|
||||
Reinitialize_State = rd_data->state;
|
||||
status = true;
|
||||
break;
|
||||
case BACNET_REINIT_STARTRESTORE:
|
||||
Device_Start_Restore();
|
||||
Reinitialize_State = rd_data->state;
|
||||
status = true;
|
||||
break;
|
||||
case BACNET_REINIT_ENDBACKUP:
|
||||
case BACNET_REINIT_ENDRESTORE:
|
||||
case BACNET_REINIT_ABORTRESTORE:
|
||||
Reinitialize_State = rd_data->state;
|
||||
status = true;
|
||||
break;
|
||||
#else
|
||||
case BACNET_REINIT_STARTBACKUP:
|
||||
case BACNET_REINIT_STARTRESTORE:
|
||||
case BACNET_REINIT_ENDBACKUP:
|
||||
case BACNET_REINIT_ENDRESTORE:
|
||||
case BACNET_REINIT_ABORTRESTORE:
|
||||
if (dcc_communication_disabled()) {
|
||||
@@ -1333,6 +1432,7 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data)
|
||||
ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case BACNET_REINIT_ACTIVATE_CHANGES:
|
||||
/* note: activate changes *after* the simple ack is sent */
|
||||
for (i = 0; i < Network_Port_Count(); i++) {
|
||||
@@ -1390,7 +1490,9 @@ bool Device_Set_Object_Instance_Number(uint32_t object_id)
|
||||
bool status = true; /* return value */
|
||||
|
||||
if (object_id <= BACNET_MAX_INSTANCE) {
|
||||
/* Make the change and update the database revision */
|
||||
Object_Instance_Number = object_id;
|
||||
Device_Inc_Database_Revision();
|
||||
} else {
|
||||
status = false;
|
||||
}
|
||||
@@ -1519,16 +1621,58 @@ BACNET_DEVICE_STATUS Device_System_Status(void)
|
||||
|
||||
int Device_Set_System_Status(BACNET_DEVICE_STATUS status, bool local)
|
||||
{
|
||||
/*return value - 0 = ok, -1 = bad value, -2 = not allowed */
|
||||
int result = -1;
|
||||
int result = 0; /*return value - 0 = ok, -1 = bad value, -2 = not allowed */
|
||||
|
||||
(void)local;
|
||||
if (status < MAX_DEVICE_STATUS) {
|
||||
/* We limit the options available depending on whether the source is
|
||||
* internal or external. */
|
||||
if (local) {
|
||||
switch (status) {
|
||||
case STATUS_OPERATIONAL:
|
||||
case STATUS_OPERATIONAL_READ_ONLY:
|
||||
case STATUS_DOWNLOAD_REQUIRED:
|
||||
case STATUS_DOWNLOAD_IN_PROGRESS:
|
||||
case STATUS_NON_OPERATIONAL:
|
||||
#if defined BACNET_BACKUP_RESTORE
|
||||
case STATUS_BACKUP_IN_PROGRESS:
|
||||
#endif
|
||||
System_Status = status;
|
||||
result = 0;
|
||||
break;
|
||||
#ifndef BACNET_BACKUP_RESTORE
|
||||
case STATUS_BACKUP_IN_PROGRESS:
|
||||
result = -2;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (status) {
|
||||
case STATUS_OPERATIONAL:
|
||||
case STATUS_OPERATIONAL_READ_ONLY:
|
||||
case STATUS_NON_OPERATIONAL:
|
||||
/* Allow these for the moment as a way to easily alter
|
||||
* overall device operation. The lack of password protection
|
||||
* or other authentication makes allowing writes to this
|
||||
* property a risky facility to provide. */
|
||||
System_Status = status;
|
||||
break;
|
||||
case STATUS_DOWNLOAD_REQUIRED:
|
||||
case STATUS_DOWNLOAD_IN_PROGRESS:
|
||||
case STATUS_BACKUP_IN_PROGRESS:
|
||||
/* Don't allow outsider set these - they should probably
|
||||
* be set if the device config is incomplete or
|
||||
* corrupted or perhaps after some sort of operator
|
||||
* wipe operation. */
|
||||
result = -2;
|
||||
break;
|
||||
default:
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return (result);
|
||||
}
|
||||
|
||||
const char *Device_Vendor_Name(void)
|
||||
@@ -1544,6 +1688,11 @@ bool Device_Set_Vendor_Name(const char *name, size_t length)
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Returns the Vendor ID for this Device.
|
||||
* See the assignments at
|
||||
* https://bacnet.org/assigned-vendor-ids/
|
||||
* @return The Vendor ID of this Device.
|
||||
*/
|
||||
uint16_t Device_Vendor_Identifier(void)
|
||||
{
|
||||
return Vendor_Identifier;
|
||||
@@ -1670,7 +1819,11 @@ uint8_t Device_Protocol_Revision(void)
|
||||
|
||||
BACNET_SEGMENTATION Device_Segmentation_Supported(void)
|
||||
{
|
||||
#if BACNET_SEGMENTATION_ENABLED
|
||||
return SEGMENTATION_BOTH;
|
||||
#else
|
||||
return SEGMENTATION_NONE;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1824,7 +1977,7 @@ bool Device_Valid_Object_Name(
|
||||
for (i = 1; i <= max_objects; i++) {
|
||||
check_id = Device_Object_List_Identifier(i, &type, &instance);
|
||||
if (check_id) {
|
||||
pObject = Device_Object_Functions_Find((BACNET_OBJECT_TYPE)type);
|
||||
pObject = Device_Object_Functions_Find(type);
|
||||
if ((pObject != NULL) && (pObject->Object_Name != NULL) &&
|
||||
(pObject->Object_Name(instance, &object_name2) &&
|
||||
characterstring_same(object_name1, &object_name2))) {
|
||||
@@ -1854,7 +2007,7 @@ bool Device_Valid_Object_Id(
|
||||
bool status = false; /* return value */
|
||||
struct object_functions *pObject = NULL;
|
||||
|
||||
pObject = Device_Object_Functions_Find((BACNET_OBJECT_TYPE)object_type);
|
||||
pObject = Device_Object_Functions_Find(object_type);
|
||||
if ((pObject != NULL) && (pObject->Object_Valid_Instance != NULL)) {
|
||||
status = pObject->Object_Valid_Instance(object_instance);
|
||||
}
|
||||
@@ -1877,9 +2030,14 @@ bool Device_Object_Name_Copy(
|
||||
bool found = false;
|
||||
|
||||
pObject = Device_Object_Functions_Find(object_type);
|
||||
if ((pObject != NULL) && (pObject->Object_Name != NULL)) {
|
||||
if (pObject != NULL) {
|
||||
if (pObject->Object_Valid_Instance &&
|
||||
pObject->Object_Valid_Instance(object_instance)) {
|
||||
if (pObject->Object_Name) {
|
||||
found = pObject->Object_Name(object_instance, object_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
@@ -1989,6 +2147,187 @@ uint32_t Device_Interval_Offset(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Device_Configuration_File_Set(unsigned index, uint32_t instance)
|
||||
{
|
||||
bool status = false;
|
||||
#if BACNET_BACKUP_FILE_COUNT
|
||||
if (index < BACNET_BACKUP_FILE_COUNT) {
|
||||
Configuration_Files[index] = instance;
|
||||
status = true;
|
||||
}
|
||||
#else
|
||||
(void)index;
|
||||
(void)instance;
|
||||
#endif
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
uint32_t Device_Configuration_File(unsigned index)
|
||||
{
|
||||
uint32_t instance = BACNET_MAX_INSTANCE + 1;
|
||||
|
||||
#if BACNET_BACKUP_FILE_COUNT
|
||||
if (index < BACNET_BACKUP_FILE_COUNT) {
|
||||
instance = Configuration_Files[index];
|
||||
}
|
||||
#else
|
||||
(void)index;
|
||||
#endif
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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_Configuration_File_Encode(
|
||||
uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu)
|
||||
{
|
||||
int apdu_len = BACNET_STATUS_ERROR;
|
||||
|
||||
(void)object_instance;
|
||||
#if BACNET_BACKUP_FILE_COUNT
|
||||
if (array_index < BACNET_BACKUP_FILE_COUNT) {
|
||||
apdu_len = encode_application_object_id(
|
||||
apdu, OBJECT_FILE, Configuration_Files[array_index]);
|
||||
}
|
||||
#else
|
||||
(void)array_index;
|
||||
(void)apdu;
|
||||
#endif
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
#if defined BACNET_BACKUP_RESTORE
|
||||
/**
|
||||
* @brief Decode a BACnetLIST property element to determine the element length
|
||||
* @param object_instance [in] BACnet object instance number
|
||||
* @param apdu [in] Buffer in which the APDU contents are extracted
|
||||
* @param apdu_size [in] The size of the APDU buffer
|
||||
* @return The length of the decoded apdu, or BACNET_STATUS_ERROR on error
|
||||
*/
|
||||
static int Device_Configuration_File_Length(
|
||||
uint32_t object_instance, uint8_t *apdu, size_t apdu_size)
|
||||
{
|
||||
(void)object_instance;
|
||||
return bacnet_object_id_application_decode(apdu, apdu_size, NULL, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write a value to a BACnetLIST property element value
|
||||
* using a BACnetARRAY write utility function
|
||||
* @param object_instance [in] BACnet object instance number
|
||||
* @param array_index [in] array index to write:
|
||||
* 0=array size, 1 to N for individual array members
|
||||
* @param application_data [in] encoded element value
|
||||
* @param application_data_len [in] The size of the encoded element value
|
||||
* @return BACNET_ERROR_CODE value
|
||||
*/
|
||||
static BACNET_ERROR_CODE Device_Configuration_File_Write(
|
||||
uint32_t object_instance,
|
||||
BACNET_ARRAY_INDEX array_index,
|
||||
uint8_t *application_data,
|
||||
size_t application_data_len)
|
||||
{
|
||||
BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
uint32_t instance = 0;
|
||||
BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
|
||||
int len = 0;
|
||||
|
||||
(void)object_instance;
|
||||
if (array_index == 0) {
|
||||
/* This array is not required to be resizable
|
||||
through BACnet write services */
|
||||
error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
|
||||
} else if (array_index <= BACNET_BACKUP_FILE_COUNT) {
|
||||
len = bacnet_object_id_application_decode(
|
||||
application_data, application_data_len, &object_type, &instance);
|
||||
if (len > 0) {
|
||||
if ((object_type == OBJECT_FILE) &&
|
||||
Device_Valid_Object_Id(object_type, instance)) {
|
||||
if (Device_Configuration_File_Set(array_index - 1, instance)) {
|
||||
error_code = ERROR_CODE_SUCCESS;
|
||||
} else {
|
||||
error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
} else {
|
||||
error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
} else {
|
||||
error_code = ERROR_CODE_INVALID_DATA_TYPE;
|
||||
}
|
||||
} else {
|
||||
error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
|
||||
}
|
||||
|
||||
return error_code;
|
||||
}
|
||||
|
||||
uint16_t Device_Backup_Failure_Timeout(void)
|
||||
{
|
||||
return Backup_Failure_Timeout;
|
||||
}
|
||||
|
||||
bool Device_Backup_Failure_Timeout_Set(uint16_t timeout)
|
||||
{
|
||||
Backup_Failure_Timeout = timeout;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t Device_Backup_Preparation_Time(void)
|
||||
{
|
||||
return Backup_Preparation_Time;
|
||||
}
|
||||
|
||||
bool Device_Backup_Preparation_Time_Set(uint16_t time)
|
||||
{
|
||||
Backup_Preparation_Time = time;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t Device_Restore_Preparation_Time(void)
|
||||
{
|
||||
return Restore_Preparation_Time;
|
||||
}
|
||||
|
||||
bool Device_Restore_Preparation_Time_Set(uint16_t time)
|
||||
{
|
||||
Restore_Preparation_Time = time;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t Device_Restore_Completion_Time(void)
|
||||
{
|
||||
return Restore_Completion_Time;
|
||||
}
|
||||
|
||||
bool Device_Restore_Completion_Time_Set(uint16_t time)
|
||||
{
|
||||
Restore_Completion_Time = time;
|
||||
return true;
|
||||
}
|
||||
|
||||
BACNET_BACKUP_STATE Device_Backup_And_Restore_State(void)
|
||||
{
|
||||
return Backup_State;
|
||||
}
|
||||
|
||||
bool Device_Backup_And_Restore_State_Set(BACNET_BACKUP_STATE state)
|
||||
{
|
||||
Backup_State = state;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* ReadProperty handler for this object. For the given ReadProperty
|
||||
* data, the application_data is loaded or the error flags are set.
|
||||
@@ -2128,8 +2467,12 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
rpdata->object_instance, rpdata->array_index,
|
||||
Device_Object_List_Element_Encode, count, apdu, apdu_max);
|
||||
if (apdu_len == BACNET_STATUS_ABORT) {
|
||||
#if BACNET_SEGMENTATION_ENABLED
|
||||
rpdata->error_code = ERROR_CODE_ABORT_BUFFER_OVERFLOW;
|
||||
#else
|
||||
rpdata->error_code =
|
||||
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
|
||||
#endif
|
||||
} else if (apdu_len == BACNET_STATUS_ERROR) {
|
||||
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
||||
rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
|
||||
@@ -2187,6 +2530,45 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
encode_application_unsigned(&apdu[0], Device_Interval_Offset());
|
||||
break;
|
||||
#endif
|
||||
#if defined(BACNET_BACKUP_RESTORE)
|
||||
case PROP_CONFIGURATION_FILES:
|
||||
apdu_len = bacnet_array_encode(
|
||||
rpdata->object_instance, rpdata->array_index,
|
||||
Device_Configuration_File_Encode, BACNET_BACKUP_FILE_COUNT,
|
||||
&apdu[0], apdu_max);
|
||||
if (apdu_len == BACNET_STATUS_ABORT) {
|
||||
#if BACNET_SEGMENTATION_ENABLED
|
||||
rpdata->error_code = ERROR_CODE_ABORT_BUFFER_OVERFLOW;
|
||||
#else
|
||||
rpdata->error_code =
|
||||
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
|
||||
#endif
|
||||
} else if (apdu_len == BACNET_STATUS_ERROR) {
|
||||
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
||||
rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
|
||||
}
|
||||
break;
|
||||
case PROP_BACKUP_FAILURE_TIMEOUT:
|
||||
apdu_len = encode_application_unsigned(
|
||||
&apdu[0], Device_Backup_Failure_Timeout());
|
||||
break;
|
||||
case PROP_BACKUP_PREPARATION_TIME:
|
||||
apdu_len = encode_application_unsigned(
|
||||
&apdu[0], Device_Backup_Preparation_Time());
|
||||
break;
|
||||
case PROP_RESTORE_PREPARATION_TIME:
|
||||
apdu_len = encode_application_unsigned(
|
||||
&apdu[0], Device_Restore_Preparation_Time());
|
||||
break;
|
||||
case PROP_RESTORE_COMPLETION_TIME:
|
||||
apdu_len = encode_application_unsigned(
|
||||
&apdu[0], Device_Restore_Completion_Time());
|
||||
break;
|
||||
case PROP_BACKUP_AND_RESTORE_STATE:
|
||||
apdu_len = encode_application_enumerated(
|
||||
&apdu[0], Device_Backup_And_Restore_State());
|
||||
break;
|
||||
#endif
|
||||
#if (BACNET_COV_SUBSCRIPTIONS_SIZE > 0)
|
||||
case PROP_ACTIVE_COV_SUBSCRIPTIONS:
|
||||
if ((apdu_len = handler_cov_encode_subscriptions(
|
||||
@@ -2469,6 +2851,60 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#if defined(BACNET_BACKUP_RESTORE)
|
||||
case PROP_BACKUP_FAILURE_TIMEOUT:
|
||||
status = write_property_type_valid(
|
||||
wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
|
||||
if (status) {
|
||||
if (value.type.Unsigned_Int <= UINT16_MAX) {
|
||||
Device_Backup_Failure_Timeout_Set(
|
||||
(uint16_t)value.type.Unsigned_Int);
|
||||
status = true;
|
||||
} else {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PROP_BACKUP_PREPARATION_TIME:
|
||||
status = write_property_type_valid(
|
||||
wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
|
||||
if (status) {
|
||||
if (value.type.Unsigned_Int <= UINT16_MAX) {
|
||||
Device_Backup_Preparation_Time_Set(
|
||||
(uint16_t)value.type.Unsigned_Int);
|
||||
status = true;
|
||||
} else {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PROP_RESTORE_PREPARATION_TIME:
|
||||
status = write_property_type_valid(
|
||||
wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
|
||||
if (status) {
|
||||
if (value.type.Unsigned_Int <= UINT16_MAX) {
|
||||
Device_Restore_Preparation_Time_Set(
|
||||
(uint16_t)value.type.Unsigned_Int);
|
||||
status = true;
|
||||
} else {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PROP_CONFIGURATION_FILES:
|
||||
wp_data->error_code = bacnet_array_write(
|
||||
wp_data->object_instance, wp_data->array_index,
|
||||
Device_Configuration_File_Length,
|
||||
Device_Configuration_File_Write, BACNET_BACKUP_FILE_COUNT,
|
||||
wp_data->application_data, wp_data->application_data_len);
|
||||
if (wp_data->error_code == ERROR_CODE_SUCCESS) {
|
||||
status = true;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case PROP_UTC_OFFSET:
|
||||
status = write_property_type_valid(
|
||||
@@ -2759,7 +3195,9 @@ int Device_Remove_List_Element(BACNET_LIST_ELEMENT_DATA *list_element)
|
||||
if (pObject->Object_Remove_List_Element) {
|
||||
status = pObject->Object_Remove_List_Element(list_element);
|
||||
if (status) {
|
||||
Device_Remove_List_Element_Callback(list_element);
|
||||
if (Device_Remove_List_Element_Callback) {
|
||||
(void)Device_Remove_List_Element_Callback(list_element);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
list_element->error_class = ERROR_CLASS_PROPERTY;
|
||||
@@ -2932,6 +3370,102 @@ bool Device_Delete_Object(BACNET_DELETE_OBJECT_DATA *data)
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loop through the Device object-list property and export to
|
||||
* a file as BACnet CreateObject services with List of Initial Values
|
||||
* for every writable property
|
||||
*/
|
||||
void Device_Start_Backup(void)
|
||||
{
|
||||
#if defined BACNET_BACKUP_RESTORE
|
||||
size_t i = 0;
|
||||
uint32_t object_count = 0;
|
||||
BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
|
||||
uint32_t object_instance = 0;
|
||||
const int32_t *writable_properties = NULL;
|
||||
struct special_property_list_t property_list = { 0 };
|
||||
uint8_t object_apdu[MAX_APDU] = { 0 };
|
||||
BACNET_CREATE_OBJECT_DATA create_data = { 0 };
|
||||
bool status = false;
|
||||
int32_t len = 0, offset = 0;
|
||||
|
||||
Backup_State = BACKUP_STATE_PREPARING_FOR_BACKUP;
|
||||
object_count = Device_Object_List_Count();
|
||||
Backup_State = BACKUP_STATE_PERFORMING_A_BACKUP;
|
||||
for (i = 0; i < object_count; i++) {
|
||||
/* get the object type and instance from the device object list */
|
||||
status = Device_Object_List_Identifier(
|
||||
(uint32_t)(i + 1), &object_type, &object_instance);
|
||||
if (status) {
|
||||
Device_Objects_Property_List(
|
||||
object_type, object_instance, &property_list);
|
||||
(void)Device_Objects_Writable_Property_List(
|
||||
object_type, object_instance, &writable_properties);
|
||||
create_data.object_type = object_type;
|
||||
create_data.object_instance = object_instance;
|
||||
create_data.application_data_len = 0;
|
||||
len = create_object_writable_properties_encode(
|
||||
object_apdu, sizeof(object_apdu), &create_data,
|
||||
property_list.Required.pList, property_list.Optional.pList,
|
||||
property_list.Proprietary.pList, writable_properties,
|
||||
Device_Read_Property);
|
||||
if (len > 0) {
|
||||
#if defined(BACFILE)
|
||||
(void)bacfile_write_offset(
|
||||
Configuration_Files[0], offset, &object_apdu[0],
|
||||
(uint32_t)len);
|
||||
#endif
|
||||
offset += len;
|
||||
}
|
||||
}
|
||||
}
|
||||
Backup_State = BACKUP_STATE_IDLE;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loop through the Device object-list property and export to
|
||||
* a file as BACnet CreateObject services with List of Initial Values
|
||||
* for every writable property
|
||||
*/
|
||||
void Device_Start_Restore(void)
|
||||
{
|
||||
#if defined BACNET_BACKUP_RESTORE
|
||||
BACNET_DATE_TIME bdateTime = { 0 };
|
||||
datetime_local(&bdateTime.date, &bdateTime.time, NULL, NULL);
|
||||
bacapp_timestamp_datetime_set(&Last_Restore_Time, &bdateTime);
|
||||
Backup_State = BACKUP_STATE_PREPARING_FOR_RESTORE;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(INTRINSIC_REPORTING)
|
||||
void Device_local_reporting(void)
|
||||
{
|
||||
struct object_functions *pObject = NULL;
|
||||
uint32_t objects_count = 0;
|
||||
uint32_t object_instance = 0;
|
||||
BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
|
||||
uint32_t idx = 0;
|
||||
|
||||
objects_count = Device_Object_List_Count();
|
||||
|
||||
/* loop for all objects */
|
||||
for (idx = 1; idx <= objects_count; idx++) {
|
||||
Device_Object_List_Identifier(idx, &object_type, &object_instance);
|
||||
|
||||
pObject = Device_Object_Functions_Find(object_type);
|
||||
if (pObject != NULL) {
|
||||
if (pObject->Object_Valid_Instance &&
|
||||
pObject->Object_Valid_Instance(object_instance)) {
|
||||
if (pObject->Object_Intrinsic_Reporting) {
|
||||
pObject->Object_Intrinsic_Reporting(object_instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Looks up the requested Object to see if the functionality is supported.
|
||||
* @ingroup ObjHelpers
|
||||
* @param [in] The object type to be looked up.
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "bacnet/bacdcode.h"
|
||||
#include "bacnet/bacerror.h"
|
||||
#include "bacnet/create_object.h"
|
||||
#include "bacnet/proplist.h"
|
||||
|
||||
/**
|
||||
* @brief Encode one value for CreateObject List-of-Initial-Values
|
||||
@@ -31,6 +32,103 @@ int create_object_encode_initial_value(
|
||||
return bacapp_property_value_encode(apdu, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Encode one value for CreateObject List-of-Initial-Values
|
||||
*
|
||||
* BACnetPropertyValue ::= SEQUENCE {
|
||||
* property-identifier [0] BACnetPropertyIdentifier,
|
||||
* property-array-index [1] Unsigned OPTIONAL,
|
||||
* -- used only with array datatypes
|
||||
* -- if omitted with an array the entire array is referenced
|
||||
* property-value [2] ABSTRACT-SYNTAX.&Type,
|
||||
* -- any datatype appropriate for the specified property
|
||||
* priority [3] Unsigned (1..16) OPTIONAL
|
||||
* -- used only when property is commandable
|
||||
* }
|
||||
*
|
||||
* @param apdu Pointer to the buffer for encoded values
|
||||
* @param offset Offset into the buffer to start encoding
|
||||
* @param value Pointer to the property value used for encoding
|
||||
* @return Bytes encoded or zero on error.
|
||||
*/
|
||||
int create_object_encode_initial_value_data(
|
||||
uint8_t *apdu, int offset, BACNET_CREATE_OBJECT_PROPERTY_VALUE *value)
|
||||
{
|
||||
int len = 0, apdu_len = 0, i = 0;
|
||||
|
||||
if (apdu) {
|
||||
apdu += offset;
|
||||
}
|
||||
/* property-identifier [0] BACnetPropertyIdentifier */
|
||||
len = encode_context_enumerated(apdu, 0, value->propertyIdentifier);
|
||||
apdu_len += len;
|
||||
if (apdu) {
|
||||
apdu += len;
|
||||
}
|
||||
/* property-array-index [1] Unsigned OPTIONAL */
|
||||
if (value->propertyArrayIndex != BACNET_ARRAY_ALL) {
|
||||
len = encode_context_unsigned(apdu, 1, value->propertyArrayIndex);
|
||||
apdu_len += len;
|
||||
if (apdu) {
|
||||
apdu += len;
|
||||
}
|
||||
}
|
||||
/* property-value [2] ABSTRACT-SYNTAX.&Type */
|
||||
len = encode_opening_tag(apdu, 2);
|
||||
apdu_len += len;
|
||||
if (apdu) {
|
||||
apdu += len;
|
||||
}
|
||||
len = value->application_data_len;
|
||||
if (apdu) {
|
||||
/* data */
|
||||
for (i = 0; i < len; i++) {
|
||||
apdu[i] = value->application_data[i];
|
||||
}
|
||||
}
|
||||
apdu_len += len;
|
||||
if (apdu) {
|
||||
apdu += len;
|
||||
}
|
||||
len = encode_closing_tag(apdu, 2);
|
||||
apdu_len += len;
|
||||
if (apdu) {
|
||||
apdu += len;
|
||||
}
|
||||
/* priority [3] Unsigned (1..16) OPTIONAL */
|
||||
if (value->priority != BACNET_NO_PRIORITY) {
|
||||
len = encode_context_unsigned(apdu, 3, value->priority);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Encode one value for CreateObject List-of-Initial-Values
|
||||
* @param apdu Pointer to the buffer for encoding into
|
||||
* @param apdu_size number of bytes available in the buffer
|
||||
* @param data Pointer to the service data used for encoding values
|
||||
* @return number of bytes encoded, or zero if unable to encode or too large
|
||||
*/
|
||||
size_t create_object_initial_value_data_encode(
|
||||
uint8_t *apdu,
|
||||
size_t apdu_size,
|
||||
int offset,
|
||||
BACNET_CREATE_OBJECT_PROPERTY_VALUE *value)
|
||||
{
|
||||
size_t apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
apdu_len = create_object_encode_initial_value_data(NULL, offset, value);
|
||||
if (apdu_len > apdu_size) {
|
||||
apdu_len = 0;
|
||||
} else {
|
||||
apdu_len = create_object_encode_initial_value_data(apdu, offset, value);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decode one BACnetPropertyValue value
|
||||
*
|
||||
@@ -749,3 +847,149 @@ bool create_object_process(
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Encode the CreateObject service request with writable properties
|
||||
* encoded in the List of Initial Values.
|
||||
* @param apdu [in] The APDU buffer.
|
||||
* @param apdu_size [in] The size of the APDU buffer.
|
||||
* @param data [in,out] The Create Object data containing the request details.
|
||||
* @param optional_properties - list of optional properties
|
||||
* @param proprietary_properties - list of proprietary properties
|
||||
* @param writable_properties - list of writable properties
|
||||
* @param read_property [in] Function pointer to the Read Property handler.
|
||||
* @return Number of bytes encoded
|
||||
*/
|
||||
int create_object_writable_properties_encode(
|
||||
uint8_t *apdu,
|
||||
size_t apdu_size,
|
||||
BACNET_CREATE_OBJECT_DATA *data,
|
||||
const int32_t *required_properties,
|
||||
const int32_t *optional_properties,
|
||||
const int32_t *proprietary_properties,
|
||||
const int32_t *writable_properties,
|
||||
read_property_function read_property)
|
||||
{
|
||||
int apdu_len = 0, len = 0;
|
||||
size_t j = 0, priority = 0, a = 0;
|
||||
BACNET_UNSIGNED_INTEGER array_count = 0;
|
||||
uint32_t writable_property_count = 0;
|
||||
uint8_t property_apdu[MAX_APDU] = { 0 };
|
||||
BACNET_READ_PROPERTY_DATA rpdata = { 0 };
|
||||
BACNET_CREATE_OBJECT_PROPERTY_VALUE property_value = { 0 };
|
||||
|
||||
if (!data) {
|
||||
return 0;
|
||||
}
|
||||
writable_property_count = property_list_count(writable_properties);
|
||||
if (writable_property_count == 0) {
|
||||
/* no writable properties
|
||||
create the object absent the List of Initial Values */
|
||||
apdu_len = create_object_service_request_encode(apdu, apdu_size, data);
|
||||
} else {
|
||||
data->application_data_len = 0;
|
||||
for (j = 0; j < writable_property_count; j++) {
|
||||
/* read each writable property value from our device */
|
||||
rpdata.application_data = property_apdu;
|
||||
rpdata.application_data_len = sizeof(property_apdu);
|
||||
rpdata.object_type = data->object_type;
|
||||
rpdata.object_instance = data->object_instance;
|
||||
rpdata.object_property = writable_properties[j];
|
||||
if (property_list_bacnet_array_member(
|
||||
rpdata.object_type, rpdata.object_property)) {
|
||||
/* array properties - get the size */
|
||||
rpdata.array_index = 0;
|
||||
len = 0;
|
||||
if (read_property) {
|
||||
len = read_property(&rpdata);
|
||||
}
|
||||
if (len <= 0) {
|
||||
continue;
|
||||
}
|
||||
/* convert to integer */
|
||||
len = bacnet_unsigned_application_decode(
|
||||
&property_apdu[0], (uint32_t)len, &array_count);
|
||||
if (len <= 0) {
|
||||
continue;
|
||||
}
|
||||
for (a = 1; a <= array_count; a++) {
|
||||
rpdata.array_index = a;
|
||||
len = 0;
|
||||
if (read_property) {
|
||||
len = read_property(&rpdata);
|
||||
}
|
||||
if (len <= 0) {
|
||||
continue;
|
||||
}
|
||||
property_value.propertyIdentifier = writable_properties[j];
|
||||
property_value.propertyArrayIndex = a;
|
||||
property_value.priority = BACNET_NO_PRIORITY;
|
||||
property_value.application_data_len = len;
|
||||
property_value.application_data = property_apdu;
|
||||
len = create_object_initial_value_data_encode(
|
||||
data->application_data, sizeof(data->application_data),
|
||||
data->application_data_len, &property_value);
|
||||
if (len > 0) {
|
||||
data->application_data_len += len;
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
property_list_commandable_member(
|
||||
rpdata.object_type, rpdata.object_property) &&
|
||||
property_lists_member(
|
||||
required_properties, optional_properties,
|
||||
proprietary_properties, PROP_PRIORITY_ARRAY)) {
|
||||
/* convert the priority-array index to
|
||||
present-value and priority */
|
||||
for (priority = 1; priority <= BACNET_MAX_PRIORITY;
|
||||
priority++) {
|
||||
rpdata.object_property = PROP_PRIORITY_ARRAY;
|
||||
rpdata.array_index = (uint32_t)priority;
|
||||
len = 0;
|
||||
if (read_property) {
|
||||
len = read_property(&rpdata);
|
||||
}
|
||||
if (len <= 0) {
|
||||
continue;
|
||||
}
|
||||
property_value.propertyIdentifier = PROP_PRESENT_VALUE;
|
||||
property_value.propertyArrayIndex = BACNET_ARRAY_ALL;
|
||||
property_value.priority = priority;
|
||||
property_value.application_data_len = len;
|
||||
property_value.application_data = property_apdu;
|
||||
len = create_object_initial_value_data_encode(
|
||||
data->application_data, sizeof(data->application_data),
|
||||
data->application_data_len, &property_value);
|
||||
if (len > 0) {
|
||||
data->application_data_len += len;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* non array, non-priority properties */
|
||||
rpdata.array_index = BACNET_ARRAY_ALL;
|
||||
len = 0;
|
||||
if (read_property) {
|
||||
len = read_property(&rpdata);
|
||||
}
|
||||
if (len <= 0) {
|
||||
continue;
|
||||
}
|
||||
property_value.propertyIdentifier = rpdata.object_property;
|
||||
property_value.propertyArrayIndex = BACNET_ARRAY_ALL;
|
||||
property_value.priority = BACNET_NO_PRIORITY;
|
||||
property_value.application_data_len = len;
|
||||
property_value.application_data = property_apdu;
|
||||
len = create_object_initial_value_data_encode(
|
||||
data->application_data, sizeof(data->application_data),
|
||||
data->application_data_len, &property_value);
|
||||
if (len > 0) {
|
||||
data->application_data_len += len;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* writable properties - create object with List of Initial Values */
|
||||
apdu_len = create_object_service_request_encode(apdu, apdu_size, data);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "bacnet/bacdcode.h"
|
||||
#include "bacnet/bacapp.h"
|
||||
#include "bacnet/delete_object.h"
|
||||
#include "bacnet/rp.h"
|
||||
#include "bacnet/wp.h"
|
||||
|
||||
#ifndef BACNET_CREATE_OBJECT_LIST_VALUES_ENABLED
|
||||
@@ -87,6 +88,20 @@ extern "C" {
|
||||
BACNET_STACK_EXPORT
|
||||
int create_object_encode_initial_value(
|
||||
uint8_t *apdu, int offset, const BACNET_PROPERTY_VALUE *value);
|
||||
BACNET_STACK_EXPORT
|
||||
int create_object_encode_initial_value_data(
|
||||
uint8_t *apdu, int offset, BACNET_CREATE_OBJECT_PROPERTY_VALUE *value);
|
||||
BACNET_STACK_EXPORT
|
||||
size_t create_object_initial_value_data_encode(
|
||||
uint8_t *apdu,
|
||||
size_t apdu_size,
|
||||
int offset,
|
||||
BACNET_CREATE_OBJECT_PROPERTY_VALUE *value);
|
||||
BACNET_STACK_EXPORT
|
||||
int create_object_decode_initial_value(
|
||||
const uint8_t *apdu,
|
||||
uint32_t apdu_size,
|
||||
BACNET_CREATE_OBJECT_PROPERTY_VALUE *value);
|
||||
|
||||
BACNET_STACK_EXPORT
|
||||
size_t create_object_service_request_encode(
|
||||
@@ -132,6 +147,16 @@ bool create_object_process(
|
||||
create_object_function create_object,
|
||||
delete_object_function delete_object,
|
||||
write_property_function write_property);
|
||||
BACNET_STACK_EXPORT
|
||||
int create_object_writable_properties_encode(
|
||||
uint8_t *apdu,
|
||||
size_t apdu_size,
|
||||
BACNET_CREATE_OBJECT_DATA *data,
|
||||
const int32_t *required_properties,
|
||||
const int32_t *optional_properties,
|
||||
const int32_t *proprietary_properties,
|
||||
const int32_t *writable_properties,
|
||||
read_property_function read_property);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -95,6 +95,9 @@ bool property_lists_member(
|
||||
*
|
||||
* @param rpdata - ReadProperty data, including requested data and
|
||||
* data for the reply, or error response.
|
||||
* @param pListRequired - list of required properties
|
||||
* @param pListOptional - list of optional properties
|
||||
* @param pListProprietary - list of proprietary properties
|
||||
*
|
||||
* @return number of APDU bytes in the response, or
|
||||
* BACNET_STATUS_ERROR on error.
|
||||
|
||||
Reference in New Issue
Block a user