diff --git a/bacnet-stack/Makefile b/bacnet-stack/Makefile index dc0d7ebc..32f7dcdb 100644 --- a/bacnet-stack/Makefile +++ b/bacnet-stack/Makefile @@ -1,6 +1,6 @@ all: library readprop writeprop readfile writefile reinit server dcc \ whohas whois ucov timesync epics readpropm mstpcap \ - whoisrouter iamrouter initrouter + whoisrouter iamrouter initrouter gateway @echo "utilities are in the bin directory" clean: lib/Makefile\ @@ -20,7 +20,8 @@ clean: lib/Makefile\ demo/whoisrouter/Makefile \ demo/iamrouter/Makefile \ demo/initrouter/Makefile \ - demo/mstpcap/Makefile + demo/mstpcap/Makefile \ + demo/gateway/Makefile make -C lib clean make -C demo/readprop clean make -C demo/readpropm clean @@ -39,6 +40,7 @@ clean: lib/Makefile\ make -C demo/iamrouter clean make -C demo/initrouter clean make -C demo/mstpcap clean + make -C demo/gateway clean library: lib/Makefile make -C lib all @@ -106,6 +108,8 @@ at91sam7s: ports/at91sam7s/makefile bdk-atxx4-mstp: ports/bdk-atxx4-mstp/Makefile make -C ports/bdk-atxx4-mstp clean all +gateway: demo/gateway/Makefile + ( cd demo/gateway ; make ; cp bacgateway ../../bin ) diff --git a/bacnet-stack/demo/gateway/Makefile b/bacnet-stack/demo/gateway/Makefile new file mode 100644 index 00000000..5b96e21b --- /dev/null +++ b/bacnet-stack/demo/gateway/Makefile @@ -0,0 +1,87 @@ +#Makefile to build BACnet Gateway Demonstration Application for the Linux Port + +# tools - only if you need them. +# Most platforms have this already defined +# CC = gcc + +# Executable file name +TARGET = bacgateway + +# Configure the BACnet Datalink Layer +#BACDL_DEFINE = -DBACDL_ETHERNET +#BACDL_DEFINE = -DBACDL_ARCNET +#BACDL_DEFINE = -DBACDL_MSTP +BACDL_DEFINE = -DBACDL_BIP +BACNET_DEFINES = -DPRINT_ENABLED=1 -DBACAPP_ALL -DBACFILE +DEFINES = $(BACNET_DEFINES) $(BACDL_DEFINE) + +# Directories +BACNET_PORT = linux +BACNET_PORT_DIR = ../../ports/${BACNET_PORT} +BACNET_INCLUDE = ../../include + +# BACnet Library +BACNET_LIB_DIR = ../../lib +BACNET_LIB_NAME = bacnet +BACNET_LIB_TARGET = $(BACNET_LIB_DIR)/lib$(BACNET_LIB_NAME).a +# Compiler Setup +INCLUDES = -I$(BACNET_INCLUDE) -I$(BACNET_PORT_DIR) +ifeq (${BACNET_PORT},linux) +PFLAGS = -pthread +TARGET_BIN = ${TARGET} +LIBRARIES=-lc,-lgcc,-lrt,-lm,-L$(BACNET_LIB_DIR),-l$(BACNET_LIB_NAME) +endif +ifeq (${BACNET_PORT},win32) +TARGET_BIN = ${TARGET}.exe +LIBRARY1=-L$(BACNET_LIB_DIR),-l$(BACNET_LIB_NAME) +LIBRARY2=-lws2_32,-lgcc,-lm,-liphlpapi,-lwinmm +LIBRARIES=$(LIBRARY1),$(LIBRARY2) +endif + +#build for release (default) or debug +ifeq (${BUILD},debug) +# Use -g to put info for gdb in the executable +DEBUGGING = -g -DDEBUG_ENABLED=1 +OPTIMIZATION = -O0 +LINK_OPTIMIZATION = +ifeq (${BACDL_DEFINE},-DBACDL_BIP=1) +DEFINES += -DBIP_DEBUG +endif +else +DEBUGGING = +# Use -f optimizations and then link option --gc-sections to reduce executable size. +# Of course, not when you want debug output for gdb! +OPTIMIZATION = -Os -fdata-sections -ffunction-sections +LINK_OPTIMIZATION = -Wl,--gc-sections +endif + +# put all the flags together +CFLAGS = -Wall $(DEBUGGING) $(OPTIMIZATION) $(INCLUDES) $(DEFINES) +LFLAGS = -Wl,-Map=$(TARGET).map,$(LIBRARIES) $(LINK_OPTIMIZATION) + +SRCS = main.c device.c + +OBJS = ${SRCS:.c=.o} + +all: ${BACNET_LIB_TARGET} Makefile ${TARGET_BIN} + size ${TARGET_BIN} + +${TARGET_BIN}: ${OBJS} Makefile ${BACNET_LIB_TARGET} + ${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@ + +lib: ${BACNET_LIB_TARGET} + +${BACNET_LIB_TARGET}: + ( cd ${BACNET_LIB_DIR} ; make clean ; make ) + +.c.o: + ${CC} -c ${CFLAGS} $*.c -o $@ + +depend: + rm -f .depend + ${CC} -MM ${CFLAGS} *.c >> .depend + +clean: + rm -f core ${TARGET_BIN} ${OBJS} ${BACNET_LIB_TARGET} $(TARGET).map + +include: .depend diff --git a/bacnet-stack/demo/gateway/device.c b/bacnet-stack/demo/gateway/device.c new file mode 100644 index 00000000..476156e9 --- /dev/null +++ b/bacnet-stack/demo/gateway/device.c @@ -0,0 +1,1683 @@ +/************************************************************************** +* +* Copyright (C) 2005,2006,2009 Steve Karg +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*********************************************************************/ + +/** @file device.c Base "class" for handling all BACnet objects belonging + * to a BACnet device, as well as Device-specific properties. */ + +#include +#include +#include /* for memmove */ +#include /* for timezone, localtime */ +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bacapp.h" +#include "config.h" /* the custom stuff */ +#include "gateway.h" +#include "apdu.h" +#include "wp.h" /* write property handling */ +#include "rp.h" /* read property handling */ +#include "version.h" +#include "device.h" /* me */ +#include "handlers.h" +#include "datalink.h" +#include "address.h" +/* include the objects */ +#include "device.h" +#include "ai.h" +#include "ao.h" +#include "av.h" +#include "bi.h" +#include "bo.h" +#include "bv.h" +#include "lc.h" +#include "lsp.h" +#include "mso.h" +#include "ms-input.h" +#include "trendlog.h" +#if defined(BACFILE) +#include "bacfile.h" /* object list dependency */ +#endif +/* os specfic includes */ +#include "timer.h" + +#if defined(__BORLANDC__) +/* seems to not be defined in time.h as specified by The Open Group */ +/* difference from UTC and local standard time */ +long int timezone; +#endif + +/* forward prototypes */ +static int Device_Read_Property_Local( + BACNET_READ_PROPERTY_DATA * rpdata); +static bool Device_Write_Property_Local( + BACNET_WRITE_PROPERTY_DATA * wp_data); + +/** Defines the group of object helper functions for any supported Object. + * @ingroup ObjHelpers + * Each Object must provide some implementation of each of these helpers + * in order to properly support the handlers. Eg, the ReadProperty handler + * handler_read_property() relies on the instance of Object_Read_Property + * for each Object type. + * In both appearance and operation, this group of functions acts like + * they are member functions of a C++ Object base class. + */ +static struct object_functions { + BACNET_OBJECT_TYPE Object_Type; + object_init_function Object_Init; + object_count_function Object_Count; + object_index_to_instance_function Object_Index_To_Instance; + object_valid_instance_function Object_Valid_Instance; + object_name_function Object_Name; + read_property_function Object_Read_Property; + write_property_function Object_Write_Property; + rpm_property_lists_function Object_RPM_List; + rr_info_function Object_RR_Info; + object_iterate_function Object_Iterator; +} Object_Table[] = { + { + OBJECT_DEVICE, NULL, Device_Count, Device_Index_To_Instance, + Device_Valid_Object_Instance_Number, Device_Name, + Device_Read_Property_Local, Device_Write_Property_Local, + Device_Property_Lists, DeviceGetRRInfo, NULL}, { + OBJECT_ANALOG_INPUT, Analog_Input_Init, Analog_Input_Count, + Analog_Input_Index_To_Instance, Analog_Input_Valid_Instance, + Analog_Input_Name, Analog_Input_Read_Property, NULL, + Analog_Input_Property_Lists, NULL, NULL}, { + OBJECT_ANALOG_OUTPUT, Analog_Output_Init, Analog_Output_Count, + Analog_Output_Index_To_Instance, Analog_Output_Valid_Instance, + Analog_Output_Name, Analog_Output_Read_Property, + Analog_Output_Write_Property, Analog_Output_Property_Lists, + NULL, NULL}, { + OBJECT_ANALOG_VALUE, Analog_Value_Init, Analog_Value_Count, + Analog_Value_Index_To_Instance, Analog_Value_Valid_Instance, + Analog_Value_Name, Analog_Value_Read_Property, + Analog_Value_Write_Property, Analog_Value_Property_Lists, NULL, + NULL}, { + OBJECT_BINARY_INPUT, Binary_Input_Init, Binary_Input_Count, + Binary_Input_Index_To_Instance, Binary_Input_Valid_Instance, + Binary_Input_Name, Binary_Input_Read_Property, NULL, + Binary_Input_Property_Lists, NULL, NULL}, { + OBJECT_BINARY_OUTPUT, Binary_Output_Init, Binary_Output_Count, + Binary_Output_Index_To_Instance, Binary_Output_Valid_Instance, + Binary_Output_Name, Binary_Output_Read_Property, + Binary_Output_Write_Property, Binary_Output_Property_Lists, + NULL, NULL}, { + OBJECT_BINARY_VALUE, Binary_Value_Init, Binary_Value_Count, + Binary_Value_Index_To_Instance, Binary_Value_Valid_Instance, + Binary_Value_Name, Binary_Value_Read_Property, + Binary_Value_Write_Property, Binary_Value_Property_Lists, NULL, + NULL}, { + OBJECT_LIFE_SAFETY_POINT, Life_Safety_Point_Init, + Life_Safety_Point_Count, Life_Safety_Point_Index_To_Instance, + Life_Safety_Point_Valid_Instance, Life_Safety_Point_Name, + Life_Safety_Point_Read_Property, + Life_Safety_Point_Write_Property, + Life_Safety_Point_Property_Lists, NULL, NULL}, { + OBJECT_LOAD_CONTROL, Load_Control_Init, Load_Control_Count, + Load_Control_Index_To_Instance, Load_Control_Valid_Instance, + Load_Control_Name, Load_Control_Read_Property, + Load_Control_Write_Property, Load_Control_Property_Lists, NULL, + NULL}, { + OBJECT_MULTI_STATE_OUTPUT, Multistate_Output_Init, + Multistate_Output_Count, Multistate_Output_Index_To_Instance, + Multistate_Output_Valid_Instance, Multistate_Output_Name, + Multistate_Output_Read_Property, + Multistate_Output_Write_Property, + Multistate_Output_Property_Lists, NULL, NULL}, { + OBJECT_MULTI_STATE_INPUT, Multistate_Input_Init, + Multistate_Input_Count, Multistate_Input_Index_To_Instance, + Multistate_Input_Valid_Instance, Multistate_Input_Name, + Multistate_Input_Read_Property, + Multistate_Input_Write_Property, + Multistate_Input_Property_Lists, NULL, NULL}, { + OBJECT_TRENDLOG, Trend_Log_Init, Trend_Log_Count, + Trend_Log_Index_To_Instance, Trend_Log_Valid_Instance, + Trend_Log_Name, Trend_Log_Read_Property, + Trend_Log_Write_Property, Trend_Log_Property_Lists, + TrendLogGetRRInfo, NULL}, +#if defined(BACFILE) + { + OBJECT_FILE, bacfile_init, bacfile_count, bacfile_index_to_instance, + bacfile_valid_instance, bacfile_name, bacfile_read_property, + bacfile_write_property, BACfile_Property_Lists, NULL}, +#endif + { + MAX_BACNET_OBJECT_TYPE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL} +}; + +/** Glue function to let the Device object, when called by a handler, + * lookup which Object type needs to be invoked. + * @ingroup ObjHelpers + * @param Object_Type [in] The type of BACnet Object the handler wants to access. + * @return Pointer to the group of object helper functions that implement this + * type of Object. + */ +static struct object_functions *Device_Objects_Find_Functions( + BACNET_OBJECT_TYPE Object_Type) +{ + struct object_functions *pObject = NULL; + + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + /* handle each object type */ + if (pObject->Object_Type == Object_Type) { + return (pObject); + } + + pObject++; + } + + return (NULL); +} + +/** Try to find a rr_info_function helper function for the requested object type. + * @ingroup ObjIntf + * + * @param object_type [in] The type of BACnet Object the handler wants to access. + * @return Pointer to the object helper function that implements the + * ReadRangeInfo function, Object_RR_Info, for this type of Object on + * success, else a NULL pointer if the type of Object isn't supported + * or doesn't have a ReadRangeInfo function. + */ +rr_info_function Device_Objects_RR_Info( + BACNET_OBJECT_TYPE object_type) +{ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + return (pObject != NULL ? pObject->Object_RR_Info : NULL); +} + +static unsigned property_list_count( + const int *pList) +{ + unsigned property_count = 0; + + if (pList) { + while (*pList != -1) { + property_count++; + pList++; + } + } + + return property_count; +} + +/** For a given object type, returns the special property list. + * This function is used for ReadPropertyMultiple calls which want + * just Required, just Optional, or All properties. + * @ingroup ObjIntf + * + * @param object_type [in] The desired BACNET_OBJECT_TYPE whose properties + * are to be listed. + * @param pPropertyList [out] Reference to the structure which will, on return, + * list, separately, the Required, Optional, and Proprietary object + * properties with their counts. + */ +void Device_Objects_Property_List( + BACNET_OBJECT_TYPE object_type, + struct special_property_list_t *pPropertyList) +{ + struct object_functions *pObject = NULL; + + pPropertyList->Required.pList = NULL; + pPropertyList->Optional.pList = NULL; + pPropertyList->Proprietary.pList = NULL; + + /* If we can find an entry for the required object type + * and there is an Object_List_RPM fn ptr then call it + * to populate the pointers to the individual list counters. + */ + + pObject = Device_Objects_Find_Functions(object_type); + if ((pObject != NULL) && (pObject->Object_RPM_List != NULL)) { + pObject->Object_RPM_List(&pPropertyList->Required.pList, + &pPropertyList->Optional.pList, &pPropertyList->Proprietary.pList); + } + + /* Fetch the counts if available otherwise zero them */ + pPropertyList->Required.count = + pPropertyList->Required.pList == + NULL ? 0 : property_list_count(pPropertyList->Required.pList); + + pPropertyList->Optional.count = + pPropertyList->Optional.pList == + NULL ? 0 : property_list_count(pPropertyList->Optional.pList); + + pPropertyList->Proprietary.count = + pPropertyList->Proprietary.pList == + NULL ? 0 : property_list_count(pPropertyList->Proprietary.pList); + + return; +} + +/** Commands a Device re-initialization, to a given state. + * The request's password must match for the operation to succeed. + * This implementation provides a framework, but doesn't + * actually *DO* anything. + * @note You could use a mix of states and passwords to multiple outcomes. + * @note You probably want to restart *after* the simple ack has been sent + * from the return handler, so just set a local flag here. + * @ingroup ObjIntf + * + * @param rd_data [in,out] The information from the RD request. + * On failure, the error class and code will be set. + * @return True if succeeds (password is correct), else False. + */ +bool Device_Reinitialize( + BACNET_REINITIALIZE_DEVICE_DATA * rd_data) +{ + bool status = false; + + if (characterstring_ansi_same(&rd_data->password, "Jesus")) { + switch (rd_data->state) { + case BACNET_REINIT_COLDSTART: + break; + case BACNET_REINIT_WARMSTART: + break; + case BACNET_REINIT_STARTBACKUP: + break; + case BACNET_REINIT_ENDBACKUP: + break; + case BACNET_REINIT_STARTRESTORE: + break; + case BACNET_REINIT_ENDRESTORE: + break; + case BACNET_REINIT_ABORTRESTORE: + break; + default: + break; + } + /* 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 */ + status = true; + } else { + rd_data->error_class = ERROR_CLASS_SECURITY; + rd_data->error_code = ERROR_CODE_PASSWORD_FAILURE; + } + + return status; +} + +/* These three arrays are used by the ReadPropertyMultiple handler */ +static const int Device_Properties_Required[] = { + PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, + PROP_OBJECT_TYPE, + PROP_SYSTEM_STATUS, + PROP_VENDOR_NAME, + PROP_VENDOR_IDENTIFIER, + PROP_MODEL_NAME, + PROP_FIRMWARE_REVISION, + PROP_APPLICATION_SOFTWARE_VERSION, + PROP_PROTOCOL_VERSION, + PROP_PROTOCOL_REVISION, + PROP_PROTOCOL_SERVICES_SUPPORTED, + PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, + PROP_OBJECT_LIST, + PROP_MAX_APDU_LENGTH_ACCEPTED, + PROP_SEGMENTATION_SUPPORTED, + PROP_APDU_TIMEOUT, + PROP_NUMBER_OF_APDU_RETRIES, +#if defined(BACDL_MSTP) + PROP_MAX_MASTER, + PROP_MAX_INFO_FRAMES, +#endif + PROP_DEVICE_ADDRESS_BINDING, + PROP_DATABASE_REVISION, + -1 +}; + +static const int Device_Properties_Optional[] = { + PROP_DESCRIPTION, + PROP_LOCAL_TIME, + PROP_UTC_OFFSET, + PROP_LOCAL_DATE, + PROP_DAYLIGHT_SAVINGS_STATUS, + PROP_LOCATION, + PROP_ACTIVE_COV_SUBSCRIPTIONS, + -1 +}; + +static const int Device_Properties_Proprietary[] = { + -1 +}; + +void Device_Property_Lists( + const int **pRequired, + const int **pOptional, + const int **pProprietary) +{ + if (pRequired) + *pRequired = Device_Properties_Required; + if (pOptional) + *pOptional = Device_Properties_Optional; + if (pProprietary) + *pProprietary = Device_Properties_Proprietary; + + return; +} + +/** For this demo, model the gateway as the main Device, with two remote + * devices that are reached via its routing capabilities. + */ +DEVICE_OBJECT_DATA Devices[NUM_DEVICES]; + +/* Note: You really only need to define variables here for + properties that are writable or that may change (and that aren't part + of the DEVICE_OBJECT_DATA structure). + The properties that are constant can be hard coded + into the read-property encoding. */ + +/* static uint32_t Object_Instance_Number = 260001; */ +/* static char My_Object_Name[MAX_DEV_NAME_LEN + 1] = "SimpleServer"; */ +static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL; +static char *Vendor_Name = BACNET_VENDOR_NAME; +static uint16_t Vendor_Identifier = BACNET_VENDOR_ID; +static char Model_Name[MAX_DEV_MOD_LEN + 1] = "GNU"; +static char Application_Software_Version[MAX_DEV_VER_LEN + 1] = "1.11.0"; +static char Location[MAX_DEV_LOC_LEN + 1] = "USA"; +/* static char Description[MAX_DEV_DESC_LEN + 1] = "server"; */ +/* static uint8_t Protocol_Version = 1; - constant, not settable */ +/* static uint8_t Protocol_Revision = 4; - constant, not settable */ +/* Protocol_Services_Supported - dynamically generated */ +/* Protocol_Object_Types_Supported - in RP encoding */ +/* Object_List - dynamically generated */ +/* static BACNET_SEGMENTATION Segmentation_Supported = SEGMENTATION_NONE; */ +/* static uint8_t Max_Segments_Accepted = 0; */ +/* VT_Classes_Supported */ +/* Active_VT_Sessions */ +static BACNET_TIME Local_Time; /* rely on OS, if there is one */ +static BACNET_DATE Local_Date; /* rely on OS, if there is one */ +/* NOTE: BACnet UTC Offset is inverse of common practice. + If your UTC offset is -5hours of GMT, + then BACnet UTC offset is +5hours. + BACnet UTC offset is expressed in minutes. */ +static int32_t UTC_Offset = 5 * 60; +static bool Daylight_Savings_Status = false; /* rely on OS */ +/* List_Of_Session_Keys */ +/* Time_Synchronization_Recipients */ +/* Max_Master - rely on MS/TP subsystem, if there is one */ +/* 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 */ +/* Auto_Slave_Discovery */ +/* Slave_Address_Binding */ +/* Profile_Name */ + + +unsigned Device_Count( + void) +{ + return 1; +} + +/** Return the Object Instance for the given Devices[] array index. + * @param index [in] Index into the Devices[] array. + * @return The Object_Instance_Number of the given Device. + */ +uint32_t Device_Index_To_Instance( unsigned index) +{ + if ( index < NUM_DEVICES ) + return Devices[index].bacObj.Object_Instance_Number; + else + return 0; +} + +/** Given an object_instance, find the corresponding DEVICE_OBJECT_DATA struct. + * @param object_instance [in] The Object_Instance_Number to be matched. + * @return Pointer to the structure for the indicated Device, or NULL if none found. + */ +DEVICE_OBJECT_DATA* Device_Instance_To_Data( uint32_t object_instance ) +{ + int i; + /* If MAX, just return first Device */ + if ( object_instance == BACNET_MAX_INSTANCE ) + return &Devices[0]; + for ( i = 0; i < NUM_DEVICES; i++ ) { + if ( Devices[i].bacObj.Object_Instance_Number == object_instance ) + return &Devices[i]; + } + /* Else, null return */ + return NULL; +} + +/** Initialize the BACnet Device Addresses for each Device object. + * The gateway has already gotten the normal address (eg, PC's IP for BIP) and + * the remote devices get + * - For BIP, the IP address reversed, and 4th byte equal to index. + * (Eg, 11.22.33.44 for the gateway becomes 44.33.22.01 for the first remote + * device.) This is sure to be unique! The port number stays the same. + * - For MS/TP, [Steve inserts a good idea here] + */ +void Initialize_Device_Addresses( ) +{ + int i; + DEVICE_OBJECT_DATA* pDev = &Devices[0]; + uint16_t myPort; + /* Setup info for the main gateway device first */ +#if defined(BACDL_BIP) + struct in_addr *netPtr; /* Lets us cast to this type */ + uint32_t myAddr = ntohl( bip_get_addr() ); + memcpy( pDev->bacDevAddr.mac, &myAddr, 4 ); + myPort = bip_get_port(); + memcpy( &pDev->bacDevAddr.mac[4], &myPort, 2 ); + pDev->bacDevAddr.mac_len = 6; +#elif defined(BACDL_MSTP) + /* Todo: */ + pDev->bacDevAddr.mac_len = 2; +#else +#error "No support for this Data Link Layer type " +#endif + + for (i = 1; i < NUM_DEVICES; i++ ) { + pDev = &Devices[i]; +#if defined(BACDL_BIP) + netPtr = (struct in_addr *) pDev->bacDevAddr.mac; + pDev->bacDevAddr.mac[0] = Devices[0].bacDevAddr.mac[3]; + pDev->bacDevAddr.mac[1] = Devices[0].bacDevAddr.mac[2]; + pDev->bacDevAddr.mac[2] = Devices[0].bacDevAddr.mac[1]; + pDev->bacDevAddr.mac[3] = i; + pDev->bacDevAddr.mac[4] = Devices[0].bacDevAddr.mac[4]; + pDev->bacDevAddr.mac[5] = Devices[0].bacDevAddr.mac[5]; + pDev->bacDevAddr.mac_len = 6; + printf( " - Routed device %d at %s \n", i, inet_ntoa( *netPtr ) ); +#elif defined(BACDL_MSTP) + /* Todo: set MS/TP net and port #s */ + pDev->bacDevAddr.mac_len = 2; +#endif + + } +} + +/* methods to manipulate the data */ + +/** Return the Object Instance number for our (gateway) Device Object. + * This is a key function, widely invoked by the handler code, since + * it provides "our" (ie, local) address. + * @ingroup ObjIntf + * @return The Instance number used in the BACNET_OBJECT_ID for the Device. + */ +uint32_t Device_Object_Instance_Number( + void) +{ + return Devices[0].bacObj.Object_Instance_Number; +} + +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 */ + Devices[0].bacObj.Object_Instance_Number = object_id; + Device_Inc_Database_Revision(); + } else + status = false; + + return status; +} + +bool Device_Change_Object_Instance_Number( + uint32_t old_object_instance, uint32_t new_object_instance ) +{ + bool status = false; /* return value */ + int i; + + if (new_object_instance <= BACNET_MAX_INSTANCE) { + for( i = NUM_DEVICES-1; i > 0; i-- ) { + if (Devices[i].bacObj.Object_Instance_Number == old_object_instance) + break; + /* Else, if we exit this for loop without a match, i will be 0, + * so the main gateway Device will be changed regardless + * of matching to the old_object_id + */ + } + /* Make the change and update the database revision */ + Devices[i].bacObj.Object_Instance_Number = new_object_instance; + Device_Inc_Database_Revision(); + status = true; + } + return status; +} + +bool Device_Valid_Object_Instance_Number( + uint32_t object_id) +{ + /* BACnet allows for a wildcard instance number */ + return ((Devices[0].bacObj.Object_Instance_Number == object_id) || + (object_id == BACNET_MAX_INSTANCE)); +} + +char *Device_Name( + uint32_t object_instance) +{ + DEVICE_OBJECT_DATA* pDev = Device_Instance_To_Data( object_instance ); + + if ( pDev != NULL ) + return pDev->bacObj.Object_Name; + + return NULL; +} + +const char *Device_Object_Name( + void) +{ + return Devices[0].bacObj.Object_Name; +} + +bool Device_Set_Object_Name( + uint32_t object_instance, + const char *name, + size_t length) +{ + bool status = false; /*return value */ + DEVICE_OBJECT_DATA* pDev = Device_Instance_To_Data( object_instance ); + + if ((pDev != NULL) && (length < MAX_DEV_NAME_LEN)) { + /* Make the change and update the database revision */ + strncpy( pDev->bacObj.Object_Name, name, MAX_DEV_NAME_LEN); + pDev->bacObj.Object_Name[MAX_DEV_NAME_LEN -1] = 0; + Device_Inc_Database_Revision(); + status = true; + } + + return status; +} + +BACNET_DEVICE_STATUS Device_System_Status( + void) +{ + return System_Status; +} + +int Device_Set_System_Status( + BACNET_DEVICE_STATUS status, + bool local) +{ + int result = 0; /*return value - 0 = ok, -1 = bad value, -2 = not allowed */ + + /* 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: + System_Status = status; + break; + + /* Don't support backup at present so don't allow setting */ + case STATUS_BACKUP_IN_PROGRESS: + result = -2; + break; + + 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: + System_Status = status; + break; + + /* Don't allow outsider set this - it 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: + result = -2; + break; + + default: + result = -1; + break; + } + } + + return (result); +} + +const char *Device_Vendor_Name( + void) +{ + return Vendor_Name; +} + +/** Returns the Vendor ID for this Device. + * See the assignments at http://www.bacnet.org/VendorID/BACnet%20Vendor%20IDs.htm + * @return The Vendor ID of this Device. + */ +uint16_t Device_Vendor_Identifier( + void) +{ + return Vendor_Identifier; +} + +void Device_Set_Vendor_Identifier( + uint16_t vendor_id) +{ + Vendor_Identifier = vendor_id; +} + +const char *Device_Model_Name( + void) +{ + return Model_Name; +} + +bool Device_Set_Model_Name( + const char *name, + size_t length) +{ + bool status = false; /*return value */ + + if (length < sizeof(Model_Name)) { + memmove(Model_Name, name, length); + Model_Name[length] = 0; + status = true; + } + + return status; +} + +const char *Device_Firmware_Revision( + void) +{ + return BACnet_Version; +} + +const char *Device_Application_Software_Version( + void) +{ + return Application_Software_Version; +} + +bool Device_Set_Application_Software_Version( + const char *name, + size_t length) +{ + bool status = false; /*return value */ + + if (length < sizeof(Application_Software_Version)) { + memmove(Application_Software_Version, name, length); + Application_Software_Version[length] = 0; + status = true; + } + + return status; +} + +const char *Device_Description( + void) +{ + return Devices[0].Description; +} + +bool Device_Set_Description( + uint32_t object_instance, + const char *name, + size_t length) + { + bool status = false; /*return value */ + DEVICE_OBJECT_DATA* pDev = Device_Instance_To_Data( object_instance ); + + if ((pDev != NULL) && (length < MAX_DEV_DESC_LEN)) { + /* Make the change and update the database revision */ + strncpy( pDev->Description, name, MAX_DEV_DESC_LEN); + pDev->Description[MAX_DEV_DESC_LEN -1] = 0; + status = true; + } + + return status; +} + +const char *Device_Location( + void) +{ + return Location; +} + +bool Device_Set_Location( + const char *name, + size_t length) +{ + bool status = false; /*return value */ + + if (length < sizeof(Location)) { + memmove(Location, name, length); + Location[length] = 0; + status = true; + } + + return status; +} + +uint8_t Device_Protocol_Version( + void) +{ + return BACNET_PROTOCOL_VERSION; +} + +uint8_t Device_Protocol_Revision( + void) +{ + return BACNET_PROTOCOL_REVISION; +} + +BACNET_SEGMENTATION Device_Segmentation_Supported( + void) +{ + return SEGMENTATION_NONE; +} + +uint32_t Device_Database_Revision( + void) +{ + return Database_Revision; +} + +void Device_Set_Database_Revision( + uint32_t revision) +{ + Database_Revision = revision; +} + +/* + * Shortcut for incrementing database revision as this is potentially + * the most common operation if changing object names and ids is + * implemented. + */ +void Device_Inc_Database_Revision( + void) +{ + Database_Revision++; +} + +/** Get the total count of objects supported by this Device Object. + * @note Since many network clients depend on the object list + * for discovery, it must be consistent! + * @return The count of objects, for all supported Object types. + */ +unsigned Device_Object_List_Count( + void) +{ + unsigned count = 0; /* number of objects */ + struct object_functions *pObject = NULL; + + /* initialize the default return values */ + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count) { + count += pObject->Object_Count(); + } + pObject++; + } + + return count; +} + +/** Lookup the Object at the given array index in the Device's Object List. + * Even though we don't keep a single linear array of objects in the Device, + * this method acts as though we do and works through a virtual, concatenated + * array of all of our object type arrays. + * + * @param array_index [in] The desired array index (1 to N) + * @param object_type [out] The object's type, if found. + * @param instance [out] The object's instance number, if found. + * @return True if found, else false. + */ +bool Device_Object_List_Identifier( + unsigned array_index, + int *object_type, + uint32_t * instance) +{ + bool status = false; + unsigned count = 0; + unsigned object_index = 0; + unsigned temp_index = 0; + struct object_functions *pObject = NULL; + + /* array index zero is length - so invalid */ + if (array_index == 0) { + return status; + } + object_index = array_index - 1; + /* initialize the default return values */ + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count) { + object_index -= count; + count = pObject->Object_Count(); + if (object_index < count) { + /* Use the iterator function if available otherwise + * look for the index to instance to get the ID */ + if (pObject->Object_Iterator) { + /* First find the first object */ + temp_index = pObject->Object_Iterator(~(unsigned) 0); + /* Then step through the objects to find the nth */ + while (object_index != 0) { + temp_index = pObject->Object_Iterator(temp_index); + object_index--; + } + /* set the object_index up before falling through to next bit */ + object_index = temp_index; + } + if (pObject->Object_Index_To_Instance) { + *object_type = pObject->Object_Type; + *instance = + pObject->Object_Index_To_Instance(object_index); + status = true; + break; + } + } + } + pObject++; + } + + return status; +} + +/** Determine if we have an object with the given object_name. + * If the object_type and object_instance pointers are not null, + * and the lookup succeeds, they will be given the resulting values. + * @param object_name [in] The desired Object Name to look for. + * @param object_type [out] The BACNET_OBJECT_TYPE of the matching Object. + * @param object_instance [out] The object instance number of the matching Object. + * @return True on success or else False if not found. + */ +bool Device_Valid_Object_Name( + const char *object_name, + int *object_type, + uint32_t * object_instance) +{ + bool found = false; + int type = 0; + uint32_t instance; + unsigned max_objects = 0, i = 0; + bool check_id = false; + char *name = NULL; + + max_objects = Device_Object_List_Count(); + for (i = 0; i < max_objects; i++) { + check_id = Device_Object_List_Identifier(i, &type, &instance); + if (check_id) { + name = Device_Valid_Object_Id(type, instance); + if (strcmp(name, object_name) == 0) { + found = true; + if (object_type) { + *object_type = type; + } + if (object_instance) { + *object_instance = instance; + } + break; + } + } + } + + return found; +} + +/** Determine if we have an object of this type and instance number. + * @param object_type [in] The desired BACNET_OBJECT_TYPE + * @param object_instance [in] The object instance number to be looked up. + * @return The Object Name or else NULL if not found + */ +char *Device_Valid_Object_Id( + int object_type, + uint32_t object_instance) +{ + char *name = NULL; /* return value */ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if ((pObject != NULL) && (pObject->Object_Name != NULL)) + name = pObject->Object_Name(object_instance); + + return name; +} + +static void Update_Current_Time( + void) +{ + struct tm *tblock = NULL; +#if defined(_MSC_VER) + time_t tTemp; +#else + struct timeval tv; +#endif +/* +struct tm + +int tm_sec Seconds [0,60]. +int tm_min Minutes [0,59]. +int tm_hour Hour [0,23]. +int tm_mday Day of month [1,31]. +int tm_mon Month of year [0,11]. +int tm_year Years since 1900. +int tm_wday Day of week [0,6] (Sunday =0). +int tm_yday Day of year [0,365]. +int tm_isdst Daylight Savings flag. +*/ +#if defined(_MSC_VER) + time(&tTemp); + tblock = localtime(&tTemp); +#else + if (gettimeofday(&tv, NULL) == 0) { + tblock = localtime(&tv.tv_sec); + } +#endif + + if (tblock) { + datetime_set_date(&Local_Date, (uint16_t) tblock->tm_year + 1900, + (uint8_t) tblock->tm_mon + 1, (uint8_t) tblock->tm_mday); +#if !defined(_MSC_VER) + datetime_set_time(&Local_Time, (uint8_t) tblock->tm_hour, + (uint8_t) tblock->tm_min, (uint8_t) tblock->tm_sec, + (uint8_t) (tv.tv_usec / 10000)); +#else + datetime_set_time(&Local_Time, (uint8_t) tblock->tm_hour, + (uint8_t) tblock->tm_min, (uint8_t) tblock->tm_sec, 0); +#endif + if (tblock->tm_isdst) { + Daylight_Savings_Status = true; + } else { + Daylight_Savings_Status = false; + } + /* note: timezone is declared in stdlib. */ + UTC_Offset = timezone / 60; + } else { + datetime_date_wildcard_set(&Local_Date); + datetime_time_wildcard_set(&Local_Time); + Daylight_Savings_Status = false; + } +} + +/* return the length of the apdu encoded or BACNET_STATUS_ERROR for error or + BACNET_STATUS_ABORT for abort message */ +static int Device_Read_Property_Local( + BACNET_READ_PROPERTY_DATA * rpdata) +{ + int apdu_len = 0; /* return value */ + int len = 0; /* apdu len intermediate value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + unsigned i = 0; + int object_type = 0; + uint32_t instance = 0; + unsigned count = 0; + uint8_t *apdu = NULL; + struct object_functions *pObject = NULL; + bool found = false; + DEVICE_OBJECT_DATA *pDev; + + if (rpdata == NULL) { + return 0; + } + pDev = Device_Instance_To_Data( rpdata->object_instance ); + if ((rpdata->application_data == NULL) || + (rpdata->application_data_len == 0) || (pDev == NULL)) { + return 0; + } + + apdu = rpdata->application_data; + switch (rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = + encode_application_object_id(&apdu[0], OBJECT_DEVICE, + pDev->bacObj.Object_Instance_Number); + break; + case PROP_OBJECT_NAME: + characterstring_init_ansi(&char_string, pDev->bacObj.Object_Name); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_application_enumerated(&apdu[0], OBJECT_DEVICE); + break; + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, pDev->Description); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_SYSTEM_STATUS: + apdu_len = encode_application_enumerated(&apdu[0], System_Status); + break; + case PROP_VENDOR_NAME: + characterstring_init_ansi(&char_string, Vendor_Name); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_VENDOR_IDENTIFIER: + apdu_len = + encode_application_unsigned(&apdu[0], Vendor_Identifier); + break; + case PROP_MODEL_NAME: + characterstring_init_ansi(&char_string, Model_Name); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_FIRMWARE_REVISION: + characterstring_init_ansi(&char_string, BACnet_Version); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_APPLICATION_SOFTWARE_VERSION: + characterstring_init_ansi(&char_string, + Application_Software_Version); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_LOCATION: + characterstring_init_ansi(&char_string, Location); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_LOCAL_TIME: + Update_Current_Time(); + apdu_len = encode_application_time(&apdu[0], &Local_Time); + break; + case PROP_UTC_OFFSET: + Update_Current_Time(); + apdu_len = encode_application_signed(&apdu[0], UTC_Offset); + break; + case PROP_LOCAL_DATE: + Update_Current_Time(); + apdu_len = encode_application_date(&apdu[0], &Local_Date); + break; + case PROP_DAYLIGHT_SAVINGS_STATUS: + Update_Current_Time(); + apdu_len = + encode_application_boolean(&apdu[0], Daylight_Savings_Status); + break; + case PROP_PROTOCOL_VERSION: + apdu_len = + encode_application_unsigned(&apdu[0], + Device_Protocol_Version()); + break; + case PROP_PROTOCOL_REVISION: + apdu_len = + encode_application_unsigned(&apdu[0], + Device_Protocol_Revision()); + break; + case PROP_PROTOCOL_SERVICES_SUPPORTED: + /* Note: list of services that are executed, not initiated. */ + bitstring_init(&bit_string); + for (i = 0; i < MAX_BACNET_SERVICES_SUPPORTED; i++) { + /* automatic lookup based on handlers set */ + bitstring_set_bit(&bit_string, (uint8_t) i, + apdu_service_supported((BACNET_SERVICES_SUPPORTED) i)); + } + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED: + /* Note: this is the list of objects that can be in this device, + not a list of objects that this device can access */ + bitstring_init(&bit_string); + for (i = 0; i < MAX_ASHRAE_OBJECT_TYPE; i++) { + /* initialize all the object types to not-supported */ + bitstring_set_bit(&bit_string, (uint8_t) i, false); + } + /* set the object types with objects to supported */ + + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if ((pObject->Object_Count) && (pObject->Object_Count() > 0)) { + bitstring_set_bit(&bit_string, pObject->Object_Type, true); + } + pObject++; + } + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + case PROP_OBJECT_LIST: + count = Device_Object_List_Count(); + /* Array element zero is the number of objects in the list */ + if (rpdata->array_index == 0) + apdu_len = encode_application_unsigned(&apdu[0], count); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. Note that more than likely you will have */ + /* to return an error if the number of encoded objects exceeds */ + /* your maximum APDU size. */ + else if (rpdata->array_index == BACNET_ARRAY_ALL) { + for (i = 1; i <= count; i++) { + found = + Device_Object_List_Identifier(i, &object_type, + &instance); + if (found) { + len = + encode_application_object_id(&apdu[apdu_len], + object_type, instance); + apdu_len += len; + /* assume next one is the same size as this one */ + /* can we all fit into the APDU? */ + if ((apdu_len + len) >= MAX_APDU) { + /* Abort response */ + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + apdu_len = BACNET_STATUS_ABORT; + break; + } + } else { + /* error: internal error? */ + rpdata->error_class = ERROR_CLASS_SERVICES; + rpdata->error_code = ERROR_CODE_OTHER; + apdu_len = BACNET_STATUS_ERROR; + break; + } + } + } else { + found = + Device_Object_List_Identifier(rpdata->array_index, + &object_type, &instance); + if (found) { + apdu_len = + encode_application_object_id(&apdu[0], object_type, + instance); + } else { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = BACNET_STATUS_ERROR; + } + } + break; + case PROP_MAX_APDU_LENGTH_ACCEPTED: + apdu_len = encode_application_unsigned(&apdu[0], MAX_APDU); + break; + case PROP_SEGMENTATION_SUPPORTED: + apdu_len = + encode_application_enumerated(&apdu[0], + Device_Segmentation_Supported()); + break; + case PROP_APDU_TIMEOUT: + apdu_len = encode_application_unsigned(&apdu[0], apdu_timeout()); + break; + case PROP_NUMBER_OF_APDU_RETRIES: + apdu_len = encode_application_unsigned(&apdu[0], apdu_retries()); + break; + case PROP_DEVICE_ADDRESS_BINDING: + /* FIXME: the real max apdu remaining should be passed into function */ + apdu_len = address_list_encode(&apdu[0], MAX_APDU); + break; + case PROP_DATABASE_REVISION: + apdu_len = + encode_application_unsigned(&apdu[0], Database_Revision); + break; +#if defined(BACDL_MSTP) + case PROP_MAX_INFO_FRAMES: + apdu_len = + encode_application_unsigned(&apdu[0], + dlmstp_max_info_frames()); + break; + case PROP_MAX_MASTER: + apdu_len = + encode_application_unsigned(&apdu[0], dlmstp_max_master()); + break; +#endif + case PROP_ACTIVE_COV_SUBSCRIPTIONS: + /* FIXME: the real max apdu should be passed into function */ + apdu_len = handler_cov_encode_subscriptions(&apdu[0], MAX_APDU); + break; + default: + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = BACNET_STATUS_ERROR; + break; + } + /* only array properties can have array options */ + if ((apdu_len >= 0) && (rpdata->object_property != PROP_OBJECT_LIST) && + (rpdata->array_index != BACNET_ARRAY_ALL)) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** Looks up the requested Object and Property, and encodes its Value in an APDU. + * @ingroup ObjIntf + * If the Object or Property can't be found, sets the error class and code. + * + * @param rpdata [in,out] Structure with the desired Object and Property info + * on entry, and APDU message on return. + * @return The length of the APDU on success, else BACNET_STATUS_ERROR + */ +int Device_Read_Property( + BACNET_READ_PROPERTY_DATA * rpdata) +{ + int apdu_len = BACNET_STATUS_ERROR; + struct object_functions *pObject = NULL; + + /* initialize the default return values */ + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; + pObject = Device_Objects_Find_Functions(rpdata->object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(rpdata->object_instance)) { + if (pObject->Object_Read_Property) { + apdu_len = pObject->Object_Read_Property(rpdata); + } + } else { + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE; + } + + return apdu_len; +} + +/* returns true if successful */ +static bool Device_Write_Property_Local( + BACNET_WRITE_PROPERTY_DATA * wp_data) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + int temp; + DEVICE_OBJECT_DATA *pDev; + + if (wp_data == NULL) { + return status; + } + pDev = Device_Instance_To_Data( wp_data->object_instance ); + if ((wp_data->application_data_len == 0) || (pDev == NULL)) { + return status; + } + + /* decode the some of the request */ + len = + bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_OBJECT_IDENTIFIER: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_OBJECT_ID, + &wp_data->error_class, &wp_data->error_code); + if (status) { + if (value.type.Object_Id.type == OBJECT_DEVICE) { + status = Device_Change_Object_Instance_Number( + wp_data->object_instance, + value.type.Object_Id.instance ); + } + if (status) { + /* FIXME: we could send an I-Am broadcast to let the world know */ + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_NUMBER_OF_APDU_RETRIES: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT, + &wp_data->error_class, &wp_data->error_code); + if (status) { + /* FIXME: bounds check? */ + apdu_retries_set((uint8_t) value.type.Unsigned_Int); + } + break; + case PROP_APDU_TIMEOUT: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT, + &wp_data->error_class, &wp_data->error_code); + if (status) { + /* FIXME: bounds check? */ + apdu_timeout_set((uint16_t) value.type.Unsigned_Int); + } + break; + case PROP_VENDOR_IDENTIFIER: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT, + &wp_data->error_class, &wp_data->error_code); + if (status) { + /* FIXME: bounds check? */ + Device_Set_Vendor_Identifier((uint16_t) value. + type.Unsigned_Int); + } + break; + case PROP_SYSTEM_STATUS: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED, + &wp_data->error_class, &wp_data->error_code); + if (status) { + temp = Device_Set_System_Status((BACNET_DEVICE_STATUS) + value.type.Enumerated, false); + if (temp != 0) { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + if (temp == -1) { + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } else { + wp_data->error_code = + ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED; + } + } + } + break; + case PROP_OBJECT_NAME: + status = + WPValidateString(&value, MAX_DEV_NAME_LEN, false, + &wp_data->error_class, &wp_data->error_code); + if (status) { + status = Device_Set_Object_Name( wp_data->object_instance, + characterstring_value(&value.type.Character_String), + characterstring_length(&value.type.Character_String)); + } + break; + case PROP_LOCATION: + status = + WPValidateString(&value, MAX_DEV_LOC_LEN, true, + &wp_data->error_class, &wp_data->error_code); + if (status) { + Device_Set_Location(characterstring_value(&value. + type.Character_String), + characterstring_length(&value.type.Character_String)); + } + break; + + case PROP_DESCRIPTION: + status = + WPValidateString(&value, MAX_DEV_DESC_LEN, true, + &wp_data->error_class, &wp_data->error_code); + if (status) { + status = Device_Set_Description( wp_data->object_instance, + characterstring_value(&value.type.Character_String), + characterstring_length(&value.type.Character_String)); + } + break; + case PROP_MODEL_NAME: + status = + WPValidateString(&value, MAX_DEV_MOD_LEN, true, + &wp_data->error_class, &wp_data->error_code); + if (status) { + Device_Set_Model_Name(characterstring_value(&value. + type.Character_String), + characterstring_length(&value.type.Character_String)); + } + break; + +#if defined(BACDL_MSTP) + case PROP_MAX_INFO_FRAMES: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT, + &wp_data->error_class, &wp_data->error_code); + if (status) { + if (value.type.Unsigned_Int <= 255) { + dlmstp_set_max_info_frames((uint8_t) value. + type.Unsigned_Int); + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_MAX_MASTER: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT, + &wp_data->error_class, &wp_data->error_code); + if (status) { + if ((value.type.Unsigned_Int > 0) && + (value.type.Unsigned_Int <= 127)) { + dlmstp_set_max_master((uint8_t) value.type.Unsigned_Int); + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; +#endif + default: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + +/** Looks up the requested Object and Property, and set the new Value in it, + * if allowed. + * If the Object or Property can't be found, sets the error class and code. + * @ingroup ObjIntf + * + * @param wp_data [in,out] Structure with the desired Object and Property info + * and new Value on entry, and APDU message on return. + * @return True on success, else False if there is an error. + */ +bool Device_Write_Property( + BACNET_WRITE_PROPERTY_DATA * wp_data) +{ + bool status = false; /* Ever the pessamist! */ + struct object_functions *pObject = NULL; + + /* initialize the default return values */ + wp_data->error_class = ERROR_CLASS_OBJECT; + wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + pObject = Device_Objects_Find_Functions(wp_data->object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(wp_data->object_instance)) { + if (pObject->Object_Write_Property) { + status = pObject->Object_Write_Property(wp_data); + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } + } else { + wp_data->error_class = ERROR_CLASS_OBJECT; + wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + wp_data->error_class = ERROR_CLASS_OBJECT; + wp_data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE; + } + + return (status); +} + + +/** Initialize the child Object instances for this Device. + * @ingroup ObjIntf + */ +void Device_Init( + void) +{ + struct object_functions *pObject = NULL; + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Init) { + pObject->Object_Init(); + } + pObject++; + } +} + +/** Initialize the Device Objects and each of the child Object instances. + * @param first_object_instance Set the first (gateway) Device to this + instance number, and subsequent devices to incremented values. + */ +void Devices_Init( + uint32_t first_object_instance ) +{ + int i; + + /* First, initialize the gateway and remote Device objects. */ + for ( i = 0; i < NUM_DEVICES; i++ ) + { + DEVICE_OBJECT_DATA *pDev = &Devices[i]; + pDev->bacObj.mObject_Type = OBJECT_DEVICE; + pDev->bacObj.Object_Instance_Number = first_object_instance + i; + snprintf( pDev->bacObj.Object_Name, MAX_DEV_NAME_LEN, "%s %d", + DEV_NAME_BASE, i+1); + if ( i == 0 ) + strncpy( pDev->Description, DEV_DESCR_GATEWAY, MAX_DEV_DESC_LEN ); + else + snprintf( pDev->Description, MAX_DEV_DESC_LEN, "%s %d", + DEV_DESCR_REMOTE, i); + + } + + /* Now do the child objects (all shared by all devices) */ + Device_Init(); +} + + +bool DeviceGetRRInfo( + BACNET_READ_RANGE_DATA * pRequest, /* Info on the request */ + RR_PROP_INFO * pInfo) +{ /* Where to put the response */ + bool status = false; /* return value */ + + switch (pRequest->object_property) { + case PROP_VT_CLASSES_SUPPORTED: + case PROP_ACTIVE_VT_SESSIONS: + case PROP_LIST_OF_SESSION_KEYS: + case PROP_TIME_SYNCHRONIZATION_RECIPIENTS: + case PROP_MANUAL_SLAVE_ADDRESS_BINDING: + case PROP_SLAVE_ADDRESS_BINDING: + case PROP_RESTART_NOTIFICATION_RECIPIENTS: + case PROP_UTC_TIME_SYNCHRONIZATION_RECIPIENTS: + pInfo->RequestTypes = RR_BY_POSITION; + pRequest->error_class = ERROR_CLASS_PROPERTY; + pRequest->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + break; + + case PROP_DEVICE_ADDRESS_BINDING: + pInfo->RequestTypes = RR_BY_POSITION; + pInfo->Handler = rr_address_list_encode; + status = true; + break; + + case PROP_ACTIVE_COV_SUBSCRIPTIONS: + pInfo->RequestTypes = RR_BY_POSITION; + pRequest->error_class = ERROR_CLASS_PROPERTY; + pRequest->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + break; + default: + pRequest->error_class = ERROR_CLASS_SERVICES; + pRequest->error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST; + break; + } + + return status; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testDevice( + Test * pTest) +{ + bool status = false; + const char *name = "Patricia"; + + status = Device_Set_Object_Instance_Number(0); + ct_test(pTest, Device_Object_Instance_Number() == 0); + ct_test(pTest, status == true); + status = Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + ct_test(pTest, Device_Object_Instance_Number() == BACNET_MAX_INSTANCE); + ct_test(pTest, status == true); + status = Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE / 2); + ct_test(pTest, + Device_Object_Instance_Number() == (BACNET_MAX_INSTANCE / 2)); + ct_test(pTest, status == true); + status = Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE + 1); + ct_test(pTest, + Device_Object_Instance_Number() != (BACNET_MAX_INSTANCE + 1)); + ct_test(pTest, status == false); + + + Device_Set_System_Status(STATUS_NON_OPERATIONAL, true); + ct_test(pTest, Device_System_Status() == STATUS_NON_OPERATIONAL); + + ct_test(pTest, Device_Vendor_Identifier() == BACNET_VENDOR_ID); + + Device_Set_Model_Name(name, strlen(name)); + ct_test(pTest, strcmp(Device_Model_Name(), name) == 0); + + return; +} + +#ifdef TEST_DEVICE +int main( + void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Device", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testDevice); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_DEVICE */ +#endif /* TEST */ diff --git a/bacnet-stack/demo/gateway/device.h b/bacnet-stack/demo/gateway/device.h new file mode 100644 index 00000000..ea9f9fa8 --- /dev/null +++ b/bacnet-stack/demo/gateway/device.h @@ -0,0 +1,314 @@ +/************************************************************************** +* +* Copyright (C) 2005 Steve Karg +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*********************************************************************/ + +/** @file device.h Defines functions for handling all BACnet objects belonging + * to a BACnet device, as well as Device-specific properties. */ + +#ifndef DEVICE_H +#define DEVICE_H + +#include +#include +#include "bacdef.h" +#include "bacenum.h" +#include "wp.h" +#include "rd.h" +#include "rp.h" +#include "rpm.h" +#include "readrange.h" + +/** Called so a BACnet object can perform any necessary initialization. + * @ingroup ObjHelpers + */ +typedef void ( + *object_init_function) ( + void); + +/** Counts the number of objects of this type. + * @ingroup ObjHelpers + * @return Count of implemented objects of this type. + */ +typedef unsigned ( + *object_count_function) ( + void); + +/** Maps an object index position to its corresponding BACnet object instance number. + * @ingroup ObjHelpers + * @param index [in] The index of the object, in the array of objects of its type. + * @return The BACnet object instance number to be used in a BACNET_OBJECT_ID. + */ +typedef uint32_t( + *object_index_to_instance_function) + ( + unsigned index); + +/** Provides the BACnet Object_Name for a given object instance of this type. + * @ingroup ObjHelpers + * @param [in] The object instance number to be looked up. + * @return Pointer to a string containing the unique Object_Name. This string + * is temporary and should be copied upon the return. It is + * allocated by the system and does not need to be freed. + */ +typedef char *( + *object_name_function) + ( + uint32_t object_instance); + +/** Look in the table of objects of this type, and see if this is a valid + * instance number. + * @ingroup ObjHelpers + * @param [in] The object instance number to be looked up. + * @return True if the object instance refers to a valid object of this type. + */ +typedef bool( + *object_valid_instance_function) ( + uint32_t object_instance); + +/** Helper function to step through an array of objects and find either the + * first one or the next one of a given type. Used to step through an array + * of objects which is not necessarily contiguous for each type i.e. the + * index for the 'n'th object of a given type is not necessarily 'n'. + * @ingroup ObjHelpers + * @param [in] The index of the current object or a value of ~0 to indicate + * start at the beginning. + * @return The index of the next object of the required type or ~0 (all bits + * == 1) to indicate no more objects found. + */ +typedef unsigned ( + *object_iterate_function) ( + unsigned current_index); + + + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* String Lengths - excluding any nul terminator */ +#define MAX_DEV_NAME_LEN 32 +#define MAX_DEV_LOC_LEN 64 +#define MAX_DEV_MOD_LEN 32 +#define MAX_DEV_VER_LEN 16 +#define MAX_DEV_DESC_LEN 64 + +/** Structure to define the Object Properties common to all Objects. */ +typedef struct commonBacObj_s +{ + + /** The BACnet type of this object (ie, what class is this object from?). + * This property, of type BACnetObjectType, indicates membership in a + * particular object type class. Each inherited class will be of one type. + */ + BACNET_OBJECT_TYPE mObject_Type; + + /** The instance number for this class instance. */ + uint32_t Object_Instance_Number; + + /** Object Name, taken from its BACnet/WS node path; must be unique. + * This property, of type CharacterString, shall represent a name for + * the object that is unique within the BACnet Device that maintains it. + * We will use the object's node path as its Name. + */ + char Object_Name[MAX_DEV_NAME_LEN + 1]; +} COMMON_BAC_OBJECT; + + +/** Structure to define the variable Properties of Device Objects. + * This structure only defines variables for properties that are unique to + * a given Device object. The rest are fixed in device.c or hard-coded in + * into the read-property encoding. + */ +typedef struct devObj_s +{ + /** The BACnet Device Address for this device; ->len depends on DLL type. */ + BACNET_ADDRESS bacDevAddr; + + /** Structure for the Object Properties common to all Objects. */ + COMMON_BAC_OBJECT bacObj; + + /** Device Description. */ + char Description[MAX_DEV_DESC_LEN + 1]; + +} DEVICE_OBJECT_DATA; + + + + void Device_Init( + void); + void Devices_Init( + uint32_t first_object_instance ); + void Initialize_Device_Addresses( ); + + + bool Device_Reinitialize( + BACNET_REINITIALIZE_DEVICE_DATA * rd_data); + + BACNET_REINITIALIZED_STATE Device_Reinitialized_State( + void); + + rr_info_function Device_Objects_RR_Info( + BACNET_OBJECT_TYPE object_type); + + void Device_Property_Lists( + const int **pRequired, + const int **pOptional, + const int **pProprietary); + void Device_Objects_Property_List( + BACNET_OBJECT_TYPE object_type, + struct special_property_list_t *pPropertyList); + + uint32_t Device_Object_Instance_Number( + void); + bool Device_Set_Object_Instance_Number( + uint32_t object_id); + bool Device_Valid_Object_Instance_Number( + uint32_t object_id); + unsigned Device_Object_List_Count( + void); + bool Device_Object_List_Identifier( + unsigned array_index, + int *object_type, + uint32_t * instance); + + unsigned Device_Count( + void); + uint32_t Device_Index_To_Instance( + unsigned index); + char *Device_Name( + uint32_t object_instance); + + BACNET_DEVICE_STATUS Device_System_Status( + void); + int Device_Set_System_Status( + BACNET_DEVICE_STATUS status, + bool local); + + const char *Device_Vendor_Name( + void); + + uint16_t Device_Vendor_Identifier( + void); + void Device_Set_Vendor_Identifier( + uint16_t vendor_id); + + const char *Device_Model_Name( + void); + bool Device_Set_Model_Name( + const char *name, + size_t length); + + const char *Device_Firmware_Revision( + void); + + const char *Device_Application_Software_Version( + void); + bool Device_Set_Application_Software_Version( + const char *name, + size_t length); + +/* bool Device_Set_Object_Name( + * const char *name, + * size_t length); */ + const char *Device_Object_Name( + void); + + const char *Device_Description( + void); +/* bool Device_Set_Description( + * const char *name, + * size_t length); */ + + const char *Device_Location( + void); + bool Device_Set_Location( + const char *name, + size_t length); + + /* some stack-centric constant values - no set methods */ + uint8_t Device_Protocol_Version( + void); + uint8_t Device_Protocol_Revision( + void); + BACNET_SEGMENTATION Device_Segmentation_Supported( + void); + + uint32_t Device_Database_Revision( + void); + void Device_Set_Database_Revision( + uint32_t revision); + void Device_Inc_Database_Revision( + void); + + bool Device_Valid_Object_Name( + const char *object_name, + int *object_type, + uint32_t * object_instance); + char *Device_Valid_Object_Id( + int object_type, + uint32_t object_instance); + + int Device_Read_Property( + BACNET_READ_PROPERTY_DATA * rpdata); + bool Device_Write_Property( + BACNET_WRITE_PROPERTY_DATA * wp_data); + + bool DeviceGetRRInfo( + BACNET_READ_RANGE_DATA * pRequest, /* Info on the request */ + RR_PROP_INFO * pInfo); /* Where to put the information */ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +/** @defgroup ObjFrmwk Object Framework + * The modules in this section describe the BACnet-stack's framework for + * BACnet-defined Objects (Device, Analog Input, etc). There are two submodules + * to describe this arrangement: + * - The "object helper functions" which provide C++-like common functionality + * to all supported object types. + * - The interface between the implemented Objects and the BAC-stack services, + * specifically the handlers, which are mediated through function calls to + * the Device object. + *//** @defgroup ObjHelpers Object Helper Functions + * @ingroup ObjFrmwk + * This section describes the function templates for the helper functions that + * provide common object support. + *//** @defgroup ObjIntf Handler-to-Object Interface Functions + * @ingroup ObjFrmwk + * This section describes the fairly limited set of functions that link the + * BAC-stack handlers to the BACnet Object instances. All of these calls are + * situated in the Device Object, which "knows" how to reach its child Objects. + * + * Most of these calls have a common operation: + * -# Call Device_Objects_Find_Functions( for the desired Object_Type ) + * - Gets a pointer to the object_functions for this Type of Object. + * -# Call the Object's Object_Valid_Instance( for the desired object_instance ) + * to make sure there is such an instance. + * -# Call the Object helper function needed by the handler, + * eg Object_Read_Property() for the RP handler. + * + */ +#endif diff --git a/bacnet-stack/demo/gateway/gateway.h b/bacnet-stack/demo/gateway/gateway.h new file mode 100644 index 00000000..1f713cfa --- /dev/null +++ b/bacnet-stack/demo/gateway/gateway.h @@ -0,0 +1,49 @@ +/************************************************************************** +* +* Copyright (C) 2006 Steve Karg +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*********************************************************************/ + +#ifndef GATEWAY_H_ +#define GATEWAY_H_ + +/** @file gateway/gateway.h Header for example gateway (ie, BACnet Router + * and Devices) using the BACnet Stack. */ + +/** @defgroup GatewayDemo Demo of a BACnet virtual gateway (multiple Device). + * @ingroup Demos + * This is a basic demonstration of a BACnet Router with child devices (ie, + * gateway) appearing on a virtual BACnet network behind the Router. + * This is an extension of the ServerDemo project. + */ + +/* Device configuration definitions. */ +#define NUM_DEVICES 3 /* Gateway + two remote devices */ +#define FIRST_DEVICE_NUMBER 260001 +#define VIRTUAL_DNET 2709 /* your choice of number here */ +#define DEV_NAME_BASE "Gateway Demo Device" +#define DEV_DESCR_GATEWAY "Gateway Device and Router" +#define DEV_DESCR_REMOTE "Routed Remote Device" + + + +#endif /* GATEWAY_H_ */ diff --git a/bacnet-stack/demo/gateway/main.c b/bacnet-stack/demo/gateway/main.c new file mode 100644 index 00000000..e5f9c8cd --- /dev/null +++ b/bacnet-stack/demo/gateway/main.c @@ -0,0 +1,230 @@ +/************************************************************************** +* +* Copyright (C) 2006 Steve Karg +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*********************************************************************/ +/** + * Code for this project began with code from the demo/server project and + * Paul Chapman's vmac project. + */ + +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "gateway.h" +#include "address.h" +#include "bacdef.h" +#include "handlers.h" +#include "client.h" +#include "dlenv.h" +#include "bacdcode.h" +#include "npdu.h" +#include "apdu.h" +#include "iam.h" +#include "tsm.h" +#include "device.h" +#include "bacfile.h" +#include "datalink.h" +#include "dcc.h" +#include "net.h" +#include "txbuf.h" +#include "lc.h" +#include "debug.h" +#include "version.h" +/* include the device object */ +#include "device.h" +//#include "vmac.h" + +/** @file gateway/main.c Example virtual gateway application using the BACnet Stack. */ + +/* Prototypes */ +extern void routing_npdu_handler( BACNET_ADDRESS * src, uint8_t * pdu, uint16_t pdu_len); + +/* (Doxygen note: The next two lines pull all the following Javadoc + * into the GatewayDemo module.) */ +/** @addtogroup GatewayDemo */ +/*@{*/ + +/** Buffer used for receiving */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/** The list of DNETs that our router can reach. + * Only one entry since we don't support downstream routers. + */ +int DNET_list[2] = { + VIRTUAL_DNET, -1 /* Need -1 terminator */ +}; +/** Initialize the handlers we will utilize. + * @see Device_Init, apdu_set_unconfirmed_handler, apdu_set_confirmed_handler + */ +static void Init_Service_Handlers( + uint32_t first_object_instance ) +{ + Devices_Init( first_object_instance ); + /* we need to handle who-is to support dynamic device binding */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, handler_who_has); + /* set the handler for all the services we don't implement */ + /* It is required to send the proper reject message... */ + apdu_set_unrecognized_service_handler_handler + (handler_unrecognized_service); + /* Set the handlers for any confirmed services that we support. */ + /* We must implement read property - it's required! */ + apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY, + handler_read_property); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROP_MULTIPLE, + handler_read_property_multiple); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, + handler_write_property); +#if defined(BACFILE) + apdu_set_confirmed_handler(SERVICE_CONFIRMED_ATOMIC_READ_FILE, + handler_atomic_read_file); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, + handler_atomic_write_file); +#endif + apdu_set_confirmed_handler(SERVICE_CONFIRMED_REINITIALIZE_DEVICE, + handler_reinitialize_device); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION, + handler_timesync_utc); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION, + handler_timesync); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_SUBSCRIBE_COV, + handler_cov_subscribe); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_COV_NOTIFICATION, + handler_ucov_notification); + /* handle communication so we can shutup when asked */ + apdu_set_confirmed_handler(SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + handler_device_communication_control); +} + +/** Handler registered with atexit() inside main function to, well, cleanup. + * Especially if we don't end normally. + * @see datalink_cleanup + */ +static void cleanup( + void) +{ + datalink_cleanup(); +} + +/** Main function of server demo. + * + * @see Device_Set_Object_Instance_Number, dlenv_init, Send_I_Am, + * datalink_receive, npdu_handler, + * dcc_timer_seconds, bvlc_maintenance_timer, + * Load_Control_State_Machine_Handler, 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[]) +{ + BACNET_ADDRESS src = { + 0 + }; /* address where message came from */ + uint16_t pdu_len = 0; + unsigned timeout = 1000; /* milliseconds */ + time_t last_seconds = 0; + time_t current_seconds = 0; + uint32_t elapsed_seconds = 0; + uint32_t elapsed_milliseconds = 0; + uint32_t first_object_instance = FIRST_DEVICE_NUMBER; + + /* Router data */ +// BACNET_DEVICE_PROFILE* device; +// BACNET_VMAC_ADDRESS adr; + + /* allow the device ID to be set */ + if (argc > 1) { + first_object_instance = strtol(argv[1], NULL, 0); + if ( ( first_object_instance == 0 ) || + ( first_object_instance >= BACNET_MAX_INSTANCE ) ) { + printf( "Error: Invalid Object Instance %s \n", argv[1] ); + printf( "Provide a number from 1 to %ul \n", BACNET_MAX_INSTANCE -1 ); + exit(1); + } + } + printf("BACnet Router Demo\n" "BACnet Stack Version %s\n" + "BACnet Device ID: %u\n" "Max APDU: %d\n", BACnet_Version, + first_object_instance, MAX_APDU); + Init_Service_Handlers( first_object_instance ); + dlenv_init(); + atexit(cleanup); + Initialize_Device_Addresses( ); + +// /* initialize vmac table and router device */ +// device = vmac_initialize(99, 2001); +// +// debug_printf(device->name, "ROUTER:%u", vmac_get_subnet()); + + /* configure the timeout values */ + last_seconds = time(NULL); + /* broadcast an I-Am on startup */ + Send_I_Am(&Handler_Transmit_Buffer[0]); + + /* broadcast an I-am-router-to-network on startup */ + printf( "Remote Network DNET Number %d \n", DNET_list[0] ); + Send_I_Am_Router_To_Network( DNET_list ); + + /* loop forever */ + for (;;) { + /* input */ + current_seconds = time(NULL); + + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + + /* process */ + if (pdu_len) { + routing_npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + /* at least one second has passed */ + elapsed_seconds = current_seconds - last_seconds; + if (elapsed_seconds) { + last_seconds = current_seconds; + dcc_timer_seconds(elapsed_seconds); +#if defined(BACDL_BIP) && BBMD_ENABLED + bvlc_maintenance_timer(elapsed_seconds); +#endif + dlenv_maintenance_timer(elapsed_seconds); + Load_Control_State_Machine_Handler(); + elapsed_milliseconds = elapsed_seconds * 1000; + handler_cov_task(elapsed_seconds); + tsm_timer_milliseconds(elapsed_milliseconds); + } + /* output */ + + /* blink LEDs, Turn on or off outputs, etc */ + } +} + +/* @} */ + +/* End group GatewayDemo */ diff --git a/bacnet-stack/demo/gateway/makefile.b32 b/bacnet-stack/demo/gateway/makefile.b32 new file mode 100644 index 00000000..c45da4e8 --- /dev/null +++ b/bacnet-stack/demo/gateway/makefile.b32 @@ -0,0 +1,144 @@ +# +# Simple makefile to build an executable for Win32 +# +# This makefile assumes Borland bcc32 development environment +# on Windows NT/9x/2000/XP +# + +!ifndef BORLAND_DIR +BORLAND_DIR_Not_Defined: + @echo . + @echo You must define environment variable BORLAND_DIR to compile. +!endif + +# target +PRODUCT = bacgateway +PRODUCT_EXE = $(PRODUCT).exe + +# tools +CC = $(BORLAND_DIR)\bin\bcc32 +MAKE=$(BORLAND_DIR)\bin\make.exe +#LINK = $(BORLAND_DIR)\bin\tlink32 +LINK = $(BORLAND_DIR)\bin\ilink32 + +BACNET_LIB_DIR = ..\..\lib +BACNET_LIB = $(BACNET_LIB_DIR)\bacnet.lib +# getting back from the library +BACNET_DEMO_DIR = ..\demo\gateway + +# directories +BACNET_PORT = ..\..\ports\win32 +BACNET_INCLUDE = ..\..\include +INCLUDES = \ + -I$(BACNET_INCLUDE) \ + -I$(BACNET_PORT) \ + -I$(BORLAND_DIR)\include + +# +BACNET_DEFINES = -DPRINT_ENABLED=1 -DBACFILE -DBACAPP_ALL +#BACDL_DEFINE=-DBACDL_MSTP -DCRC_USE_TABLE +BACDL_DEFINE=-DBACDL_BIP=1 -DUSE_INADDR=1 +DEFINES = $(BACNET_DEFINES) $(BACDL_DEFINE) + +SRCS = main.c device.c + +OBJS = $(SRCS:.c=.obj) + +# +# Compiler definitions +# +BCC_CFG = bcc32.cfg + +# +# Include directories +# +CFLAGS = $(INCLUDES) $(DEFINES) + +# +# Libraries +# +C_LIB_DIR = $(BORLAND_DIR)\lib + +LIBS = $(BACNET_LIB) \ + $(C_LIB_DIR)\IMPORT32.lib \ + $(C_LIB_DIR)\CW32MT.lib \ + +# +# Main target +# +# This should be the first one in the makefile + +all : $(BACNET_LIB) $(BCC_CFG) $(OBJS) $(PRODUCT_EXE) + del $(BCC_CFG) + +install: $(PRODUCT_EXE) + copy $(PRODUCT_EXE) ..\..\bin\$(PRODUCT_EXE) + +$(BACNET_LIB): + cd $(BACNET_LIB_DIR) + $(MAKE) -i -f makefile.b32 clean + $(MAKE) -f makefile.b32 all + cd $(BACNET_DEMO_DIR) + +# Linker specific: the link below is for BCC linker/compiler. If you link +# with a different linker - please change accordingly. +# + +# need a temp response file (@&&| ... |) because command line is too long +# $** lists each dependency +# $< target name +# $* target name without extension +$(PRODUCT_EXE) : $(OBJS) + @echo Running Linker for $(PRODUCT_EXE) + $(LINK) -L$(C_LIB_DIR) -L$(BACNET_LIB_DIR) -m -c -s -v @&&| + $(BORLAND_DIR)\lib\c0x32.obj $** + $< + $*.map + $(LIBS) +| + +# +# Utilities + +clean : + del $(OBJS) + del $(PRODUCT_EXE) + del $(PRODUCT).map + del $(PRODUCT).ilc + del $(PRODUCT).ild + del $(PRODUCT).ilf + del $(PRODUCT).ils + del $(PRODUCT).tds + del $(BCC_CFG) + +# +# Generic rules +# +.SUFFIXES: .cpp .c .sbr .obj + +# +# cc generic rule +# +.c.obj: + $(CC) +$(BCC_CFG) -o$@ $< + +# Compiler configuration file +$(BCC_CFG) : + Copy &&| + $(CFLAGS) + -c + -y #include line numbers in OBJ's + -v #include debug info + -w+ #turn on all warnings + #-Od #disable all optimizations + -O2 #disable all optimizations + -WM #multithread + #-WM- #not multithread + -w-aus # ignore warning assigned a value that is never used + -w-sig # ignore warning conversion may lose sig digits + #-a4 #32 bit data alignment + #-M # generate link map + #-ls # linker options +| $@ + +# EOF: makefile