diff --git a/ports/stm32f4xx/CMakeLists.txt b/ports/stm32f4xx/CMakeLists.txt new file mode 100644 index 00000000..4ad1e17b --- /dev/null +++ b/ports/stm32f4xx/CMakeLists.txt @@ -0,0 +1,257 @@ +# This is a CMake example for STM32F429ZI hardware on Nucleof429zi board +# using the ARM GCC compiler and STM32F4xx_StdPeriph_Driver library. +# +# Board Nucleof429zi +# MCU STM32F429ZI +# CPU Cortex-M4 +# Clock 180MHz +# RAM 256Kb +# Flash 2Mb +# +# To build this project you need to install: +# - ARM GCC compiler +# - CMake +# +# To build this project you need to run: +# - cmake -S . -B build +# - cmake --build build +# +# To flash this project you need to run: +# - st-flash write build/bacnet-mstp.hex 0x8000000 +# +# To debug this project you need to run: +# - arm-none-eabi-gdb -q build/bacnet-mstp.out +# - (gdb) target extended-remote localhost:3333 +# - (gdb) monitor reset halt +# - (gdb) load +# - (gdb) monitor reset halt +# - (gdb) monitor reset init +# - (gdb) monitor reset run +# - (gdb) monitor reset exit +# - (gdb) quit +# +# You can also use VSCode with Cortex-Debug extension +# +# This example was tested with: +# - ARM GCC 10.3.1 +# - CMake 3.22.1 +# - STM32F4xx_StdPeriph_Driver V1.0.0 +# - BACnet Stack V1.3.2 +# +cmake_minimum_required(VERSION 3.20) + +# Cross compilers and tools +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_C_COMPILER arm-none-eabi-gcc) +set(CMAKE_CXX_COMPILER arm-none-eabi-g++) +set(CMAKE_ASM_COMPILER arm-none-eabi-gcc) +set(CMAKE_AR arm-none-eabi-ar) +set(CMAKE_OBJCOPY arm-none-eabi-objcopy) +set(CMAKE_OBJDUMP arm-none-eabi-objdump) +set(SIZE arm-none-eabi-size) +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + +project(bacnet-mstp) + +enable_language(C ASM) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) + +# Specific ARM microcontroller compiler and linker settings +add_compile_options(-mcpu=cortex-m4) +add_compile_options(-mthumb -mthumb-interwork) +add_compile_options(-ffunction-sections -fdata-sections) +add_compile_options(-fno-common -fmessage-length=0) +add_link_options(-mcpu=cortex-m4) +add_link_options(-mthumb -mthumb-interwork) +add_link_options(-Wl,-gc-sections,--print-memory-usage) + +# Enable hardware FPU +add_compile_options(-mfloat-abi=hard -mfpu=fpv4-sp-d16) +add_link_options(-mfloat-abi=hard -mfpu=fpv4-sp-d16) +add_compile_definitions(ARM_MATH_CM4;ARM_MATH_MATRIX_CHECK;ARM_MATH_ROUNDING) + +# Build types +if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") + message(STATUS "Maximum optimization for speed") + add_compile_options(-Ofast) +elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") + message(STATUS "Maximum optimization for speed, debug info included") + add_compile_options(-Ofast -g) +elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "MinSizeRel") + message(STATUS "Maximum optimization for size") + add_compile_options(-Os) +else () + message(STATUS "Minimal optimization, debug info included") + add_compile_definitions(DEBUG) + add_compile_options(-Og -g3) +endif () + +# eliminate the deprecated function warnings +option(BACNET_STACK_DEPRECATED_DISABLE "Disable deprecation compile warnings" ON) +if(BACNET_STACK_DEPRECATED_DISABLE) + add_definitions(-DBACNET_STACK_DEPRECATED_DISABLE) +endif() + +set(LIBRARY_BACNET_INC "../../src") +set(LIBRARY_BACNET_CORE "../../src/bacnet") +set(LIBRARY_BACNET_BASIC "../../src/bacnet/basic") +set(LIBRARY_STM32_SRC "./STM32F4xx_StdPeriph_Driver/src") +set(LIBRARY_STM32_INC "./STM32F4xx_StdPeriph_Driver/inc") +set(LIBRARY_CMSIS_INC "./CMSIS") +set(LIBRARY_CMSIS_GCC_INC "./CMSIS/gcc_ride7") + +set(BACNET_PROJECT_SOURCE + ${LIBRARY_STM32_SRC}/stm32f4xx_adc.c + ${LIBRARY_STM32_SRC}/stm32f4xx_can.c + ${LIBRARY_STM32_SRC}/stm32f4xx_crc.c + ${LIBRARY_STM32_SRC}/stm32f4xx_dac.c + ${LIBRARY_STM32_SRC}/stm32f4xx_dbgmcu.c + ${LIBRARY_STM32_SRC}/stm32f4xx_dcmi.c + ${LIBRARY_STM32_SRC}/stm32f4xx_dma.c + ${LIBRARY_STM32_SRC}/stm32f4xx_exti.c + ${LIBRARY_STM32_SRC}/stm32f4xx_flash.c + ${LIBRARY_STM32_SRC}/stm32f4xx_fsmc.c + ${LIBRARY_STM32_SRC}/stm32f4xx_gpio.c + ${LIBRARY_STM32_SRC}/stm32f4xx_i2c.c + ${LIBRARY_STM32_SRC}/stm32f4xx_iwdg.c + ${LIBRARY_STM32_SRC}/stm32f4xx_misc.c + ${LIBRARY_STM32_SRC}/stm32f4xx_pwr.c + ${LIBRARY_STM32_SRC}/stm32f4xx_rcc.c + ${LIBRARY_STM32_SRC}/stm32f4xx_rng.c + ${LIBRARY_STM32_SRC}/stm32f4xx_rtc.c + ${LIBRARY_STM32_SRC}/stm32f4xx_sdio.c + ${LIBRARY_STM32_SRC}/stm32f4xx_spi.c + ${LIBRARY_STM32_SRC}/stm32f4xx_syscfg.c + ${LIBRARY_STM32_SRC}/stm32f4xx_tim.c + ${LIBRARY_STM32_SRC}/stm32f4xx_usart.c + ${LIBRARY_STM32_SRC}/stm32f4xx_wwdg.c + ${LIBRARY_STM32_SRC}/syscalls.c + + ${CMAKE_SOURCE_DIR}/stm32f4xx_conf.h + + ${CMAKE_SOURCE_DIR}/main.c + ${CMAKE_SOURCE_DIR}/stm32f4xx_it.c + ${CMAKE_SOURCE_DIR}/stm32f4xx_it.h + ${CMAKE_SOURCE_DIR}/system_stm32f4xx.c + ${CMAKE_SOURCE_DIR}/system_stm32f4xx.h + + ${CMAKE_SOURCE_DIR}/bacnet.c + ${CMAKE_SOURCE_DIR}/dlmstp.c + ${CMAKE_SOURCE_DIR}/led.c + ${CMAKE_SOURCE_DIR}/mstimer-init.c + ${CMAKE_SOURCE_DIR}/rs485.c + + ${CMAKE_SOURCE_DIR}/device.c + ${CMAKE_SOURCE_DIR}/netport.c + + ${LIBRARY_BACNET_BASIC}/service/h_dcc.c + ${LIBRARY_BACNET_BASIC}/service/h_apdu.c + ${LIBRARY_BACNET_BASIC}/npdu/h_npdu.c + ${LIBRARY_BACNET_BASIC}/service/h_rd.c + ${LIBRARY_BACNET_BASIC}/service/h_rp.c + ${LIBRARY_BACNET_BASIC}/service/h_rpm.c + ${LIBRARY_BACNET_BASIC}/service/h_whohas.c + ${LIBRARY_BACNET_BASIC}/service/h_whois.c + ${LIBRARY_BACNET_BASIC}/service/h_wp.c + ${LIBRARY_BACNET_BASIC}/service/h_noserv.c + ${LIBRARY_BACNET_BASIC}/service/s_iam.c + ${LIBRARY_BACNET_BASIC}/service/s_ihave.c + ${LIBRARY_BACNET_BASIC}/tsm/tsm.c + ${LIBRARY_BACNET_BASIC}/sys/debug.c + ${LIBRARY_BACNET_BASIC}/sys/ringbuf.c + ${LIBRARY_BACNET_BASIC}/sys/fifo.c + ${LIBRARY_BACNET_BASIC}/sys/mstimer.c + + ${LIBRARY_BACNET_CORE}/abort.c + ${LIBRARY_BACNET_CORE}/bacaddr.c + ${LIBRARY_BACNET_CORE}/bacapp.c + ${LIBRARY_BACNET_CORE}/bacdcode.c + ${LIBRARY_BACNET_CORE}/bacdest.c + ${LIBRARY_BACNET_CORE}/bacdevobjpropref.c + ${LIBRARY_BACNET_CORE}/bacerror.c + ${LIBRARY_BACNET_CORE}/bacint.c + ${LIBRARY_BACNET_CORE}/bacreal.c + ${LIBRARY_BACNET_CORE}/bacstr.c + ${LIBRARY_BACNET_CORE}/datalink/cobs.c + ${LIBRARY_BACNET_CORE}/datalink/crc.c + ${LIBRARY_BACNET_CORE}/datetime.c + ${LIBRARY_BACNET_CORE}/dcc.c + ${LIBRARY_BACNET_CORE}/iam.c + ${LIBRARY_BACNET_CORE}/ihave.c + ${LIBRARY_BACNET_CORE}/hostnport.c + ${LIBRARY_BACNET_CORE}/lighting.c + ${LIBRARY_BACNET_CORE}/memcopy.c + ${LIBRARY_BACNET_CORE}/npdu.c + ${LIBRARY_BACNET_CORE}/proplist.c + ${LIBRARY_BACNET_CORE}/rd.c + ${LIBRARY_BACNET_CORE}/reject.c + ${LIBRARY_BACNET_CORE}/rp.c + ${LIBRARY_BACNET_CORE}/rpm.c + ${LIBRARY_BACNET_CORE}/timestamp.c + ${LIBRARY_BACNET_CORE}/weeklyschedule.c + ${LIBRARY_BACNET_CORE}/dailyschedule.c + ${LIBRARY_BACNET_CORE}/bactimevalue.c + ${LIBRARY_BACNET_CORE}/whohas.c + ${LIBRARY_BACNET_CORE}/whois.c + ${LIBRARY_BACNET_CORE}/wp.c + + CMSIS/gcc_ride7/startup_stm32f4xx.s + CMSIS/stm32f4xx.h +) + +set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/stm32f4xx.ld) + +set(EXECUTABLE ${PROJECT_NAME}.out) + +add_executable(${EXECUTABLE} ${BACNET_PROJECT_SOURCE}) + +target_compile_definitions(${EXECUTABLE} PRIVATE + -DNDEBUG + -DUSE_STDPERIPH_DRIVER + -DSTM32F4XX + -DBACDL_MSTP + -DMAX_APDU=480 + -DBIG_ENDIAN=0 + -DMAX_TSM_TRANSACTIONS=1 +) + +# inhibit pedantic warnings +target_compile_options(${EXECUTABLE} PRIVATE + -Wall + -Wno-comment + -Wno-missing-braces + -Wno-unused-variable +) + +target_include_directories(${EXECUTABLE} PRIVATE + ${CMAKE_SOURCE_DIR} + ${LIBRARY_CMSIS_INC} + ${LIBRARY_CMSIS_GCC_INC} + ${LIBRARY_STM32_INC} + ${LIBRARY_BACNET_INC} +) + +target_link_options(${EXECUTABLE} PRIVATE + -T${LINKER_SCRIPT} + -specs=nano.specs + -lm + -lnosys + -Wl,-Map=${PROJECT_NAME}.map,--cref + -Wl,--gc-sections +) + +# Print executable size +add_custom_command(TARGET ${EXECUTABLE} + POST_BUILD + COMMAND arm-none-eabi-size ${EXECUTABLE} +) + +# Create hex file +add_custom_command(TARGET ${EXECUTABLE} + POST_BUILD + COMMAND arm-none-eabi-objcopy -O ihex ${EXECUTABLE} ${PROJECT_NAME}.hex + COMMAND arm-none-eabi-objcopy -O binary ${EXECUTABLE} ${PROJECT_NAME}.bin +) diff --git a/ports/stm32f4xx/device.c b/ports/stm32f4xx/device.c index 59e52ac6..c3626ab1 100644 --- a/ports/stm32f4xx/device.c +++ b/ports/stm32f4xx/device.c @@ -23,6 +23,9 @@ * *********************************************************************/ +/** @file device.c Base "class" for handling all BACnet objects belonging + * to a BACnet device, as well as Device-specific properties. */ + #include #include #include @@ -99,6 +102,14 @@ static const int Device_Properties_Optional[] = { PROP_DESCRIPTION, static const int Device_Properties_Proprietary[] = { -1 }; +/** 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 my_object_functions *Device_Objects_Find_Functions( BACNET_OBJECT_TYPE Object_Type) { @@ -117,138 +128,17 @@ static struct my_object_functions *Device_Objects_Find_Functions( return (NULL); } -static int Read_Property_Common( - struct my_object_functions *pObject, BACNET_READ_PROPERTY_DATA *rpdata) -{ - int apdu_len = BACNET_STATUS_ERROR; - BACNET_CHARACTER_STRING char_string; - uint8_t *apdu = NULL; -#if (BACNET_PROTOCOL_REVISION >= 14) - struct special_property_list_t property_list; -#endif - - if ((rpdata->application_data == NULL) || - (rpdata->application_data_len == 0)) { - return 0; - } - apdu = rpdata->application_data; - switch (rpdata->object_property) { - case PROP_OBJECT_IDENTIFIER: - /* only array properties can have array options */ - if (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; - } else { - /* Device Object exception: requested instance - may not match our instance if a wildcard */ - if (rpdata->object_type == OBJECT_DEVICE) { - rpdata->object_instance = Object_Instance_Number; - } - apdu_len = encode_application_object_id( - &apdu[0], rpdata->object_type, rpdata->object_instance); - } - break; - case PROP_OBJECT_NAME: - /* only array properties can have array options */ - if (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; - } else { - characterstring_init_ansi(&char_string, ""); - if (pObject->Object_Name) { - (void)pObject->Object_Name( - rpdata->object_instance, &char_string); - } - apdu_len = - encode_application_character_string(&apdu[0], &char_string); - } - break; - case PROP_OBJECT_TYPE: - /* only array properties can have array options */ - if (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; - } else { - apdu_len = encode_application_enumerated( - &apdu[0], rpdata->object_type); - } - break; -#if (BACNET_PROTOCOL_REVISION >= 14) - case PROP_PROPERTY_LIST: - Device_Objects_Property_List( - rpdata->object_type, rpdata->object_instance, &property_list); - apdu_len = property_list_encode(rpdata, - property_list.Required.pList, property_list.Optional.pList, - property_list.Proprietary.pList); - break; -#endif - default: - if (pObject->Object_Read_Property) { - apdu_len = pObject->Object_Read_Property(rpdata); - } - break; - } - - return apdu_len; -} - -/* Encodes the property APDU and returns the length, - or sets the error, and returns BACNET_STATUS_ERROR */ -int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) -{ - int apdu_len = BACNET_STATUS_ERROR; - struct my_object_functions *pObject = NULL; - - /* initialize the default return values */ - pObject = Device_Objects_Find_Functions(rpdata->object_type); - if (pObject) { - if (pObject->Object_Valid_Instance && - pObject->Object_Valid_Instance(rpdata->object_instance)) { - apdu_len = Read_Property_Common(pObject, 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_UNKNOWN_OBJECT; - } - - return apdu_len; -} - -bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) -{ - bool status = false; - struct my_object_functions *pObject = NULL; - - /* initialize the default return values */ - pObject = Device_Objects_Find_Functions(wp_data->object_type); - if (pObject) { - 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_UNKNOWN_OBJECT; - } - - return status; -} - -/* for a given object type, returns the special property list */ +/** 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, uint32_t object_instance, struct special_property_list_t *pPropertyList) @@ -290,52 +180,19 @@ void Device_Objects_Property_List(BACNET_OBJECT_TYPE object_type, void Device_Property_Lists( const int **pRequired, const int **pOptional, const int **pProprietary) { - if (pRequired) + if (pRequired) { *pRequired = Device_Properties_Required; - if (pOptional) + } + if (pOptional) { *pOptional = Device_Properties_Optional; - if (pProprietary) + } + if (pProprietary) { *pProprietary = Device_Properties_Proprietary; + } return; } -unsigned Device_Count(void) -{ - return 1; -} - -uint32_t Device_Index_To_Instance(unsigned index) -{ - (void)index; - return Object_Instance_Number; -} - -bool Device_Object_Name( - uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) -{ - bool status = false; - - if (object_instance == Object_Instance_Number) { - status = characterstring_copy(object_name, &My_Object_Name); - } - - return status; -} - -bool Device_Set_Object_Name(BACNET_CHARACTER_STRING *object_name) -{ - bool status = false; /*return value */ - - if (!characterstring_same(&My_Object_Name, object_name)) { - /* Make the change and update the database revision */ - status = characterstring_copy(&My_Object_Name, object_name); - Device_Inc_Database_Revision(); - } - - return status; -} - /** * @brief Sets the ReinitializeDevice password * @@ -435,30 +292,50 @@ BACNET_REINITIALIZED_STATE Device_Reinitialized_State(void) return Reinitialize_State; } -void Device_Init(object_functions_t *object_table) +unsigned Device_Count(void) { - struct my_object_functions *pObject = NULL; + return 1; +} - /* we don't use the object table passed in - since there is extra stuff we don't need in there. */ - (void)object_table; - /* our local object table */ - pObject = &Object_Table[0]; - while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { - if (pObject->Object_Init) { - pObject->Object_Init(); - } - pObject++; +uint32_t Device_Index_To_Instance(unsigned index) +{ + (void)index; + return Object_Instance_Number; +} + +bool Device_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) +{ + bool status = false; + + if (object_instance == Object_Instance_Number) { + status = characterstring_copy(object_name, &My_Object_Name); } - dcc_set_status_duration(COMMUNICATION_ENABLE, 0); - if (Object_Instance_Number >= BACNET_MAX_INSTANCE) { - Object_Instance_Number = 103; - srand(Object_Instance_Number); + + return status; +} + +bool Device_Set_Object_Name(BACNET_CHARACTER_STRING *object_name) +{ + bool status = false; /*return value */ + + if (!characterstring_same(&My_Object_Name, object_name)) { + /* Make the change and update the database revision */ + status = characterstring_copy(&My_Object_Name, object_name); + Device_Inc_Database_Revision(); } - characterstring_init_ansi(&My_Object_Name, Device_Name_Default); + + 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 Device_Object_Instance_Number(void) { return Object_Instance_Number; @@ -515,13 +392,21 @@ uint32_t Device_Database_Revision(void) return Database_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++; } -/* Since many network clients depend on the object list */ -/* for discovery, it must be consistent! */ +/** 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 */ @@ -539,6 +424,16 @@ unsigned Device_Object_List_Count(void) 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( uint32_t array_index, BACNET_OBJECT_TYPE *object_type, uint32_t *instance) { @@ -604,6 +499,15 @@ int Device_Object_List_Element_Encode( return apdu_len; } +/** 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(BACNET_CHARACTER_STRING *object_name1, BACNET_OBJECT_TYPE *object_type, uint32_t *object_instance) @@ -639,6 +543,11 @@ bool Device_Valid_Object_Name(BACNET_CHARACTER_STRING *object_name1, 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 True if found, else False if no such Object in this device. + */ bool Device_Valid_Object_Id( BACNET_OBJECT_TYPE object_type, uint32_t object_instance) { @@ -653,6 +562,12 @@ bool Device_Valid_Object_Id( return status; } +/** Copy a child object's object_name value, given its ID. + * @param object_type [in] The BACNET_OBJECT_TYPE of the child Object. + * @param object_instance [in] The object instance number of the child Object. + * @param object_name [out] The Object Name found for this child Object. + * @return True on success or else False if not found. + */ bool Device_Object_Name_Copy(BACNET_OBJECT_TYPE object_type, uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) @@ -668,19 +583,20 @@ bool Device_Object_Name_Copy(BACNET_OBJECT_TYPE object_type, return found; } -/* return the length of the apdu encoded or BACNET_STATUS_ERROR for error */ +/* return the length of the apdu encoded or BACNET_STATUS_ERROR for error or + BACNET_STATUS_ABORT for abort message */ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) { int apdu_len = 0; /* return value */ - BACNET_BIT_STRING bit_string; - BACNET_CHARACTER_STRING char_string; + BACNET_BIT_STRING bit_string = { 0 }; + BACNET_CHARACTER_STRING char_string = { 0 }; uint32_t i = 0; uint32_t count = 0; uint8_t *apdu = NULL; - int apdu_max = 0; struct my_object_functions *pObject = NULL; + uint16_t apdu_max = 0; - if ((rpdata->application_data == NULL) || + if ((rpdata == NULL) || (rpdata->application_data == NULL) || (rpdata->application_data_len == 0)) { return 0; } @@ -751,8 +667,7 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) bitstring_set_bit(&bit_string, (uint8_t)i, false); } /* set the object types with objects to supported */ - i = 0; - pObject = &Object_Table[i]; + pObject = Object_Table; 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); @@ -819,6 +734,126 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) return apdu_len; } +/** Looks up the common Object and Property, and encodes its Value in an + * APDU. Sets the error class and code if request is not appropriate. + * @param pObject - object table + * @param rpdata [in,out] Structure with the requested Object & Property info + * on entry, and APDU message on return. + * @return The length of the APDU on success, else BACNET_STATUS_ERROR + */ +static int Read_Property_Common( + struct my_object_functions *pObject, BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = BACNET_STATUS_ERROR; + BACNET_CHARACTER_STRING char_string; + uint8_t *apdu = NULL; +#if (BACNET_PROTOCOL_REVISION >= 14) + struct special_property_list_t property_list; +#endif + + if ((rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + apdu = rpdata->application_data; + switch (rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + /* only array properties can have array options */ + if (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; + } else { + /* Device Object exception: requested instance + may not match our instance if a wildcard */ + if (rpdata->object_type == OBJECT_DEVICE) { + rpdata->object_instance = Object_Instance_Number; + } + apdu_len = encode_application_object_id( + &apdu[0], rpdata->object_type, rpdata->object_instance); + } + break; + case PROP_OBJECT_NAME: + /* only array properties can have array options */ + if (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; + } else { + characterstring_init_ansi(&char_string, ""); + if (pObject->Object_Name) { + (void)pObject->Object_Name( + rpdata->object_instance, &char_string); + } + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + } + break; + case PROP_OBJECT_TYPE: + /* only array properties can have array options */ + if (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; + } else { + apdu_len = encode_application_enumerated( + &apdu[0], rpdata->object_type); + } + break; +#if (BACNET_PROTOCOL_REVISION >= 14) + case PROP_PROPERTY_LIST: + Device_Objects_Property_List( + rpdata->object_type, rpdata->object_instance, &property_list); + apdu_len = property_list_encode(rpdata, + property_list.Required.pList, property_list.Optional.pList, + property_list.Proprietary.pList); + break; +#endif + default: + if (pObject->Object_Read_Property) { + apdu_len = pObject->Object_Read_Property(rpdata); + } + break; + } + + 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 my_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) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(rpdata->object_instance)) { + apdu_len = Read_Property_Common(pObject, 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_UNKNOWN_OBJECT; + } + + return apdu_len; +} + +/* returns true if successful */ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) { bool status = false; /* return value - false=error */ @@ -953,3 +988,152 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) return status; } + +/** + * @brief Handles the writing of the object name property + * @param wp_data [in,out] WriteProperty data structure + * @param Object_Write_Property object specific function to write the property + * @return True on success, else False if there is an error. + */ +static bool Device_Write_Property_Object_Name( + BACNET_WRITE_PROPERTY_DATA *wp_data, + write_property_function Object_Write_Property) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_CHARACTER_STRING value; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t object_instance = 0; + int apdu_size = 0; + uint8_t *apdu = NULL; + + if (!wp_data) { + return false; + } + if (wp_data->array_index != BACNET_ARRAY_ALL) { + /* only array properties can have array options */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + return false; + } + apdu = wp_data->application_data; + apdu_size = wp_data->application_data_len; + len = bacnet_character_string_application_decode(apdu, apdu_size, &value); + if (len > 0) { + if ((characterstring_encoding(&value) != CHARACTER_ANSI_X34) || + (characterstring_length(&value) == 0) || + (!characterstring_printable(&value))) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } else { + status = true; + } + } else if (len == 0) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + if (status) { + /* All the object names in a device must be unique */ + if (Device_Valid_Object_Name(&value, &object_type, &object_instance)) { + if ((object_type == wp_data->object_type) && + (object_instance == wp_data->object_instance)) { + /* writing same name to same object */ + status = true; + } else { + /* name already exists in some object */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_DUPLICATE_NAME; + status = false; + } + } else { + status = Object_Write_Property(wp_data); + } + } + + 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; + struct my_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) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(wp_data->object_instance)) { + if (pObject->Object_Write_Property) { +#if (BACNET_PROTOCOL_REVISION >= 14) + if (wp_data->object_property == PROP_PROPERTY_LIST) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + return status; + } +#endif + if (wp_data->object_property == PROP_OBJECT_NAME) { + status = Device_Write_Property_Object_Name( + wp_data, pObject->Object_Write_Property); + } else { + 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_UNKNOWN_OBJECT; + } + + return status; +} + +/** Initialize the Device Object. + Initialize the group of object helper functions for any supported Object. + Initialize each of the Device Object child Object instances. + * @ingroup ObjIntf + * @param object_table [in,out] array of structure with object functions. + * Each Child Object must provide some implementation of each of these + * functions in order to properly support the default handlers. + */ +void Device_Init(object_functions_t *object_table) +{ + struct my_object_functions *pObject = NULL; + + /* we don't use the object table passed in + since there is extra stuff we don't need in there. */ + (void)object_table; + /* our local object table */ + pObject = &Object_Table[0]; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Init) { + pObject->Object_Init(); + } + pObject++; + } + dcc_set_status_duration(COMMUNICATION_ENABLE, 0); + if (Object_Instance_Number >= BACNET_MAX_INSTANCE) { + Object_Instance_Number = 103; + srand(Object_Instance_Number); + } + characterstring_init_ansi(&My_Object_Name, Device_Name_Default); +} diff --git a/ports/stm32f4xx/main.c b/ports/stm32f4xx/main.c index a24f7c88..d10f060b 100644 --- a/ports/stm32f4xx/main.c +++ b/ports/stm32f4xx/main.c @@ -34,6 +34,12 @@ #include "led.h" #include "bacnet.h" +int __io_putchar(int ch) +{ + (void)ch; + return 0; +} + int main(void) { struct mstimer Blink_Timer;