diff --git a/bacnet-stack/Makefile b/bacnet-stack/Makefile index 32f7dcdb..441efa0b 100644 --- a/bacnet-stack/Makefile +++ b/bacnet-stack/Makefile @@ -1,6 +1,9 @@ +# Main Makefile for BACnet-stack project + +# Including "routing" in this list to add routing functionality all: library readprop writeprop readfile writefile reinit server dcc \ whohas whois ucov timesync epics readpropm mstpcap \ - whoisrouter iamrouter initrouter gateway + routing @echo "utilities are in the bin directory" clean: lib/Makefile\ @@ -87,17 +90,9 @@ whois: demo/whois/Makefile mstpcap: demo/mstpcap/Makefile ( cd demo/mstpcap ; make clean all; cp mstpcap ../../bin ) -whoisrouter: demo/whoisrouter/Makefile - ( cd demo/whoisrouter ; make ; cp bacwir ../../bin ) - -iamrouter: demo/iamrouter/Makefile - ( cd demo/iamrouter ; make ; cp baciamr ../../bin ) - -initrouter: demo/initrouter/Makefile - ( cd demo/initrouter ; make ; cp bacinitr ../../bin ) - +# Add "ports" to the build, if desired ports: atmega168 bdk-atxx4-mstp at91sam7s - echo "Built the ports" + @echo "Built the ports" atmega168: ports/atmega168/Makefile make -C ports/atmega168 clean all @@ -108,6 +103,20 @@ at91sam7s: ports/at91sam7s/makefile bdk-atxx4-mstp: ports/bdk-atxx4-mstp/Makefile make -C ports/bdk-atxx4-mstp clean all +# Build these demo projects if you want to test routing capability +# Also enable BACROUTE_ENABLE in lib/Makefile and BAC_ROUTING in config.h +routing: whoisrouter iamrouter initrouter gateway + @echo "Built routing demos" + +whoisrouter: demo/whoisrouter/Makefile + ( cd demo/whoisrouter ; make ; cp bacwir ../../bin ) + +iamrouter: demo/iamrouter/Makefile + ( cd demo/iamrouter ; make ; cp baciamr ../../bin ) + +initrouter: demo/initrouter/Makefile + ( cd demo/initrouter ; make ; cp bacinitr ../../bin ) + 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 index 5b96e21b..a4b7ebf8 100644 --- a/bacnet-stack/demo/gateway/Makefile +++ b/bacnet-stack/demo/gateway/Makefile @@ -59,7 +59,7 @@ endif CFLAGS = -Wall $(DEBUGGING) $(OPTIMIZATION) $(INCLUDES) $(DEFINES) LFLAGS = -Wl,-Map=$(TARGET).map,$(LIBRARIES) $(LINK_OPTIMIZATION) -SRCS = main.c device.c +SRCS = main.c OBJS = ${SRCS:.c=.o} diff --git a/bacnet-stack/demo/gateway/gateway.h b/bacnet-stack/demo/gateway/gateway.h index 1f713cfa..4e862562 100644 --- a/bacnet-stack/demo/gateway/gateway.h +++ b/bacnet-stack/demo/gateway/gateway.h @@ -37,7 +37,6 @@ */ /* 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" diff --git a/bacnet-stack/demo/gateway/main.c b/bacnet-stack/demo/gateway/main.c index 63805340..39bc0436 100644 --- a/bacnet-stack/demo/gateway/main.c +++ b/bacnet-stack/demo/gateway/main.c @@ -76,13 +76,46 @@ static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; int DNET_list[2] = { VIRTUAL_DNET, -1 /* Need -1 terminator */ }; + + + +/** 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; + char nameText[MAX_DEV_NAME_LEN]; + char descText[MAX_DEV_DESC_LEN]; + + /* Gateway Device has already been initialized. + * But give it a better Description. */ + Routed_Device_Set_Description(DEV_DESCR_GATEWAY, strlen(DEV_DESCR_GATEWAY)); + + /* Now initialize the remote Device objects. */ + for ( i = 1; i < MAX_NUM_DEVICES; i++ ) + { + snprintf( nameText, MAX_DEV_NAME_LEN, "%s %d", + DEV_NAME_BASE, i+1); + snprintf( descText, MAX_DEV_DESC_LEN, "%s %d", + DEV_DESCR_REMOTE, i); + + Add_Routed_Device( (first_object_instance+i), nameText, descText ); + } + +} + + /** 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 ); + Routing_Device_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); @@ -119,6 +152,59 @@ static void Init_Service_Handlers( handler_device_communication_control); } +/** 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 = 0; /* First entry is Gateway Device */ + DEVICE_OBJECT_DATA* pDev; + 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 */ + uint8_t *gatewayMac = NULL; + uint32_t myAddr = ntohl( bip_get_addr() ); + pDev = Get_Routed_Device_Object( i ); + gatewayMac = pDev->bacDevAddr.mac; /* Keep pointer to the main MAC */ + memcpy( pDev->bacDevAddr.mac, &myAddr, 4 ); + myPort = ntohs( 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 < MAX_NUM_DEVICES; i++ ) { + pDev = Get_Routed_Device_Object( i ); + if ( pDev == NULL ) + continue; +#if defined(BACDL_BIP) + netPtr = (struct in_addr *) pDev->bacDevAddr.mac; + pDev->bacDevAddr.mac[0] = gatewayMac[3]; + pDev->bacDevAddr.mac[1] = gatewayMac[2]; + pDev->bacDevAddr.mac[2] = gatewayMac[1]; + pDev->bacDevAddr.mac[3] = i; + memcpy( &pDev->bacDevAddr.mac[4], &myPort, 2 ); + 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 + + } +} + + /** Handler registered with atexit() inside main function to, well, cleanup. * Especially if we don't end normally. * @see datalink_cleanup @@ -175,8 +261,9 @@ int main( first_object_instance, MAX_APDU); Init_Service_Handlers( first_object_instance ); dlenv_init(); - atexit(cleanup); + Devices_Init( first_object_instance ); Initialize_Device_Addresses( ); + atexit(cleanup); // /* initialize vmac table and router device */ // device = vmac_initialize(99, 2001); diff --git a/bacnet-stack/demo/handler/h_routed_npdu.c b/bacnet-stack/demo/handler/h_routed_npdu.c index 617d9429..a8974fb6 100644 --- a/bacnet-stack/demo/handler/h_routed_npdu.c +++ b/bacnet-stack/demo/handler/h_routed_npdu.c @@ -35,6 +35,7 @@ #include "npdu.h" #include "apdu.h" #include "handlers.h" +#include "device.h" #include "client.h" #include "bactext.h" #include "debug.h" @@ -173,6 +174,22 @@ static void network_control_handler( } } +/** An APDU pre-handler that makes sure that the subsequent APDU handler call + * operates on the right Device Object(s), as addressed by the destination + * (routing) information. + * + * @note Even when the destination is "routed" to our virtual BACnet network, + * the src information does not need to change to reflect that (as it normally + * would for a routed message) because the reply will be sent from the level + * of the gateway Device. + * + * @param src [in] The BACNET_ADDRESS of the message's source. + * @param dest [in] The BACNET_ADDRESS of the message's destination. + * @param DNET_list [in] List of our reachable downstream BACnet Network numbers. + * Normally just one valid entry; terminated with a -1 value. + * @param apdu [in] The apdu portion of the request, to be processed. + * @param apdu_len [in] The total (remaining) length of the apdu. + */ static void routed_apdu_handler( BACNET_ADDRESS * src, BACNET_ADDRESS * dest, @@ -180,11 +197,30 @@ static void routed_apdu_handler( uint8_t * apdu, uint16_t apdu_len) { - int dnet = DNET_list[0]; - if ((dest->net == dnet) || (dest->net == BACNET_BROADCAST_NETWORK)) { - /* Handle the normal, non-routed variety for right now in development */ + int dnet = DNET_list[0]; /* Get the DNET of our virtual network */ + int i; + + /* First, see if it's for the main Gateway Device, either because + * there's no routing info or else its a BACnet broadcast. + */ + if ( (dest->net == 0 ) || (dest->net == BACNET_BROADCAST_NETWORK)) { + /* Handle like a normal, non-routed access of the Gateway Device. + * But first, make sure our internal access is pointing at + * that Device in our table by telling it "no routing info" : */ + Lookup_Routed_Device_Address( 0, 0, NULL ); apdu_handler(src, apdu, apdu_len); - } else { + } + /* Now check for our virtual DNET or BACnet broadcast, and check + * against each of our virtually routed Devices. + * If we get a match, have it handle the APDU. + * For broadcasts, all Devices get a chance at it. + */ + if ((dest->net == dnet) || (dest->net == BACNET_BROADCAST_NETWORK)) { + for ( i = 1; i < MAX_NUM_DEVICES; i++ ) { + if ( Lookup_Routed_Device_Address( i, dest->len, dest->adr ) ) + apdu_handler(src, apdu, apdu_len); + } + } else if ( dest->net != 0 ) { /* We don't know how to reach this one */ Send_Reject_Message_To_Network( src, NETWORK_REJECT_NO_ROUTE, dest->net ); } @@ -242,15 +278,8 @@ void routing_npdu_handler( * since only routers can handle it (even if for our DNET) */ } } else if (apdu_offset <= pdu_len) { - if ((dest.net == 0) || (dest.net == BACNET_BROADCAST_NETWORK)) { - /* Handle the normal, non-routed variety */ - apdu_handler(src, &pdu[apdu_offset], - (uint16_t) (pdu_len - apdu_offset)); - } else { - /* Handle the routed variety differently */ - routed_apdu_handler(src, &dest, DNET_list, &pdu[apdu_offset], - (uint16_t) (pdu_len - apdu_offset)); - } + routed_apdu_handler(src, &dest, DNET_list, &pdu[apdu_offset], + (uint16_t) (pdu_len - apdu_offset)); } } else { /* Should we send NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK? */ diff --git a/bacnet-stack/demo/initrouter/main.c b/bacnet-stack/demo/initrouter/main.c index a505f350..4b9a811c 100644 --- a/bacnet-stack/demo/initrouter/main.c +++ b/bacnet-stack/demo/initrouter/main.c @@ -53,7 +53,15 @@ static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; /* target address */ static BACNET_ADDRESS Target_Router_Address; -static BACNET_ROUTER_PORT *Target_Router_Port_List; +/* static BACNET_ROUTER_PORT *Target_Router_Port_List; */ + +#define VIRTUAL_DNET 2709 /* your choice of number here */ +/** 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 */ +}; static bool Error_Detected = false; @@ -319,7 +327,7 @@ int main( timeout_seconds = apdu_timeout() / 1000; /* send the request */ Send_Initialize_Routing_Table(&Target_Router_Address, - Target_Router_Port_List); + DNET_list); /* loop forever */ for (;;) { /* increment timer - exit if timed out */ diff --git a/bacnet-stack/demo/object/device.c b/bacnet-stack/demo/object/device.c index 30bc632e..7291197c 100644 --- a/bacnet-stack/demo/object/device.c +++ b/bacnet-stack/demo/object/device.c @@ -68,10 +68,14 @@ long int timezone; #endif -/* forward prototypes */ -static int Device_Read_Property_Local( +/* local forward (semi-private) and external prototypes */ +int Device_Read_Property_Local( BACNET_READ_PROPERTY_DATA * rpdata); -static bool Device_Write_Property_Local( +bool Device_Write_Property_Local( + BACNET_WRITE_PROPERTY_DATA * wp_data); +extern int Routed_Device_Read_Property_Local( + BACNET_READ_PROPERTY_DATA * rpdata); +extern bool Routed_Device_Write_Property_Local( BACNET_WRITE_PROPERTY_DATA * wp_data); /** Defines the group of object helper functions for any supported Object. @@ -388,14 +392,6 @@ void Device_Property_Lists( The properties that are constant can be hard coded into the read-property encoding. */ -/* 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 - 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; @@ -950,7 +946,7 @@ int tm_isdst Daylight Savings flag. /* 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( +int Device_Read_Property_Local( BACNET_READ_PROPERTY_DATA * rpdata) { int apdu_len = 0; /* return value */ @@ -1220,7 +1216,7 @@ int Device_Read_Property( } /* returns true if successful */ -static bool Device_Write_Property_Local( +bool Device_Write_Property_Local( BACNET_WRITE_PROPERTY_DATA * wp_data) { bool status = false; /* return value */ @@ -1484,6 +1480,44 @@ bool DeviceGetRRInfo( return status; } + +#if BAC_ROUTING +/**************************************************************************** + ************* BACnet Routing Functionality (Optional) ********************** + **************************************************************************** + * The supporting functions are located in gw_device.c, except for those + * that need access to local data in this file. + ****************************************************************************/ + +/** Initialize the first of our array of Devices with the main Device's + * information, and then swap out some of the Device object functions and + * replace with ones appropriate for routing. + * @ingroup ObjIntf + * @param first_object_instance Set the first (gateway) Device to this + instance number. + */ +void Routing_Device_Init( + uint32_t first_object_instance ) +{ + struct object_functions *pDevObject = NULL; + + /* First, do the usual Device_Init() functions: */ + Device_Init(); + /* Initialize with our preset strings */ + Add_Routed_Device( first_object_instance, My_Object_Name, Description ); + + /* Now substitute our routed versions of the main object functions. */ + pDevObject = &Object_Table[0]; + pDevObject->Object_Index_To_Instance = Routed_Device_Index_To_Instance; + pDevObject->Object_Valid_Instance = Routed_Device_Valid_Object_Instance_Number; + pDevObject->Object_Name = Routed_Device_Name; + pDevObject->Object_Read_Property = Routed_Device_Read_Property_Local; + pDevObject->Object_Write_Property = Routed_Device_Write_Property_Local; +} + +#endif /* BAC_ROUTING */ + + #ifdef TEST #include #include diff --git a/bacnet-stack/demo/object/gw_device.c b/bacnet-stack/demo/object/gw_device.c new file mode 100644 index 00000000..bf41a05b --- /dev/null +++ b/bacnet-stack/demo/object/gw_device.c @@ -0,0 +1,426 @@ +/* + * $HeadURL: https://svn.eaton.com/svn/PCM-tk/trunk/PCM-tk/etn_source/tbd/gw_device.c $ + *--------------------------------------------------------------------- + * $Author: brennant $ + * $Date: Oct 13, 2010 $ + *--------------------------------------------------------------------- + * Copyright (c) 2010 Eaton Corporation, All Rights Reserved. + * + * This software is the confidential and proprietary information of + * Eaton Corporation (Confidential Information). You shall not + * disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into + * with Eaton Corporation. + * + * EATON CORPORATION MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE + * SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING + * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. EATON + * CORPORATION SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY + * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS + * SOFTWARE OR ITS DERIVATIVES. + *--------------------------------------------------------------------- + */ + +/** @file gw_device.c [Description of this file]. */ + +#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 "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 + +/* local forward and external prototypes */ +extern int Device_Read_Property_Local( + BACNET_READ_PROPERTY_DATA * rpdata); +extern bool Device_Write_Property_Local( + BACNET_WRITE_PROPERTY_DATA * wp_data); +int Routed_Device_Read_Property_Local( + BACNET_READ_PROPERTY_DATA * rpdata); +bool Routed_Device_Write_Property_Local( + BACNET_WRITE_PROPERTY_DATA * wp_data); + + +#if !BAC_ROUTING +#warning This file should not be included in the build unless BAC_ROUTING is enabled. +#endif + +/**************************************************************************** + ************* BACnet Routing Functionality (Optional) ********************** + **************************************************************************** + * It would be correct to view the routing functionality here as inheriting + * and extending the regular Device Object functionality. + ****************************************************************************/ + +/** Model the gateway as the main Device, with (two) remote + * Devices that are reached via its routing capabilities. + */ +DEVICE_OBJECT_DATA Devices[MAX_NUM_DEVICES]; +/** Keep track of the number of managed devices, including the gateway */ +uint16_t Num_Managed_Devices = 0; +/** Which Device entry are we currently managing. + * Since we are not using actual class objects here, the best we can do is + * keep this local variable which notes which of the Devices the current + * request is addressing. Should default to 0, the main gateway Device. + */ +uint16_t iCurrent_Device_Idx = 0; + +/* void Routing_Device_Init(uint32_t first_object_instance) is + * found in device.c + */ + +/** Add a Device to our table of Devices[]. + * The first entry must be the gateway device. + * @param Object_Instance [in] Set the new Device to this instance number. + * @param sObject_Name [in] Use this Object Name for the Device. + * @param sDescription [in] Set this Description for the Device. + * @return The index of this instance in the Devices[] array, + * or -1 if there isn't enough room to add this Device. + */ +uint16_t Add_Routed_Device( + uint32_t Object_Instance, + const char * sObject_Name, + const char * sDescription ) +{ + int i = Num_Managed_Devices; + if ( i < MAX_NUM_DEVICES ) + { + DEVICE_OBJECT_DATA *pDev = &Devices[i]; + Num_Managed_Devices++; + iCurrent_Device_Idx = i; + pDev->bacObj.mObject_Type = OBJECT_DEVICE; + pDev->bacObj.Object_Instance_Number = Object_Instance; + Routed_Device_Set_Object_Name( sObject_Name, strlen( sObject_Name )); + Routed_Device_Set_Description( sDescription, strlen( sDescription )); + pDev->Database_Revision = 0; /* Reset/Initialize now */ + return i; + } + else + return -1; +} + + +/** Return the Device Object descriptive data for the indicated entry. + * @param idx [in] Index into Devices[] array being requested. + * 0 is for the main, gateway Device entry. + * -1 is a special case meaning "whichever iCurrent_Device_Idx + * is currently set to" + * @return Pointer to the requested Device Object data, or NULL if the idx + * is for an invalid row entry (eg, after the last good Device). + */ +DEVICE_OBJECT_DATA * Get_Routed_Device_Object( + int idx ) +{ + if ( idx == -1 ) + return &Devices[iCurrent_Device_Idx]; + else if ( (idx >= 0) && (idx < MAX_NUM_DEVICES) ) + return &Devices[idx]; + else + return NULL; +} + +/** See if the Gateway or Routed Device at the given idx matches + * the given MAC address. + * Has the desirable side-effect of setting iCurrent_Device_Idx to the + * given idx if a match is found, for use in the subsequent routing handling + * functions here. + * + * @param idx [in] Index into Devices[] array being requested. + * 0 is for the main, gateway Device entry. + * @param address_len [in] Length of the mac_adress[] field. + * If 0, then this is a MAC broadcast. Otherwise, size is determined + * by the DLL type (eg, 6 for BIP and 2 for MSTP). + * @param mac_adress [in] The desired MAC address of a Device; + * + * @return True if the MAC addresses match (or the address_len is 0, + * meaning MAC broadcast, so it's an automatic match). + * Else False if no match or invalid idx is given. + */ +bool Lookup_Routed_Device_Address( + int idx, + uint8_t address_len, + uint8_t * mac_adress ) +{ + bool result = false; + DEVICE_OBJECT_DATA *pDev = &Devices[idx]; + int i; + + if ( (idx >= 0) && (idx < MAX_NUM_DEVICES) ) { + if ( address_len == 0 ) { + /* Automatic match */ + iCurrent_Device_Idx = idx; + result = true; + } else { + for (i = 0; i < address_len; i++) { + if (pDev->bacDevAddr.mac[i] != mac_adress[i]) + break; + } + if ( i == address_len ) { /* Success! */ + iCurrent_Device_Idx = idx; + result = true; + } + } + } + return result; +} + +/* methods to override the normal Device objection functions */ + +uint32_t Routed_Device_Index_To_Instance( + unsigned index) +{ + index = index; + return Devices[iCurrent_Device_Idx].bacObj.Object_Instance_Number; +} + +/** See if the requested Object instance matches that for the currently + * indexed Device Object. + * iCurrent_Device_Idx must have been set to point to this Device Object + * before this function is called. + * @param object_id [in] Object ID of the desired Device object. + * If the wildcard value (BACNET_MAX_INSTANCE), always matches. + * @return True if Object ID matches the present Device, else False. + */ +bool Routed_Device_Valid_Object_Instance_Number( + uint32_t object_id) +{ + bool bResult = false; + DEVICE_OBJECT_DATA *pDev = &Devices[iCurrent_Device_Idx]; + /* BACnet allows for a wildcard instance number */ + if (object_id == BACNET_MAX_INSTANCE) + bResult = true; + if ( pDev->bacObj.Object_Instance_Number == object_id ) + bResult = true; + return bResult; +} + +char *Routed_Device_Name( + uint32_t object_instance) +{ + DEVICE_OBJECT_DATA *pDev = &Devices[iCurrent_Device_Idx]; + if (object_instance == pDev->bacObj.Object_Instance_Number) { + return pDev->bacObj.Object_Name; + } + + return NULL; +} + +/** Manages ReadProperty service for fields which are different for routed + * Devices, or hands off to the default Device RP function for the rest. + * @param rpdata [in] Structure which describes the property to be read. + * @return The length of the apdu encoded, or BACNET_STATUS_ERROR for error or + * BACNET_STATUS_ABORT for abort message. + */ +int Routed_Device_Read_Property_Local( + BACNET_READ_PROPERTY_DATA * rpdata) +{ + int apdu_len = 0; /* return value */ + BACNET_CHARACTER_STRING char_string; + uint8_t *apdu = NULL; + DEVICE_OBJECT_DATA *pDev = &Devices[iCurrent_Device_Idx]; + + if ((rpdata == NULL) || (rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + 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_DESCRIPTION: + characterstring_init_ansi(&char_string, pDev->Description); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_DATABASE_REVISION: + apdu_len = + encode_application_unsigned(&apdu[0], pDev->Database_Revision); + break; + default: + apdu_len = Device_Read_Property_Local( rpdata); + break; + } + + return ( apdu_len ); +} + +bool Routed_Device_Write_Property_Local( + BACNET_WRITE_PROPERTY_DATA * wp_data) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + /* decode the some of the request */ + len = + bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + if (len < 0) { + /* error while decoding - a value larger than we can handle */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + return false; + } + /* FIXME: len < application_data_len: more data? */ + 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) && + (Routed_Device_Set_Object_Instance_Number(value.type. + Object_Id.instance))) { + /* FIXME: we could send an I-Am broadcast to let the world know */ + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_OBJECT_NAME: + status = + WPValidateString(&value, MAX_DEV_NAME_LEN, false, + &wp_data->error_class, &wp_data->error_code); + if (status) { + Routed_Device_Set_Object_Name(characterstring_value(&value. + type.Character_String), + characterstring_length(&value.type.Character_String)); + } + break; + default: + status = Device_Write_Property_Local( wp_data); + break; + } + return status; +} + +/* methods to manipulate the data */ + +/** Return the Object Instance number for our (single) Device Object. + * This is a key function, widely invoked by the handler code, since + * it provides "our" (ie, local) address. + * @ingroup ObjIntf + * @return The Instance number used in the BACNET_OBJECT_ID for the Device. + */ +uint32_t Routed_Device_Object_Instance_Number( + void) +{ + return Devices[iCurrent_Device_Idx].bacObj.Object_Instance_Number; +} + +bool Routed_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[iCurrent_Device_Idx].bacObj.Object_Instance_Number = object_id; + Routed_Device_Inc_Database_Revision(); + } else + status = false; + + return status; +} + + +/** Sets the Object Name for a routed Device (or the gateway). + * Uses local variable iCurrent_Device_Idx to know which Device + * is to be updated. + * @param name [in] Text for the new Object Name. + * @param length [in] Length of name[] text. + * @return True if succeed in updating Object Name, else False. + */ +bool Routed_Device_Set_Object_Name( + const char *name, + size_t length) +{ + bool status = false; /*return value */ + DEVICE_OBJECT_DATA *pDev = &Devices[iCurrent_Device_Idx]; + + if (length < MAX_DEV_NAME_LEN) { + /* Make the change and update the database revision */ + memmove(pDev->bacObj.Object_Name, name, length); + pDev->bacObj.Object_Name[length] = 0; + Routed_Device_Inc_Database_Revision(); + status = true; + } + + return status; +} + +bool Routed_Device_Set_Description( + const char *name, + size_t length) +{ + bool status = false; /*return value */ + DEVICE_OBJECT_DATA *pDev = &Devices[iCurrent_Device_Idx]; + + if (length < MAX_DEV_DESC_LEN) { + memmove(pDev->Description, name, length); + pDev->Description[length] = 0; + status = true; + } + + return status; +} + + +/* + * Shortcut for incrementing database revision as this is potentially + * the most common operation if changing object names and ids is + * implemented. + */ +void Routed_Device_Inc_Database_Revision( + void) +{ + DEVICE_OBJECT_DATA *pDev = &Devices[iCurrent_Device_Idx]; + pDev->Database_Revision++; +} + + diff --git a/bacnet-stack/include/config.h b/bacnet-stack/include/config.h index 130e2bde..b23c719c 100644 --- a/bacnet-stack/include/config.h +++ b/bacnet-stack/include/config.h @@ -45,6 +45,19 @@ #endif #endif +/* Enable the Gateway (Routing) functionality here, if desired. */ +#if !defined(BAC_ROUTING) +#define BAC_ROUTING 1 +#endif +#if !defined(MAX_NUM_DEVICES) +#if BAC_ROUTING +#define MAX_NUM_DEVICES 3 /* Eg, Gateway + two remote devices */ +#else +#define MAX_NUM_DEVICES 1 /* Just the one normal BACnet Device Object */ +#endif +#endif + + /* Define your processor architecture as Big Endian (PowerPC,68K,Sparc) or Little Endian (Intel,AVR) ARM and MIPS can be either - what is your setup? */ diff --git a/bacnet-stack/include/device.h b/bacnet-stack/include/device.h index bb8acb4c..c070622d 100644 --- a/bacnet-stack/include/device.h +++ b/bacnet-stack/include/device.h @@ -101,6 +101,59 @@ typedef unsigned ( unsigned current_index); +/* 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; 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. + */ + char Object_Name[MAX_DEV_NAME_LEN]; + +} COMMON_BAC_OBJECT; + + +/** Structure to define the Properties of Device Objects which distinguish + * one instance from another. + * This structure only defines fields for properties that are unique to + * a given Device object. The rest may be fixed in device.c or hard-coded + * into the read-property encoding. + * This may be useful for implementations which manage multiple Devices, + * eg, a Gateway. + */ +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]; + + /** The upcounter that shows if the Device ID or object structure has changed. */ + uint32_t Database_Revision; +} DEVICE_OBJECT_DATA; + + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -224,7 +277,46 @@ extern "C" { BACNET_READ_RANGE_DATA * pRequest, /* Info on the request */ RR_PROP_INFO * pInfo); /* Where to put the information */ +/* Prototypes for Routing functionality in the Device Object. + * Enable by defining BAC_ROUTING in config.h and including gw_device.c + * in the build (lib/Makefile). + */ + void Routing_Device_Init( + uint32_t first_object_instance ); + uint16_t Add_Routed_Device( + uint32_t Object_Instance, + const char * Object_Name, + const char * Description ); + DEVICE_OBJECT_DATA * Get_Routed_Device_Object( + int idx ); + bool Lookup_Routed_Device_Address( + int idx, + uint8_t address_len, + uint8_t * mac_adress ); + + uint32_t Routed_Device_Index_To_Instance( + unsigned index); + bool Routed_Device_Valid_Object_Instance_Number( + uint32_t object_id); + char *Routed_Device_Name( + uint32_t object_instance); + uint32_t Routed_Device_Object_Instance_Number( + void); + bool Routed_Device_Set_Object_Instance_Number( + uint32_t object_id); + + bool Routed_Device_Set_Object_Name( + const char *name, + size_t length); + bool Routed_Device_Set_Description( + const char *name, + size_t length); + void Routed_Device_Inc_Database_Revision( + void); + + + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/bacnet-stack/include/objects.h b/bacnet-stack/include/objects.h index 622bf01d..e547f6df 100644 --- a/bacnet-stack/include/objects.h +++ b/bacnet-stack/include/objects.h @@ -84,4 +84,5 @@ typedef struct object_device_t { uint32_t Database_Revision; } OBJECT_DEVICE_T; + #endif diff --git a/bacnet-stack/lib/Makefile b/bacnet-stack/lib/Makefile index 6fa3ac3c..d5e57a57 100644 --- a/bacnet-stack/lib/Makefile +++ b/bacnet-stack/lib/Makefile @@ -15,6 +15,9 @@ BACNET_DEFINES=-DPRINT_ENABLED=1 -DBACAPP_ALL -DBACFILE #BACDL_DEFINE=-DBACDL_ARCNET=1 #BACDL_DEFINE=-DBACDL_MSTP=1 BACDL_DEFINE=-DBACDL_BIP=1 +# Enable (or else don't define it) Routing functionality +# (also enable BAC_ROUTING in config.h and "routes" in main Makefile) +BACROUTE_ENABLE=y DEFINES = $(BACNET_DEFINES) $(BACDL_DEFINE) # directories @@ -92,7 +95,6 @@ HANDLER_SRC = \ $(BACNET_HANDLER)/txbuf.c \ $(BACNET_HANDLER)/noserv.c \ $(BACNET_HANDLER)/h_npdu.c \ - $(BACNET_HANDLER)/h_routed_npdu.c \ $(BACNET_HANDLER)/h_whois.c \ $(BACNET_HANDLER)/h_iam.c \ $(BACNET_HANDLER)/h_rp.c \ @@ -128,7 +130,6 @@ HANDLER_SRC = \ $(BACNET_HANDLER)/s_uevent.c \ $(BACNET_HANDLER)/s_whohas.c \ $(BACNET_HANDLER)/s_whois.c \ - $(BACNET_HANDLER)/s_router.c \ $(BACNET_HANDLER)/s_upt.c \ $(BACNET_HANDLER)/s_wp.c @@ -192,7 +193,16 @@ ifdef BACDL_ALL PORT_SRC = ${PORT_ALL_SRC} endif -SRCS = ${CORE_SRC} ${PORT_SRC} ${HANDLER_SRC} ${OBJECT_SRC} +ifdef BACROUTE_ENABLE +ROUTING_SRC = \ + $(BACNET_HANDLER)/h_routed_npdu.c \ + $(BACNET_HANDLER)/s_router.c \ + $(BACNET_OBJECT)/gw_device.c +else +ROUTING_SRC = +endif + +SRCS = ${CORE_SRC} ${PORT_SRC} ${HANDLER_SRC} ${OBJECT_SRC} ${ROUTING_SRC} OBJS = ${SRCS:.c=.o}