diff --git a/bacnet-stack/Makefile b/bacnet-stack/Makefile index cbef375b..88fa2832 100644 --- a/bacnet-stack/Makefile +++ b/bacnet-stack/Makefile @@ -15,6 +15,7 @@ SRCS = ports/linux/main.c \ whois.c \ iam.c \ rp.c \ + wp.c \ device.c \ ai.c \ abort.c \ diff --git a/bacnet-stack/ai.c b/bacnet-stack/ai.c index 8c7a973b..64f99da8 100644 --- a/bacnet-stack/ai.c +++ b/bacnet-stack/ai.c @@ -33,6 +33,29 @@ #include "bacenum.h" #include "config.h" // the custom stuff +#define MAX_ANALOG_INPUTS 32 + +bool Analog_Input_Valid_Instance(uint32_t object_instance) +{ + if (object_instance < MAX_ANALOG_INPUTS) + return true; + + return false; +} + +unsigned Analog_Input_Count(void) +{ + return MAX_ANALOG_INPUTS; +} + +// we simply have 0-n object instances. Yours might be +// more complex, and then you need to return the instance +// that correlates to the correct index +uint32_t Analog_Input_Index_To_Instance(unsigned index) +{ + return index; +} + int Analog_Input_Encode_Property_APDU( uint8_t *apdu, uint32_t object_instance, diff --git a/bacnet-stack/ai.h b/bacnet-stack/ai.h index eec25909..2fd4493c 100644 --- a/bacnet-stack/ai.h +++ b/bacnet-stack/ai.h @@ -29,6 +29,10 @@ #include #include "bacdef.h" +bool Analog_Input_Valid_Instance(uint32_t object_instance); +unsigned Analog_Input_Count(void); +uint32_t Analog_Input_Index_To_Instance(unsigned index); + int Analog_Input_Encode_Property_APDU( uint8_t *apdu, uint32_t object_instance, diff --git a/bacnet-stack/bacdcode.c b/bacnet-stack/bacdcode.c index 3341bce3..feed164f 100644 --- a/bacnet-stack/bacdcode.c +++ b/bacnet-stack/bacdcode.c @@ -785,8 +785,8 @@ int decode_object_id( int len = 0; len = decode_unsigned32(apdu,&value); - *object_type = ((value >> 22) & 0x3FF); - *instance = (value & 0x3FFFFF); + *object_type = ((value >> BACNET_INSTANCE_BITS) & BACNET_MAX_OBJECT); + *instance = (value & BACNET_MAX_INSTANCE); return len; } @@ -803,7 +803,8 @@ int encode_bacnet_object_id( int len = 0; type = object_type; - value = ((type & 0x3FF) << 22) | (instance & 0x3FFFFF); + value = ((type & BACNET_MAX_OBJECT) << BACNET_INSTANCE_BITS) | + (instance & BACNET_MAX_INSTANCE); len = encode_unsigned32(apdu,value); return len; @@ -1239,6 +1240,17 @@ int decode_date(uint8_t * apdu, int *year, int *month, int *day, int *wday) return 4; } +// returns the number of apdu bytes consumed +int encode_simple_ack(uint8_t * apdu, uint8_t invoke_id, + uint8_t service_choice) +{ + apdu[0] = PDU_TYPE_SIMPLE_ACK; + apdu[1] = invoke_id; + apdu[2] = service_choice; + + return 3; +} + /* end of decoding_encoding.c */ #ifdef TEST #include diff --git a/bacnet-stack/bacdcode.h b/bacnet-stack/bacdcode.h index d1db1bab..4a6f9e63 100644 --- a/bacnet-stack/bacdcode.h +++ b/bacnet-stack/bacdcode.h @@ -176,4 +176,8 @@ uint8_t encode_max_segs_max_apdu(int max_segs, int max_apdu); int decode_max_segs(uint8_t octet); int decode_max_apdu(uint8_t octet); +// returns the number of apdu bytes consumed +int encode_simple_ack(uint8_t * apdu, uint8_t invoke_id, + uint8_t service_choice); + #endif diff --git a/bacnet-stack/bacdef.h b/bacnet-stack/bacdef.h index f1c72f94..1ca3a68f 100644 --- a/bacnet-stack/bacdef.h +++ b/bacnet-stack/bacdef.h @@ -41,9 +41,11 @@ // largest BACnet Instance Number // Also used as a device instance number wildcard address #define BACNET_MAX_INSTANCE (0x3FFFFF) +#define BACNET_INSTANCE_BITS 22 // large BACnet Object Type #define BACNET_MAX_OBJECT (0x3FF) // Array index 0=size of array, n=array element n, MAX=all array elements +#define BACNET_ARRAY_LENGTH_INDEX 0 #define BACNET_ARRAY_ALL (~0) // embedded systems need fixed name sizes diff --git a/bacnet-stack/config.h b/bacnet-stack/config.h index 30e6bcfb..2f847270 100644 --- a/bacnet-stack/config.h +++ b/bacnet-stack/config.h @@ -20,6 +20,4 @@ // requests available. #define MAX_TSM_TRANSACTIONS 16 -#define MAX_ANALOG_INPUTS 32 - #endif diff --git a/bacnet-stack/device.c b/bacnet-stack/device.c index 2b36e882..dfc379ee 100644 --- a/bacnet-stack/device.c +++ b/bacnet-stack/device.c @@ -29,6 +29,8 @@ #include "bacdcode.h" #include "bacenum.h" #include "config.h" // the custom stuff +#include "ai.h" // object list dependency +#include "wp.h" // write property handling static uint32_t Object_Instance_Number = 0; // FIXME: it is likely that this name is configurable, @@ -88,6 +90,13 @@ void Device_Set_Object_Instance_Number(uint32_t object_id) Object_Instance_Number = object_id; } +bool Device_Valid_Object_Instance_Number(uint32_t object_id) +{ + // BACnet allows for a wildcard instance number + return ((Object_Instance_Number == object_id) || + (object_id == BACNET_MAX_INSTANCE)); +} + BACNET_DEVICE_STATUS Device_System_Status(void) { return System_Status; @@ -215,6 +224,52 @@ void Device_Set_Database_Revision(uint8_t revision) Database_Revision = revision; } +// Since many network clients depend on the object list +// for discovery, it must be consistent! +unsigned Device_Object_List_Count(void) +{ + unsigned count = 1; + + count += Analog_Input_Count(); + + return count; +} + +bool Device_Object_List_Identifier(unsigned array_index, + int *object_type, + uint32_t *instance) +{ + bool status = false; + unsigned object_index = 0; + + if (array_index == 1) + { + *object_type = OBJECT_DEVICE; + *instance = Object_Instance_Number; + status = true; + } + + if (!status) + { + // array index starts at 1, and 1 for the device object + object_index = array_index - 2; + if (object_index < Analog_Input_Count()) + { + *object_type = OBJECT_ANALOG_INPUT; + *instance = Analog_Input_Index_To_Instance(object_index); + status = true; + } + } + + // etc. + if (!status) + { + object_index -= Analog_Input_Count(); + } + + return status; +} + int Device_Encode_Property_APDU( uint8_t *apdu, BACNET_PROPERTY_ID property, @@ -224,6 +279,9 @@ int Device_Encode_Property_APDU( int len = 0; // apdu len intermediate value BACNET_BIT_STRING bit_string; int i = 0; + int object_type = 0; + uint32_t instance = 0; + unsigned count = 0; switch (property) { @@ -313,47 +371,49 @@ int Device_Encode_Property_APDU( apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); break; case PROP_OBJECT_LIST: - // FIXME: hook into real object list, not just device + count = Device_Object_List_Count(); // Array element zero is the number of objects in the list - if (array_index == 0) - apdu_len = encode_tagged_unsigned(&apdu[0], 1 + MAX_ANALOG_INPUTS); + if (array_index == BACNET_ARRAY_LENGTH_INDEX) + apdu_len = encode_tagged_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 (array_index == BACNET_ARRAY_ALL) { - len = encode_tagged_object_id(&apdu[0], OBJECT_DEVICE, - Object_Instance_Number); - apdu_len = len; - for (i = 0; i < MAX_ANALOG_INPUTS; i++) + for (i = 1; i <= count; i++) { - // assume next one is the same size as this one - // can we all fit into the APDU? - if ((apdu_len + len) >= MAX_APDU) + if (Device_Object_List_Identifier(i,&object_type,&instance)) { + len = encode_tagged_object_id(&apdu[0], 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) + { + // ERROR_CLASS_SERVICES + // ERROR_CODE_NO_SPACE_FOR_OBJECT + apdu_len = 0; + break; + } + } + else + { + // error: internal error? apdu_len = 0; break; } - len = encode_tagged_object_id(&apdu[apdu_len], - OBJECT_ANALOG_INPUT, i); - apdu_len += len; } } else { - // the first object in the list is at index=1 - if (array_index == 1) - apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_DEVICE, - Object_Instance_Number); - else if (array_index < (2 + MAX_ANALOG_INPUTS)) - { - apdu_len = encode_tagged_object_id(&apdu[0], - OBJECT_ANALOG_INPUT, array_index - 2); - } + if (Device_Object_List_Identifier(array_index,&object_type,&instance)) + apdu_len = encode_tagged_object_id(&apdu[0], object_type, instance); else { - // FIXME: handle case where index is beyond our bounds + //ERROR_CLASS_PROPERTY + //ERROR_CODE_INVALID_ARRAY_INDEX } } break; @@ -378,6 +438,73 @@ int Device_Encode_Property_APDU( return apdu_len; } +// we can send an I-Am when our device ID changes +extern bool I_Am_Request; + +// returns true if successful +bool Device_Write_Property( + BACNET_WRITE_PROPERTY_DATA *wp_data, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; // return value + uint8_t *apdu = NULL; + int len = 0; + int tag_len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + int object_type = 0; + uint32_t instance = 0; + + if (!Device_Valid_Object_Instance_Number(wp_data->object_instance)) + { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + + // decode the some of the request + apdu = wp_data->property_value; + tag_len = decode_tag_number_and_value(&apdu[0], + &tag_number, &len_value_type); + switch (wp_data->object_property) + { + case PROP_OBJECT_IDENTIFIER: + if (tag_len && (tag_number == BACNET_APPLICATION_TAG_OBJECT_ID)) + { + // FIXME: are we exceeding the property_value_len? + len = decode_object_id(&apdu[tag_len], + &object_type, + &instance); + if (len && + (object_type == OBJECT_DEVICE) && + (instance <= BACNET_MAX_INSTANCE)) + { + Device_Set_Object_Instance_Number(instance); + I_Am_Request = true; + status = true; + } + else + { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + else + { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + #ifdef TEST #include #include diff --git a/bacnet-stack/device.h b/bacnet-stack/device.h index 778158c8..faa23471 100644 --- a/bacnet-stack/device.h +++ b/bacnet-stack/device.h @@ -38,9 +38,15 @@ #include #include "bacdef.h" #include "bacenum.h" +#include "wp.h" uint32_t Device_Object_Instance_Number(void); void 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); BACNET_DEVICE_STATUS Device_System_Status(void); void Device_Set_System_Status(BACNET_DEVICE_STATUS status); @@ -83,5 +89,10 @@ int Device_Encode_Property_APDU( BACNET_PROPERTY_ID property, int32_t array_index); +bool Device_Write_Property( + BACNET_WRITE_PROPERTY_DATA *wp_data, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code); + #endif diff --git a/bacnet-stack/ports/linux/main.c b/bacnet-stack/ports/linux/main.c index 25a61e92..39979f13 100644 --- a/bacnet-stack/ports/linux/main.c +++ b/bacnet-stack/ports/linux/main.c @@ -1,19 +1,40 @@ -// This example file is not copyrighted so that you can use it as you wish. -// Written by Steve Karg - 2005 - skarg@users.sourceforge.net -// Bug fixes, feature requests, and suggestions are welcome - -// This is one way to use the embedded BACnet stack under Linux +/************************************************************************** +* +* 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. +* +*********************************************************************/ #include #include #include #include "config.h" #include "bacdef.h" +#include "bacdcode.h" #include "npdu.h" #include "apdu.h" #include "device.h" #include "ai.h" #include "rp.h" +#include "wp.h" #include "iam.h" #include "whois.h" #include "reject.h" @@ -259,7 +280,7 @@ void ReadPropertyHandler( } break; case OBJECT_ANALOG_INPUT: - if (object_instance < MAX_ANALOG_INPUTS) + if (Analog_Input_Valid_Instance(object_instance)) { len = Analog_Input_Encode_Property_APDU( &Temp_Buf[0], @@ -330,6 +351,121 @@ void ReadPropertyHandler( return; } +void WritePropertyHandler( + uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src, + BACNET_CONFIRMED_SERVICE_DATA *service_data) +{ + BACNET_WRITE_PROPERTY_DATA wp_data; + int len = 0; + int pdu_len = 0; + BACNET_ADDRESS my_address; + bool send = false; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + // decode the service request only + len = wp_decode_service_request( + service_request, + service_len, + &wp_data); + fprintf(stderr,"Received Write-Property Request!\n"); + if (len > 0) + fprintf(stderr,"type=%u instance=%u property=%u index=%d\n", + wp_data.object_type, + wp_data.object_instance, + wp_data.object_property, + wp_data.array_index); + else + fprintf(stderr,"Unable to decode Write-Property Request!\n"); + // prepare a reply + ethernet_get_my_address(&my_address); + // encode the NPDU portion of the packet + pdu_len = npdu_encode_apdu( + &Tx_Buf[0], + src, + &my_address, + false, // true for confirmed messages + MESSAGE_PRIORITY_NORMAL); + // bad decoding - send an abort + if (len == -1) + { + pdu_len += abort_encode_apdu( + &Tx_Buf[pdu_len], + service_data->invoke_id, + ABORT_REASON_OTHER); + fprintf(stderr,"Sent Abort!\n"); + send = true; + } + else if (service_data->segmented_message) + { + pdu_len += abort_encode_apdu( + &Tx_Buf[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED); + fprintf(stderr,"Sent Abort!\n"); + send = true; + } + else + { + switch (wp_data.object_type) + { + case OBJECT_DEVICE: + if (Device_Write_Property(&wp_data,&error_class,&error_code)) + { + pdu_len = encode_simple_ack( + &Tx_Buf[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY); + fprintf(stderr,"Sent Write Property Simple Ack!\n"); + send = true; + } + else + { + pdu_len += bacerror_encode_apdu( + &Tx_Buf[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, + error_class, + error_code); + fprintf(stderr,"Sent Write Property Error!\n"); + send = true; + } + break; + case OBJECT_ANALOG_INPUT: + pdu_len += bacerror_encode_apdu( + &Tx_Buf[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, + ERROR_CLASS_PROPERTY, + ERROR_CODE_WRITE_ACCESS_DENIED); + fprintf(stderr,"Sent Write Access Error!\n"); + send = true; + break; + default: + pdu_len += bacerror_encode_apdu( + &Tx_Buf[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, + ERROR_CLASS_OBJECT, + ERROR_CODE_UNKNOWN_OBJECT); + fprintf(stderr,"Sent Unknown Object Error!\n"); + send = true; + break; + } + } + if (send) + { + (void)ethernet_send_pdu( + src, // destination address + &Tx_Buf[0], + pdu_len); // number of bytes of data + } + + return; +} + static void Init_Device_Parameters(void) { // configure my initial values @@ -362,6 +498,9 @@ static void Init_Service_Handlers(void) apdu_set_confirmed_handler( SERVICE_CONFIRMED_READ_PROPERTY, ReadPropertyHandler); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_WRITE_PROPERTY, + WritePropertyHandler); } int main(int argc, char *argv[]) diff --git a/bacnet-stack/wp.c b/bacnet-stack/wp.c index f6e58a24..c684cff2 100644 --- a/bacnet-stack/wp.c +++ b/bacnet-stack/wp.c @@ -124,16 +124,10 @@ int wp_decode_service_request( if (decode_is_opening_tag_number(&apdu[len], 3)) { // a tag number of 3 is not extended so only one octet - len++; - // don't decode the application data here + len++; + // don't decode the property value here data->property_value = &apdu[len]; - // get the length of the property data - len += decode_tag_number_and_value(&apdu[len], - &tag_number, &len_value_type); - data->property_value_len = len_value_type; - // FIXME: is this the length, value or type? - // FIXME: If not len, don't increment - len += len_value_type; + data->property_value_len = apdu_len - len; } else return -1; @@ -141,17 +135,6 @@ int wp_decode_service_request( // FIXME: if the property value is not easily sized here, // then just have the application decode the property value // and the priority - if (len < apdu_len) - { - len += decode_tag_number_and_value(&apdu[len],&tag_number, - &len_value_type); - if (tag_number == 4) - { - len += decode_unsigned(&apdu[len], len_value_type, - &unsigned_value); - data->priority = (uint8_t)unsigned_value; - } - } } return len; @@ -203,7 +186,7 @@ void testWriteProperty(Test * pTest) BACNET_WRITE_PROPERTY_DATA data = {0}; BACNET_WRITE_PROPERTY_DATA test_data = {0}; - // FIXME: set values for data + // FIXME: test values for data len = wp_encode_apdu( &apdu[0], invoke_id,