/** * @file * @brief BACnet Stack sample Smart Sensor (B-SS) main file * @author Steve Karg * @date October 2024 * @copyright SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include /* BACnet Stack defines - first */ #include "bacnet/bacdef.h" /* BACnet Stack API */ #include "bacnet/bactext.h" #include "bacnet/version.h" /* some demo stuff needed */ #include "bacnet/basic/sys/filename.h" #include "bacnet/basic/sys/debug.h" #include "bacnet/basic/sys/mstimer.h" #include "bacnet/basic/server/bacnet_basic.h" #include "bacnet/basic/server/bacnet_port.h" #include "bacnet/basic/object/device.h" #include "bacnet/basic/object/ai.h" #include "bacnet/basic/object/ao.h" #include "bacnet/datalink/datalink.h" #include "bacnet/datalink/dlenv.h" static const char *Device_Name = "BACnet Smart Sensor (B-SS)"; #define SENSOR_ID 1 /* table of default point names and units */ struct BACnetObjectTable { const uint8_t type; const uint8_t instance; const uint16_t units; const float value; const char *name; const char *description; }; static struct BACnetObjectTable Object_Table[] = { { OBJECT_ANALOG_INPUT, SENSOR_ID, UNITS_DEGREES_CELSIUS, 25.0f, "Indoor Air Temperature", "indoor air temperature" }, { OBJECT_ANALOG_OUTPUT, 1, UNITS_DEGREES_ANGULAR, 0.0f, "VAV Actuator", "variable air valve actuator" }, { OBJECT_ANALOG_OUTPUT, 2, UNITS_DEGREES_CELSIUS, 25.0f, "Temperature Setpoint", "indoor air temperature setpoint" }, }; /* timer for Sensor Update Interval */ static struct mstimer Sensor_Update_Timer; /** * @brief BACnet Project Initialization Handler * @param context [in] The context to pass to the callback function * @note This is called from the BACnet task */ static void BACnet_Object_Table_Init(void *context) { unsigned i; (void)context; /* initialize child objects for this basic sample */ for (i = 0; i < ARRAY_SIZE(Object_Table); i++) { switch (Object_Table[i].type) { case OBJECT_ANALOG_INPUT: Analog_Input_Create(Object_Table[i].instance); Analog_Input_Name_Set( Object_Table[i].instance, Object_Table[i].name); Analog_Input_Present_Value_Set( Object_Table[i].instance, Object_Table[i].value); Analog_Input_Units_Set( Object_Table[i].instance, Object_Table[i].units); Analog_Input_Description_Set( Object_Table[i].instance, Object_Table[i].description); break; case OBJECT_ANALOG_OUTPUT: Analog_Output_Create(Object_Table[i].instance); Analog_Output_Name_Set( Object_Table[i].instance, Object_Table[i].name); Analog_Output_Relinquish_Default_Set( Object_Table[i].instance, Object_Table[i].value); Analog_Output_Units_Set( Object_Table[i].instance, Object_Table[i].units); Analog_Output_Description_Set( Object_Table[i].instance, Object_Table[i].description); break; default: break; } } /* start the seconds cyclic timer */ mstimer_set(&Sensor_Update_Timer, 1000); srand(0); } /** * @brief BACnet Project Task Handler * @param context [in] The context to pass to the callback function * @note This is called from the BACnet task */ static void BACnet_Object_Task(void *context) { float temperature = 0.0f, change = 0.0f; (void)context; if (mstimer_expired(&Sensor_Update_Timer)) { mstimer_reset(&Sensor_Update_Timer); /* simulate a sensor reading, and update the BACnet object values */ if (Analog_Input_Out_Of_Service(SENSOR_ID)) { return; } temperature = Analog_Input_Present_Value(SENSOR_ID); change = -1.0f + 2.0f * ((float)rand()) / RAND_MAX; temperature += change; Analog_Input_Present_Value_Set(SENSOR_ID, temperature); } } /** * @brief Store the BACnet data after a WriteProperty for object property * @param object_type - BACnet object type * @param object_instance - BACnet object instance * @param object_property - BACnet object property * @param array_index - BACnet array index * @param application_data - pointer to the data * @param application_data_len - length of the data */ static void BACnet_Basic_Store( BACNET_OBJECT_TYPE object_type, uint32_t object_instance, BACNET_PROPERTY_ID object_property, BACNET_ARRAY_INDEX array_index, uint8_t *application_data, int application_data_len) { (void)array_index; (void)application_data; (void)application_data_len; debug_printf_stdout( "BACnet Store: %s[%lu]-%s\n", bactext_object_type_name(object_type), (unsigned long)object_instance, bactext_property_name(object_property)); } /** Main function of server demo. * * @see Device_Set_Object_Instance_Number, dlenv_init, Send_I_Am, * datalink_receive, npdu_handler, * dcc_timer_seconds, datalink_maintenance_timer, * handler_cov_task, tsm_timer_milliseconds * * @param argc [in] Arg count. * @param argv [in] Takes one argument: the Device Instance #. * @return 0 on success. */ int main(int argc, char *argv[]) { if (argc > 1) { /* allow the device ID to be set */ Device_Set_Object_Instance_Number(strtol(argv[1], NULL, 0)); } if (argc > 2) { /* allow the device name to be set */ Device_Name = argv[2]; } Device_Object_Name_ANSI_Init(Device_Name); debug_printf_stdout("BACnet Device: %s\n", Device_Name); debug_printf_stdout("BACnet Stack Version %s\n", BACNET_VERSION_TEXT); debug_printf_stdout("BACnet Stack Max APDU: %d\n", MAX_APDU); bacnet_basic_init_callback_set(BACnet_Object_Table_Init, NULL); bacnet_basic_task_callback_set(BACnet_Object_Task, NULL); bacnet_basic_store_callback_set(BACnet_Basic_Store); bacnet_basic_init(); if (bacnet_port_init()) { /* OS based apps use DLENV for environment variables */ dlenv_init(); atexit(datalink_cleanup); } debug_printf_stdout("Server: initialized\n"); for (;;) { bacnet_basic_task(); bacnet_port_task(); } return 0; }