diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f4c1023a..6d87f196 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -59,12 +59,12 @@ repos: # - id: prettier - repo: https://github.com/pre-commit/pygrep-hooks - rev: 3a6eb0fadf60b3cccfd80bad9dbb6fae7e47b316 # v1.10.0 + rev: v1.10.0 # v1.10.0 hooks: - id: text-unicode-replacement-char - repo: https://github.com/editorconfig-checker/editorconfig-checker.python - rev: 1c48f639855b49be07ace8b824757152b6747baa #2.6.2 + rev: 3.2.0 #2.6.2 hooks: - id: editorconfig-checker alias: ec diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b824974..3253e76c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,11 @@ option( "enable property lists" ON) +option( + BACNET_BUILD_SERVER_MINI_APP + "compile the server-mini app" + ON) + option( BACNET_BUILD_SERVER_BASIC_APP "compile the server-basic app" @@ -919,6 +924,11 @@ if(BACNET_STACK_BUILD_APPS) target_link_libraries(piface PRIVATE ${PROJECT_NAME}) endif(BACNET_BUILD_PIFACE_APP) + if(BACNET_BUILD_SERVER_MINI_APP) + add_executable(bacmini apps/server-mini/main.c) + target_link_libraries(bacmini PRIVATE ${PROJECT_NAME}) + endif(BACNET_BUILD_SERVER_MINI_APP) + if(BACNET_BUILD_SERVER_BASIC_APP) add_executable(bacbasic apps/server-basic/main.c @@ -933,7 +943,7 @@ if(BACNET_STACK_BUILD_APPS) # Unreachable code because we have endless loop. $<$:/wd4702> ) - endif(BACNET_BUILD_BACMINI_APP) + endif(BACNET_BUILD_SERVER_BASIC_APP) if(BACNET_BUILD_BACPOLL_APP) add_executable(bacpoll diff --git a/Makefile b/Makefile index 94134c0d..995027af 100644 --- a/Makefile +++ b/Makefile @@ -214,6 +214,10 @@ server-client: server-discover: $(MAKE) LEGACY=true -s -C apps $@ +.PHONY: server-mini +server-mini: + $(MAKE) LEGACY=true NOTIFY=false -s -C apps $@ + .PHONY: sc-hub sc-hub: $(MAKE) BACDL=bsc -s -C apps $@ diff --git a/apps/Makefile b/apps/Makefile index c29de1f8..f8bb1d88 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -222,7 +222,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 \ - delete-object server-discover apdu writegroup server-basic + delete-object server-discover apdu writegroup server-basic server-mini ifeq (${BACDL_DEFINE},-DBACDL_BIP=1) SUBDIRS += whoisrouter iamrouter initrouter whatisnetnum netnumis @@ -423,6 +423,10 @@ server-client: $(BACNET_LIB_TARGET) server-discover: $(BACNET_LIB_TARGET) $(MAKE) -B -C $@ +.PHONY: server-mini +server-mini: $(BACNET_LIB_TARGET) + $(MAKE) -B -C $@ + .PHONY: timesync timesync: $(BACNET_LIB_TARGET) $(MAKE) -B -C $@ diff --git a/apps/server-mini/Makefile b/apps/server-mini/Makefile new file mode 100644 index 00000000..01b7692a --- /dev/null +++ b/apps/server-mini/Makefile @@ -0,0 +1,81 @@ +# Makefile to build the bacmini application using GCC compiler + +# Executable file name +TARGET = bacmini +# No additional BACnet dependencies for now +SRC = main.c + +# BACnet objects that are used with this app +BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object +SRC = main.c \ + $(BACNET_OBJECT_DIR)/device.c \ + $(BACNET_OBJECT_DIR)/ai.c \ + $(BACNET_OBJECT_DIR)/ao.c \ + $(BACNET_OBJECT_DIR)/av.c \ + $(BACNET_OBJECT_DIR)/bi.c \ + $(BACNET_OBJECT_DIR)/bitstring_value.c \ + $(BACNET_OBJECT_DIR)/bo.c \ + $(BACNET_OBJECT_DIR)/blo.c \ + $(BACNET_OBJECT_DIR)/bv.c \ + $(BACNET_OBJECT_DIR)/calendar.c \ + $(BACNET_OBJECT_DIR)/channel.c \ + $(BACNET_OBJECT_DIR)/color_object.c \ + $(BACNET_OBJECT_DIR)/color_temperature.c \ + $(BACNET_OBJECT_DIR)/command.c \ + $(BACNET_OBJECT_DIR)/csv.c \ + $(BACNET_OBJECT_DIR)/iv.c \ + $(BACNET_OBJECT_DIR)/lc.c \ + $(BACNET_OBJECT_DIR)/lo.c \ + $(BACNET_OBJECT_DIR)/lsp.c \ + $(BACNET_OBJECT_DIR)/lsz.c \ + $(BACNET_OBJECT_DIR)/ms-input.c \ + $(BACNET_OBJECT_DIR)/mso.c \ + $(BACNET_OBJECT_DIR)/msv.c \ + $(BACNET_OBJECT_DIR)/osv.c \ + $(BACNET_OBJECT_DIR)/piv.c \ + $(BACNET_OBJECT_DIR)/nc.c \ + $(BACNET_OBJECT_DIR)/netport.c \ + $(BACNET_OBJECT_DIR)/program.c \ + $(BACNET_OBJECT_DIR)/time_value.c \ + $(BACNET_OBJECT_DIR)/trendlog.c \ + $(BACNET_OBJECT_DIR)/schedule.c \ + $(BACNET_OBJECT_DIR)/structured_view.c \ + $(BACNET_OBJECT_DIR)/access_credential.c \ + $(BACNET_OBJECT_DIR)/access_door.c \ + $(BACNET_OBJECT_DIR)/access_point.c \ + $(BACNET_OBJECT_DIR)/access_rights.c \ + $(BACNET_OBJECT_DIR)/access_user.c \ + $(BACNET_OBJECT_DIR)/access_zone.c \ + $(BACNET_OBJECT_DIR)/credential_data_input.c \ + $(BACNET_OBJECT_DIR)/acc.c \ + $(BACNET_OBJECT_DIR)/bacfile.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 diff --git a/apps/server-mini/main.c b/apps/server-mini/main.c new file mode 100644 index 00000000..31406b95 --- /dev/null +++ b/apps/server-mini/main.c @@ -0,0 +1,334 @@ +/** + * @file + * @brief Mini BACnet server example for prototyping + * + * This example provides a minimal BACnet server for prototyping + * with the following default BACnet objects: + * - Two Read-Only Points: (AV-0), (BV-0) + * - Two Commandable (Writable) Points: (AO-0), (BO-0) + * + * If no arguments are provided, it defaults to: + * - Device ID: 260001 + * - Device Name: "MiniServer" + * + * Usage on Linux + * $ ./bacmini 54321 MiniDevice + * + * Where: + * - 54321 is the BACnet Device Instance ID + * - "MiniDevice" is the BACnet Device Name + * + * @date 2025 + */ + +#include +#include +#include +#include +#include +#include + +/* BACnet Stack includes */ +#include "bacnet/apdu.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacdef.h" +#include "bacnet/bactext.h" +#include "bacnet/basic/binding/address.h" +#include "bacnet/basic/object/ao.h" +#include "bacnet/basic/object/av.h" +#include "bacnet/basic/object/bo.h" +#include "bacnet/basic/object/bv.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/services.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/datalink/dlenv.h" +#include "bacnet/dcc.h" +#include "bacnet/getevent.h" +#include "bacnet/iam.h" +#include "bacnet/npdu.h" +#include "bacnet/version.h" + +#include "bacnet/basic/service/h_apdu.h" +#include "bacnet/basic/service/h_rp.h" +#include "bacnet/basic/service/h_whois.h" +#include "bacnet/basic/service/h_wp.h" +#include "bacnet/basic/service/s_iam.h" +#include "bacnet/basic/sys/platform.h" + +/* Buffers */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/* Update interval in seconds */ +/* Switches read only point values */ +#define INTERVAL 5 + +typedef struct { + char *binary_state; + float analog_value; +} TestValue; + +static TestValue test_values[] = { + { "active", 1.0 }, + { "inactive", 2.0 }, + { "active", 3.0 }, + { "inactive", 4.0 }, +}; + +/* BACnet Object Instances */ +static uint32_t av_instance; +static uint32_t bv_instance; +static uint32_t ao_instance; +static uint32_t bo_instance; + +/* Custom Object Table */ +static object_functions_t My_Object_Table[] = { + /* device object required for all devices */ + { OBJECT_DEVICE, + NULL, + Device_Count, + Device_Index_To_Instance, + Device_Valid_Object_Instance_Number, + Device_Object_Name, + Device_Read_Property_Local, + Device_Write_Property_Local, + Device_Property_Lists, + DeviceGetRRInfo, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL }, + + /* Analog Value (Read-Only) */ + { OBJECT_ANALOG_VALUE, + Analog_Value_Init, + Analog_Value_Count, + Analog_Value_Index_To_Instance, + Analog_Value_Valid_Instance, + Analog_Value_Object_Name, + Analog_Value_Read_Property, + NULL, + Analog_Value_Property_Lists, + NULL, + NULL, + Analog_Value_Encode_Value_List, + NULL, + NULL, + NULL, + NULL, + NULL, + Analog_Value_Create, + Analog_Value_Delete, + NULL }, + + /* Analog Output (Commandable) */ + { OBJECT_ANALOG_OUTPUT, + Analog_Output_Init, + Analog_Output_Count, + Analog_Output_Index_To_Instance, + Analog_Output_Valid_Instance, + Analog_Output_Object_Name, + Analog_Output_Read_Property, + Analog_Output_Write_Property, /* Allow writes */ + Analog_Output_Property_Lists, + NULL, + NULL, + Analog_Output_Encode_Value_List, + NULL, + NULL, + NULL, + NULL, + NULL, + Analog_Output_Create, + Analog_Output_Delete, + NULL }, + + /* Binary Output (Commandable) */ + { OBJECT_BINARY_OUTPUT, + Binary_Output_Init, + Binary_Output_Count, + Binary_Output_Index_To_Instance, + Binary_Output_Valid_Instance, + Binary_Output_Object_Name, + Binary_Output_Read_Property, + Binary_Output_Write_Property, /* Allow writes */ + Binary_Output_Property_Lists, + NULL, + NULL, + Binary_Output_Encode_Value_List, + NULL, + NULL, + NULL, + NULL, + NULL, + Binary_Output_Create, + Binary_Output_Delete, + NULL }, + + /* Binary Value (Read-Only) */ + { OBJECT_BINARY_VALUE, + Binary_Value_Init, + Binary_Value_Count, + Binary_Value_Index_To_Instance, + Binary_Value_Valid_Instance, + Binary_Value_Object_Name, + Binary_Value_Read_Property, + NULL, + Binary_Value_Property_Lists, + NULL, + NULL, + Binary_Value_Encode_Value_List, + Binary_Value_Change_Of_Value, + Binary_Value_Change_Of_Value_Clear, + NULL, + NULL, + NULL, + Binary_Value_Create, + Binary_Value_Delete, + NULL }, + + { MAX_BACNET_OBJECT_TYPE, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL } +}; + +/** + * @brief Function to update AV-0 and BV-0 values. + */ +static void process_task(void) +{ + static size_t test_index = 0; + + TestValue next_value = test_values[test_index]; + test_index = (test_index + 1) % ARRAY_SIZE(test_values); + + if (!Analog_Value_Out_Of_Service(av_instance)) { + Analog_Value_Present_Value_Set( + av_instance, next_value.analog_value, BACNET_NO_PRIORITY); + printf("AV-0 updated to: %.1f\n", next_value.analog_value); + } + + if (!Binary_Value_Out_Of_Service(bv_instance)) { + Binary_Value_Present_Value_Set( + bv_instance, + strcmp(next_value.binary_state, "active") == 0 ? 1 : 0); + printf("BV-0 updated to: %s\n", next_value.binary_state); + } +} + +/** + * @brief Initializes the BACnet objects (AV-0, AO-0, BO-0, BV-0). + */ +static void Init_Service_Handlers(void) +{ + Device_Init(My_Object_Table); + + av_instance = Analog_Value_Create(0); + ao_instance = Analog_Output_Create(0); + bo_instance = Binary_Output_Create(0); + bv_instance = Binary_Value_Create(0); + + /* Configure read-only Analog Value */ + Analog_Value_Name_Set(av_instance, "AV Read Only"); + Analog_Value_Units_Set(av_instance, UNITS_DEGREES_CELSIUS); + Analog_Value_Present_Value_Set(av_instance, 22.5, BACNET_MAX_PRIORITY); + + /* Configure writable Analog Output */ + Analog_Output_Name_Set(ao_instance, "AO Writeable"); + Analog_Output_Units_Set(ao_instance, UNITS_PERCENT); + Analog_Output_Present_Value_Set(ao_instance, 50.0, BACNET_MAX_PRIORITY); + + /* Configure writable Binary Output */ + Binary_Output_Name_Set(bo_instance, "BO Writeable"); + Binary_Output_Present_Value_Set(bo_instance, 0, BACNET_MAX_PRIORITY); + + /* Configure read-only Binary Value */ + Binary_Value_Name_Set(bv_instance, "BV Read Only"); + + printf("Created AV-0 (Read-Only), AO-0 (Commandable), BO-0 (Commandable), " + "and BV-0 (Read-Only)\n"); + + /* BACnet service handlers */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_WRITE_PROPERTY, handler_write_property); + apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service); +} + +/** + * @brief Main entry point for the BACnet server. + */ +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; + uint16_t pdu_len = 0; + unsigned timeout = 1000; + time_t last_update_time = 0; + time_t current_time; + + const char *device_name = "MiniServer"; /* Default device name */ + uint32_t device_instance = 123456; /* Default device instance ID */ + + printf("Starting BACnet Server...\n"); + + /* Handle command-line arguments */ + if (argc > 1) { + device_instance = strtoul(argv[1], NULL, 10); + } + Device_Set_Object_Instance_Number(device_instance); + printf("BACnet Device ID: %u\n", device_instance); + + /* Initialize BACnet stack */ + dlenv_init(); + Init_Service_Handlers(); + atexit(datalink_cleanup); + + if (argc > 2) { + device_name = argv[2]; + } + Device_Object_Name_ANSI_Init(device_name); + printf("BACnet Device Name: %s\n", device_name); + + /* Broadcast an I-Am message */ + Send_I_Am(&Rx_Buf[0]); + + /* Main loop */ + while (1) { + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + + current_time = time(NULL); + if (difftime(current_time, last_update_time) >= INTERVAL) { + process_task(); + last_update_time = current_time; + } + } + + return EXIT_SUCCESS; +}