From fcded70c49821df73de7988563a64a84461a23c6 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Wed, 22 Nov 2017 08:28:36 +0100 Subject: [PATCH] [r3164] esp32 support --- ports/esp32/lib/readme.txt | 36 + ports/esp32/platformio.ini | 19 + ports/esp32/readme.txt | 66 ++ ports/esp32/src/ai.c | 1472 +++++++++++++++++++++++++ ports/esp32/src/ai.h | 174 +++ ports/esp32/src/bip_init.c | 57 + ports/esp32/src/bo.c | 556 ++++++++++ ports/esp32/src/bo.h | 141 +++ ports/esp32/src/device.c | 2020 +++++++++++++++++++++++++++++++++++ ports/esp32/src/device.h | 476 +++++++++ ports/esp32/src/main.c | 215 ++++ ports/esp32/src/sdkconfig.h | 125 +++ 12 files changed, 5357 insertions(+) create mode 100644 ports/esp32/lib/readme.txt create mode 100644 ports/esp32/platformio.ini create mode 100644 ports/esp32/readme.txt create mode 100644 ports/esp32/src/ai.c create mode 100644 ports/esp32/src/ai.h create mode 100644 ports/esp32/src/bip_init.c create mode 100644 ports/esp32/src/bo.c create mode 100644 ports/esp32/src/bo.h create mode 100644 ports/esp32/src/device.c create mode 100644 ports/esp32/src/device.h create mode 100644 ports/esp32/src/main.c create mode 100644 ports/esp32/src/sdkconfig.h diff --git a/ports/esp32/lib/readme.txt b/ports/esp32/lib/readme.txt new file mode 100644 index 0000000..9c196e2 --- /dev/null +++ b/ports/esp32/lib/readme.txt @@ -0,0 +1,36 @@ + +This directory is intended for the project specific (private) libraries. +PlatformIO will compile them to static libraries and link to executable file. + +The source code of each library should be placed in separate directory, like +"lib/private_lib/[here are source files]". + +For example, see how can be organized `Foo` and `Bar` libraries: + +|--lib +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| |--Foo +| | |- Foo.c +| | |- Foo.h +| |- readme.txt --> THIS FILE +|- platformio.ini +|--src + |- main.c + +Then in `src/main.c` you should use: + +#include +#include + +// rest H/C/CPP code + +PlatformIO will find your libraries automatically, configure preprocessor's +include paths and build them. + +More information about PlatformIO Library Dependency Finder +- http://docs.platformio.org/page/librarymanager/ldf.html diff --git a/ports/esp32/platformio.ini b/ports/esp32/platformio.ini new file mode 100644 index 0000000..79f34df --- /dev/null +++ b/ports/esp32/platformio.ini @@ -0,0 +1,19 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + +[env:esp32thing] +platform = espressif32 +board = esp32thing +framework = espidf + +upload_port = COM7 +upload_speed = 1152000 + +monitor_baud = 115200 \ No newline at end of file diff --git a/ports/esp32/readme.txt b/ports/esp32/readme.txt new file mode 100644 index 0000000..3023acb --- /dev/null +++ b/ports/esp32/readme.txt @@ -0,0 +1,66 @@ +Bacnet Server for Espressif ESP32 + Steve Karg Bacnet stack using PlatformIO open source ecosystem for IoT development on VSCode or Atom + F. Chaxel 2017 + +TODO list : + +(Install VSCode or Atom and add the PlatformIO extension) + +Edit platformio.ini to adjust board, Com Port, ... + +Goto lib/stack and copy the requested files from Steve code : + + all .h from include directory (not all required by it's simple) + + these .c files from src or demo/handlers + abort.c + address.c + apdu.c + bacaddr.c + bacapp.c + bacdcode.c + bacerror.c + bacint.c + bacreal.c + bacstr.c + bip.c + bvlc.c + cov.c + datetime.c + bacdevobjpropref.c + dcc.c + debug.c + h_cov.c + h_ucov.c + h_npdu.c + h_rp.c + h_rpm.c + h_whois.c + h_wp.c + iam.c + lighting.c + memcopy.c + noserv.c + npdu.c + proplist.c + reject.c + rp.c + rpm.c + s_iam.c + tsm.c + whois.c + wp.c + +Modify + in config.h + MAX_TSM_TRANSACTIONS 255, set the value to 10 for instances + in main.c + wifi_config to fit your wifi network + BACNET_LED 5, set another IO number depending of your board + +A lot of Warning will be issued at compile time due to the redefinition of BIT macros. +Could be removes by placing a #ifndef #BIT0 .. #endif arround the BIT macro in bits.h, +and moving to the top of include list + #include "datalink.h" in tsm.c, s_iam and in device.c + #include "net.h" in bip.c and in bip.h (redondant include in bip.c) + #include "bvlc.h" in bvlc.c \ No newline at end of file diff --git a/ports/esp32/src/ai.c b/ports/esp32/src/ai.c new file mode 100644 index 0000000..b861831 --- /dev/null +++ b/ports/esp32/src/ai.c @@ -0,0 +1,1472 @@ +/************************************************************************** +* +* Copyright (C) 2005 Steve Karg +* Copyright (C) 2011 Krzysztof Malorny +* +* 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. +* +*********************************************************************/ + +/* Analog Input Objects customize for your use */ + +#include +#include +#include + +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bactext.h" +#include "config.h" /* the custom stuff */ +#include "device.h" +#include "handlers.h" +#include "proplist.h" +#include "timestamp.h" +#include "ai.h" + + +#ifndef MAX_ANALOG_INPUTS +#define MAX_ANALOG_INPUTS 2 +#endif + + +ANALOG_INPUT_DESCR AI_Descr[MAX_ANALOG_INPUTS]; + +/* These three arrays are used by the ReadPropertyMultiple handler */ +static const int Properties_Required[] = { + PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, + PROP_OBJECT_TYPE, + PROP_PRESENT_VALUE, + PROP_STATUS_FLAGS, + PROP_EVENT_STATE, + PROP_OUT_OF_SERVICE, + PROP_UNITS, + -1 +}; + +static const int Properties_Optional[] = { + PROP_DESCRIPTION, + PROP_RELIABILITY, + PROP_COV_INCREMENT, +#if defined(INTRINSIC_REPORTING) + PROP_TIME_DELAY, + PROP_NOTIFICATION_CLASS, + PROP_HIGH_LIMIT, + PROP_LOW_LIMIT, + PROP_DEADBAND, + PROP_LIMIT_ENABLE, + PROP_EVENT_ENABLE, + PROP_ACKED_TRANSITIONS, + PROP_NOTIFY_TYPE, + PROP_EVENT_TIME_STAMPS, +#endif + -1 +}; + +static const int Properties_Proprietary[] = { + 9997, + 9998, + 9999, + -1 +}; + +void Analog_Input_Property_Lists( + const int **pRequired, + const int **pOptional, + const int **pProprietary) +{ + if (pRequired) + *pRequired = Properties_Required; + if (pOptional) + *pOptional = Properties_Optional; + if (pProprietary) + *pProprietary = Properties_Proprietary; + + return; +} + + +void Analog_Input_Init( + void) +{ + unsigned i; +#if defined(INTRINSIC_REPORTING) + unsigned j; +#endif + + for (i = 0; i < MAX_ANALOG_INPUTS; i++) { + AI_Descr[i].Present_Value = 0.0f; + AI_Descr[i].Out_Of_Service = false; + AI_Descr[i].Units = UNITS_PERCENT; + AI_Descr[i].Reliability = RELIABILITY_NO_FAULT_DETECTED; + AI_Descr[i].Prior_Value = 0.0f; + AI_Descr[i].COV_Increment = 1.0f; + AI_Descr[i].Changed = false; +#if defined(INTRINSIC_REPORTING) + AI_Descr[i].Event_State = EVENT_STATE_NORMAL; + /* notification class not connected */ + AI_Descr[i].Notification_Class = BACNET_MAX_INSTANCE; + /* initialize Event time stamps using wildcards + and set Acked_transitions */ + for (j = 0; j < MAX_BACNET_EVENT_TRANSITION; j++) { + datetime_wildcard_set(&AI_Descr[i].Event_Time_Stamps[j]); + AI_Descr[i].Acked_Transitions[j].bIsAcked = true; + } + + /* Set handler for GetEventInformation function */ + handler_get_event_information_set(OBJECT_ANALOG_INPUT, + Analog_Input_Event_Information); + /* Set handler for AcknowledgeAlarm function */ + handler_alarm_ack_set(OBJECT_ANALOG_INPUT, Analog_Input_Alarm_Ack); + /* Set handler for GetAlarmSummary Service */ + handler_get_alarm_summary_set(OBJECT_ANALOG_INPUT, + Analog_Input_Alarm_Summary); +#endif + } +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Analog_Input_Valid_Instance( + uint32_t object_instance) +{ + unsigned int index; + + index = Analog_Input_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_INPUTS) + return true; + + return false; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then count how many you have */ +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; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the index */ +/* that correlates to the correct instance number */ +unsigned Analog_Input_Instance_To_Index( + uint32_t object_instance) +{ + unsigned index = MAX_ANALOG_INPUTS; + + if (object_instance < MAX_ANALOG_INPUTS) + index = object_instance; + + return index; +} + +float Analog_Input_Present_Value( + uint32_t object_instance) +{ + float value = 0.0; + unsigned int index; + + index = Analog_Input_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_INPUTS) { + value = AI_Descr[index].Present_Value; + } + + return value; +} + +static void Analog_Input_COV_Detect(unsigned int index, + float value) +{ + float prior_value = 0.0; + float cov_increment = 0.0; + float cov_delta = 0.0; + + if (index < MAX_ANALOG_INPUTS) { + prior_value = AI_Descr[index].Prior_Value; + cov_increment = AI_Descr[index].COV_Increment; + if (prior_value > value) { + cov_delta = prior_value - value; + } else { + cov_delta = value - prior_value; + } + if (cov_delta >= cov_increment) { + AI_Descr[index].Changed = true; + AI_Descr[index].Prior_Value = value; + } + } +} + +void Analog_Input_Present_Value_Set( + uint32_t object_instance, + float value) +{ + unsigned int index = 0; + + index = Analog_Input_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_INPUTS) { + Analog_Input_COV_Detect(index, value); + AI_Descr[index].Present_Value = value; + } +} + +bool Analog_Input_Object_Name( + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name) +{ + static char text_string[32] = ""; + unsigned int index; + bool status = false; + + if (object_instance==0) + status=characterstring_init_ansi(object_name, "Internal Temperature"); + else if (object_instance==1) + status=characterstring_init_ansi(object_name, "Magnetic field"); + else + { + index = Analog_Input_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_INPUTS) { + sprintf(text_string, "ANALOG INPUT %lu", (unsigned long) index); + status = characterstring_init_ansi(object_name, text_string); + } + } + return status; +} + +bool Analog_Input_Change_Of_Value( + uint32_t object_instance) +{ + unsigned index = 0; + bool changed = false; + + index = Analog_Input_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_INPUTS) { + changed = AI_Descr[index].Changed; + } + + return changed; +} + +void Analog_Input_Change_Of_Value_Clear( + uint32_t object_instance) +{ + unsigned index = 0; + + index = Analog_Input_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_INPUTS) { + AI_Descr[index].Changed = false; + } +} + +/** + * For a given object instance-number, loads the value_list with the COV data. + * + * @param object_instance - object-instance number of the object + * @param value_list - list of COV data + * + * @return true if the value list is encoded + */ +bool Analog_Input_Encode_Value_List( + uint32_t object_instance, + BACNET_PROPERTY_VALUE * value_list) +{ + bool status = false; + + if (value_list) { + value_list->propertyIdentifier = PROP_PRESENT_VALUE; + value_list->propertyArrayIndex = BACNET_ARRAY_ALL; + value_list->value.context_specific = false; + value_list->value.tag = BACNET_APPLICATION_TAG_REAL; + value_list->value.type.Real = + Analog_Input_Present_Value(object_instance); + value_list->value.next = NULL; + value_list->priority = BACNET_NO_PRIORITY; + value_list = value_list->next; + } + if (value_list) { + value_list->propertyIdentifier = PROP_STATUS_FLAGS; + value_list->propertyArrayIndex = BACNET_ARRAY_ALL; + value_list->value.context_specific = false; + value_list->value.tag = BACNET_APPLICATION_TAG_BIT_STRING; + bitstring_init(&value_list->value.type.Bit_String); + bitstring_set_bit(&value_list->value.type.Bit_String, + STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&value_list->value.type.Bit_String, + STATUS_FLAG_FAULT, false); + bitstring_set_bit(&value_list->value.type.Bit_String, + STATUS_FLAG_OVERRIDDEN, false); + if (Analog_Input_Out_Of_Service(object_instance)) { + bitstring_set_bit(&value_list->value.type.Bit_String, + STATUS_FLAG_OUT_OF_SERVICE, true); + } else { + bitstring_set_bit(&value_list->value.type.Bit_String, + STATUS_FLAG_OUT_OF_SERVICE, false); + } + value_list->value.next = NULL; + value_list->priority = BACNET_NO_PRIORITY; + value_list->next = NULL; + status = true; + } + + return status; +} + +float Analog_Input_COV_Increment( + uint32_t object_instance) +{ + unsigned index = 0; + float value = 0; + + index = Analog_Input_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_INPUTS) { + value = AI_Descr[index].COV_Increment; + } + + return value; +} + +void Analog_Input_COV_Increment_Set( + uint32_t object_instance, + float value) +{ + unsigned index = 0; + + index = Analog_Input_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_INPUTS) { + AI_Descr[index].COV_Increment = value; + Analog_Input_COV_Detect(index, AI_Descr[index].Present_Value); + } +} + +bool Analog_Input_Out_Of_Service( + uint32_t object_instance) +{ + unsigned index = 0; + bool value = false; + + index = Analog_Input_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_INPUTS) { + value = AI_Descr[index].Out_Of_Service; + } + + return value; +} + +void Analog_Input_Out_Of_Service_Set( + uint32_t object_instance, + bool value) +{ + unsigned index = 0; + + index = Analog_Input_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_INPUTS) { + /* BACnet Testing Observed Incident oi00104 + The Changed flag was not being set when a client wrote to the Out-of-Service bit. + Revealed by BACnet Test Client v1.8.16 ( www.bac-test.com/bacnet-test-client-download ) + BC 135.1: 8.2.1-A + BC 135.1: 8.2.2-A + Any discussions can be directed to edward@bac-test.com + Please feel free to remove this comment when my changes accepted after suitable time for + review by all interested parties. Say 6 months -> September 2016 */ + if (AI_Descr[index].Out_Of_Service != value) { + AI_Descr[index].Changed = true; + } + AI_Descr[index].Out_Of_Service = value; + } +} + +/* return apdu length, or BACNET_STATUS_ERROR on error */ +/* assumption - object already exists */ +int Analog_Input_Read_Property( + BACNET_READ_PROPERTY_DATA * rpdata) +{ + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + ANALOG_INPUT_DESCR *CurrentAI; + unsigned object_index = 0; +#if defined(INTRINSIC_REPORTING) + unsigned i = 0; + int len = 0; +#endif + uint8_t *apdu = NULL; + + if ((rpdata == NULL) || (rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + + object_index = Analog_Input_Instance_To_Index(rpdata->object_instance); + if (object_index < MAX_ANALOG_INPUTS) + CurrentAI = &AI_Descr[object_index]; + else + return BACNET_STATUS_ERROR; + + apdu = rpdata->application_data; + switch ((int) rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = + encode_application_object_id(&apdu[0], OBJECT_ANALOG_INPUT, + rpdata->object_instance); + break; + + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + Analog_Input_Object_Name(rpdata->object_instance, &char_string); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + + case PROP_OBJECT_TYPE: + apdu_len = + encode_application_enumerated(&apdu[0], OBJECT_ANALOG_INPUT); + break; + + case PROP_PRESENT_VALUE: + apdu_len = + encode_application_real(&apdu[0], + Analog_Input_Present_Value(rpdata->object_instance)); + break; + + case PROP_STATUS_FLAGS: + bitstring_init(&bit_string); +#if defined(INTRINSIC_REPORTING) + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, + CurrentAI->Event_State ? true : false); +#else + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); +#endif + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, + CurrentAI->Out_Of_Service); + + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + + case PROP_EVENT_STATE: +#if defined(INTRINSIC_REPORTING) + apdu_len = + encode_application_enumerated(&apdu[0], + CurrentAI->Event_State); +#else + apdu_len = + encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL); +#endif + break; + + case PROP_RELIABILITY: + apdu_len = + encode_application_enumerated(&apdu[0], + CurrentAI->Reliability); + break; + + case PROP_OUT_OF_SERVICE: + apdu_len = + encode_application_boolean(&apdu[0], + CurrentAI->Out_Of_Service); + break; + + case PROP_UNITS: + apdu_len = + encode_application_enumerated(&apdu[0], CurrentAI->Units); + break; + + case PROP_COV_INCREMENT: + apdu_len = encode_application_real(&apdu[0], + CurrentAI->COV_Increment); + break; + +#if defined(INTRINSIC_REPORTING) + case PROP_TIME_DELAY: + apdu_len = + encode_application_unsigned(&apdu[0], CurrentAI->Time_Delay); + break; + + case PROP_NOTIFICATION_CLASS: + apdu_len = + encode_application_unsigned(&apdu[0], + CurrentAI->Notification_Class); + break; + + case PROP_HIGH_LIMIT: + apdu_len = + encode_application_real(&apdu[0], CurrentAI->High_Limit); + break; + + case PROP_LOW_LIMIT: + apdu_len = encode_application_real(&apdu[0], CurrentAI->Low_Limit); + break; + + case PROP_DEADBAND: + apdu_len = encode_application_real(&apdu[0], CurrentAI->Deadband); + break; + + case PROP_LIMIT_ENABLE: + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, 0, + (CurrentAI-> + Limit_Enable & EVENT_LOW_LIMIT_ENABLE) ? true : false); + bitstring_set_bit(&bit_string, 1, + (CurrentAI-> + Limit_Enable & EVENT_HIGH_LIMIT_ENABLE) ? true : false); + + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + + case PROP_EVENT_ENABLE: + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, TRANSITION_TO_OFFNORMAL, + (CurrentAI-> + Event_Enable & EVENT_ENABLE_TO_OFFNORMAL) ? true : false); + bitstring_set_bit(&bit_string, TRANSITION_TO_FAULT, + (CurrentAI-> + Event_Enable & EVENT_ENABLE_TO_FAULT) ? true : false); + bitstring_set_bit(&bit_string, TRANSITION_TO_NORMAL, + (CurrentAI-> + Event_Enable & EVENT_ENABLE_TO_NORMAL) ? true : false); + + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + + case PROP_ACKED_TRANSITIONS: + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, TRANSITION_TO_OFFNORMAL, + CurrentAI->Acked_Transitions[TRANSITION_TO_OFFNORMAL]. + bIsAcked); + bitstring_set_bit(&bit_string, TRANSITION_TO_FAULT, + CurrentAI->Acked_Transitions[TRANSITION_TO_FAULT].bIsAcked); + bitstring_set_bit(&bit_string, TRANSITION_TO_NORMAL, + CurrentAI->Acked_Transitions[TRANSITION_TO_NORMAL].bIsAcked); + + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + + case PROP_NOTIFY_TYPE: + apdu_len = + encode_application_enumerated(&apdu[0], + CurrentAI->Notify_Type ? NOTIFY_EVENT : NOTIFY_ALARM); + break; + + case PROP_EVENT_TIME_STAMPS: + /* Array element zero is the number of elements in the array */ + if (rpdata->array_index == 0) + apdu_len = + encode_application_unsigned(&apdu[0], + MAX_BACNET_EVENT_TRANSITION); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. */ + else if (rpdata->array_index == BACNET_ARRAY_ALL) { + for (i = 0; i < MAX_BACNET_EVENT_TRANSITION; i++) { + len = + encode_opening_tag(&apdu[apdu_len], + TIME_STAMP_DATETIME); + len += + encode_application_date(&apdu[apdu_len + len], + &CurrentAI->Event_Time_Stamps[i].date); + len += + encode_application_time(&apdu[apdu_len + len], + &CurrentAI->Event_Time_Stamps[i].time); + len += + encode_closing_tag(&apdu[apdu_len + len], + TIME_STAMP_DATETIME); + + /* add it if we have room */ + if ((apdu_len + len) < MAX_APDU) + apdu_len += len; + else { + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + apdu_len = BACNET_STATUS_ABORT; + break; + } + } + } else if (rpdata->array_index <= MAX_BACNET_EVENT_TRANSITION) { + apdu_len = + encode_opening_tag(&apdu[apdu_len], TIME_STAMP_DATETIME); + apdu_len += + encode_application_date(&apdu[apdu_len], + &CurrentAI->Event_Time_Stamps[rpdata->array_index].date); + apdu_len += + encode_application_time(&apdu[apdu_len], + &CurrentAI->Event_Time_Stamps[rpdata->array_index].time); + apdu_len += + encode_closing_tag(&apdu[apdu_len], TIME_STAMP_DATETIME); + } else { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = BACNET_STATUS_ERROR; + } + break; +#endif + case 9997: + /* test case for real encoding-decoding real value correctly */ + apdu_len = encode_application_real(&apdu[0], 90.510F); + break; + case 9998: + /* test case for unsigned encoding-decoding unsigned value correctly */ + apdu_len = encode_application_unsigned(&apdu[0], 90); + break; + case 9999: + /* test case for signed encoding-decoding negative value correctly */ + apdu_len = encode_application_signed(&apdu[0], -200); + 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_EVENT_TIME_STAMPS) + && (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; +} + +/* returns true if successful */ +bool Analog_Input_Write_Property( + BACNET_WRITE_PROPERTY_DATA * wp_data) +{ + bool status = false; /* return value */ + unsigned int object_index = 0; + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + ANALOG_INPUT_DESCR *CurrentAI; + + /* 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? */ + 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; + } + /* only array properties can have array options */ + if ((wp_data->object_property != PROP_EVENT_TIME_STAMPS) && + (wp_data->array_index != BACNET_ARRAY_ALL)) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + return false; + } + object_index = Analog_Input_Instance_To_Index(wp_data->object_instance); + if (object_index < MAX_ANALOG_INPUTS) { + CurrentAI = &AI_Descr[object_index]; + } else { + return false; + } + + switch ((int) wp_data->object_property) { + case PROP_PRESENT_VALUE: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_REAL, + &wp_data->error_class, &wp_data->error_code); + + if (status) { + if (CurrentAI->Out_Of_Service == true) { + Analog_Input_Present_Value_Set(wp_data->object_instance, + value.type.Real); + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + status = false; + } + } + break; + + case PROP_OUT_OF_SERVICE: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN, + &wp_data->error_class, &wp_data->error_code); + if (status) { + Analog_Input_Out_Of_Service_Set( + wp_data->object_instance, + value.type.Boolean); + } + break; + + case PROP_UNITS: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED, + &wp_data->error_class, &wp_data->error_code); + if (status) { + CurrentAI->Units = value.type.Enumerated; + } + break; + + case PROP_COV_INCREMENT: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_REAL, + &wp_data->error_class, &wp_data->error_code); + if (status) { + if (value.type.Real >= 0.0) { + Analog_Input_COV_Increment_Set( + wp_data->object_instance, + value.type.Real); + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + +#if defined(INTRINSIC_REPORTING) + case PROP_TIME_DELAY: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT, + &wp_data->error_class, &wp_data->error_code); + + if (status) { + CurrentAI->Time_Delay = value.type.Unsigned_Int; + CurrentAI->Remaining_Time_Delay = CurrentAI->Time_Delay; + } + break; + + case PROP_NOTIFICATION_CLASS: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT, + &wp_data->error_class, &wp_data->error_code); + + if (status) { + CurrentAI->Notification_Class = value.type.Unsigned_Int; + } + break; + + case PROP_HIGH_LIMIT: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_REAL, + &wp_data->error_class, &wp_data->error_code); + + if (status) { + CurrentAI->High_Limit = value.type.Real; + } + break; + + case PROP_LOW_LIMIT: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_REAL, + &wp_data->error_class, &wp_data->error_code); + + if (status) { + CurrentAI->Low_Limit = value.type.Real; + } + break; + + case PROP_DEADBAND: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_REAL, + &wp_data->error_class, &wp_data->error_code); + + if (status) { + CurrentAI->Deadband = value.type.Real; + } + break; + + case PROP_LIMIT_ENABLE: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_BIT_STRING, + &wp_data->error_class, &wp_data->error_code); + + if (status) { + if (value.type.Bit_String.bits_used == 2) { + CurrentAI->Limit_Enable = value.type.Bit_String.value[0]; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + status = false; + } + } + break; + + case PROP_EVENT_ENABLE: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_BIT_STRING, + &wp_data->error_class, &wp_data->error_code); + + if (status) { + if (value.type.Bit_String.bits_used == 3) { + CurrentAI->Event_Enable = value.type.Bit_String.value[0]; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + status = false; + } + } + break; + + case PROP_NOTIFY_TYPE: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_ENUMERATED, + &wp_data->error_class, &wp_data->error_code); + + if (status) { + switch ((BACNET_NOTIFY_TYPE) value.type.Enumerated) { + case NOTIFY_EVENT: + CurrentAI->Notify_Type = 1; + break; + case NOTIFY_ALARM: + CurrentAI->Notify_Type = 0; + break; + default: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + status = false; + break; + } + } + break; +#endif + case PROP_OBJECT_IDENTIFIER: + case PROP_OBJECT_NAME: + case PROP_OBJECT_TYPE: + case PROP_STATUS_FLAGS: + case PROP_EVENT_STATE: + case PROP_DESCRIPTION: + case PROP_RELIABILITY: +#if defined(INTRINSIC_REPORTING) + case PROP_ACKED_TRANSITIONS: + case PROP_EVENT_TIME_STAMPS: +#endif + case 9997: + case 9998: + case 9999: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + default: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + break; + } + + return status; +} + + +void Analog_Input_Intrinsic_Reporting( + uint32_t object_instance) +{ +#if defined(INTRINSIC_REPORTING) + BACNET_EVENT_NOTIFICATION_DATA event_data; + BACNET_CHARACTER_STRING msgText; + ANALOG_INPUT_DESCR *CurrentAI; + unsigned int object_index; + uint8_t FromState = 0; + uint8_t ToState; + float ExceededLimit = 0.0f; + float PresentVal = 0.0f; + bool SendNotify = false; + + + object_index = Analog_Input_Instance_To_Index(object_instance); + if (object_index < MAX_ANALOG_INPUTS) + CurrentAI = &AI_Descr[object_index]; + else + return; + + /* check limits */ + if (!CurrentAI->Limit_Enable) + return; /* limits are not configured */ + + + if (CurrentAI->Ack_notify_data.bSendAckNotify) { + /* clean bSendAckNotify flag */ + CurrentAI->Ack_notify_data.bSendAckNotify = false; + /* copy toState */ + ToState = CurrentAI->Ack_notify_data.EventState; + +#if PRINT_ENABLED + fprintf(stderr, "Send Acknotification for (%s,%d).\n", + bactext_object_type_name(OBJECT_ANALOG_INPUT), object_instance); +#endif /* PRINT_ENABLED */ + + characterstring_init_ansi(&msgText, "AckNotification"); + + /* Notify Type */ + event_data.notifyType = NOTIFY_ACK_NOTIFICATION; + + /* Send EventNotification. */ + SendNotify = true; + } else { + /* actual Present_Value */ + PresentVal = Analog_Input_Present_Value(object_instance); + FromState = CurrentAI->Event_State; + switch (CurrentAI->Event_State) { + case EVENT_STATE_NORMAL: + /* A TO-OFFNORMAL event is generated under these conditions: + (a) the Present_Value must exceed the High_Limit for a minimum + period of time, specified in the Time_Delay property, and + (b) the HighLimitEnable flag must be set in the Limit_Enable property, and + (c) the TO-OFFNORMAL flag must be set in the Event_Enable property. */ + if ((PresentVal > CurrentAI->High_Limit) && + ((CurrentAI->Limit_Enable & EVENT_HIGH_LIMIT_ENABLE) == + EVENT_HIGH_LIMIT_ENABLE) && + ((CurrentAI->Event_Enable & EVENT_ENABLE_TO_OFFNORMAL) == + EVENT_ENABLE_TO_OFFNORMAL)) { + if (!CurrentAI->Remaining_Time_Delay) + CurrentAI->Event_State = EVENT_STATE_HIGH_LIMIT; + else + CurrentAI->Remaining_Time_Delay--; + break; + } + + /* A TO-OFFNORMAL event is generated under these conditions: + (a) the Present_Value must exceed the Low_Limit plus the Deadband + for a minimum period of time, specified in the Time_Delay property, and + (b) the LowLimitEnable flag must be set in the Limit_Enable property, and + (c) the TO-NORMAL flag must be set in the Event_Enable property. */ + if ((PresentVal < CurrentAI->Low_Limit) && + ((CurrentAI->Limit_Enable & EVENT_LOW_LIMIT_ENABLE) == + EVENT_LOW_LIMIT_ENABLE) && + ((CurrentAI->Event_Enable & EVENT_ENABLE_TO_OFFNORMAL) == + EVENT_ENABLE_TO_OFFNORMAL)) { + if (!CurrentAI->Remaining_Time_Delay) + CurrentAI->Event_State = EVENT_STATE_LOW_LIMIT; + else + CurrentAI->Remaining_Time_Delay--; + break; + } + /* value of the object is still in the same event state */ + CurrentAI->Remaining_Time_Delay = CurrentAI->Time_Delay; + break; + + case EVENT_STATE_HIGH_LIMIT: + /* Once exceeded, the Present_Value must fall below the High_Limit minus + the Deadband before a TO-NORMAL event is generated under these conditions: + (a) the Present_Value must fall below the High_Limit minus the Deadband + for a minimum period of time, specified in the Time_Delay property, and + (b) the HighLimitEnable flag must be set in the Limit_Enable property, and + (c) the TO-NORMAL flag must be set in the Event_Enable property. */ + if ((PresentVal < CurrentAI->High_Limit - CurrentAI->Deadband) + && ((CurrentAI->Limit_Enable & EVENT_HIGH_LIMIT_ENABLE) == + EVENT_HIGH_LIMIT_ENABLE) && + ((CurrentAI->Event_Enable & EVENT_ENABLE_TO_NORMAL) == + EVENT_ENABLE_TO_NORMAL)) { + if (!CurrentAI->Remaining_Time_Delay) + CurrentAI->Event_State = EVENT_STATE_NORMAL; + else + CurrentAI->Remaining_Time_Delay--; + break; + } + /* value of the object is still in the same event state */ + CurrentAI->Remaining_Time_Delay = CurrentAI->Time_Delay; + break; + + case EVENT_STATE_LOW_LIMIT: + /* Once the Present_Value has fallen below the Low_Limit, + the Present_Value must exceed the Low_Limit plus the Deadband + before a TO-NORMAL event is generated under these conditions: + (a) the Present_Value must exceed the Low_Limit plus the Deadband + for a minimum period of time, specified in the Time_Delay property, and + (b) the LowLimitEnable flag must be set in the Limit_Enable property, and + (c) the TO-NORMAL flag must be set in the Event_Enable property. */ + if ((PresentVal > CurrentAI->Low_Limit + CurrentAI->Deadband) + && ((CurrentAI->Limit_Enable & EVENT_LOW_LIMIT_ENABLE) == + EVENT_LOW_LIMIT_ENABLE) && + ((CurrentAI->Event_Enable & EVENT_ENABLE_TO_NORMAL) == + EVENT_ENABLE_TO_NORMAL)) { + if (!CurrentAI->Remaining_Time_Delay) + CurrentAI->Event_State = EVENT_STATE_NORMAL; + else + CurrentAI->Remaining_Time_Delay--; + break; + } + /* value of the object is still in the same event state */ + CurrentAI->Remaining_Time_Delay = CurrentAI->Time_Delay; + break; + + default: + return; /* shouldn't happen */ + } /* switch (FromState) */ + + ToState = CurrentAI->Event_State; + + if (FromState != ToState) { + /* Event_State has changed. + Need to fill only the basic parameters of this type of event. + Other parameters will be filled in common function. */ + + switch (ToState) { + case EVENT_STATE_HIGH_LIMIT: + ExceededLimit = CurrentAI->High_Limit; + characterstring_init_ansi(&msgText, "Goes to high limit"); + break; + + case EVENT_STATE_LOW_LIMIT: + ExceededLimit = CurrentAI->Low_Limit; + characterstring_init_ansi(&msgText, "Goes to low limit"); + break; + + case EVENT_STATE_NORMAL: + if (FromState == EVENT_STATE_HIGH_LIMIT) { + ExceededLimit = CurrentAI->High_Limit; + characterstring_init_ansi(&msgText, + "Back to normal state from high limit"); + } else { + ExceededLimit = CurrentAI->Low_Limit; + characterstring_init_ansi(&msgText, + "Back to normal state from low limit"); + } + break; + + default: + ExceededLimit = 0; + break; + } /* switch (ToState) */ + +#if PRINT_ENABLED + fprintf(stderr, "Event_State for (%s,%d) goes from %s to %s.\n", + bactext_object_type_name(OBJECT_ANALOG_INPUT), object_instance, + bactext_event_state_name(FromState), + bactext_event_state_name(ToState)); +#endif /* PRINT_ENABLED */ + + /* Notify Type */ + event_data.notifyType = CurrentAI->Notify_Type; + + /* Send EventNotification. */ + SendNotify = true; + } + } + + + if (SendNotify) { + /* Event Object Identifier */ + event_data.eventObjectIdentifier.type = OBJECT_ANALOG_INPUT; + event_data.eventObjectIdentifier.instance = object_instance; + + /* Time Stamp */ + event_data.timeStamp.tag = TIME_STAMP_DATETIME; + Device_getCurrentDateTime(&event_data.timeStamp.value.dateTime); + + if (event_data.notifyType != NOTIFY_ACK_NOTIFICATION) { + /* fill Event_Time_Stamps */ + switch (ToState) { + case EVENT_STATE_HIGH_LIMIT: + case EVENT_STATE_LOW_LIMIT: + CurrentAI->Event_Time_Stamps[TRANSITION_TO_OFFNORMAL] = + event_data.timeStamp.value.dateTime; + break; + + case EVENT_STATE_FAULT: + CurrentAI->Event_Time_Stamps[TRANSITION_TO_FAULT] = + event_data.timeStamp.value.dateTime; + break; + + case EVENT_STATE_NORMAL: + CurrentAI->Event_Time_Stamps[TRANSITION_TO_NORMAL] = + event_data.timeStamp.value.dateTime; + break; + } + } + + /* Notification Class */ + event_data.notificationClass = CurrentAI->Notification_Class; + + /* Event Type */ + event_data.eventType = EVENT_OUT_OF_RANGE; + + /* Message Text */ + event_data.messageText = &msgText; + + /* Notify Type */ + /* filled before */ + + /* From State */ + if (event_data.notifyType != NOTIFY_ACK_NOTIFICATION) + event_data.fromState = FromState; + + /* To State */ + event_data.toState = CurrentAI->Event_State; + + /* Event Values */ + if (event_data.notifyType != NOTIFY_ACK_NOTIFICATION) { + /* Value that exceeded a limit. */ + event_data.notificationParams.outOfRange.exceedingValue = + PresentVal; + /* Status_Flags of the referenced object. */ + bitstring_init(&event_data.notificationParams.outOfRange. + statusFlags); + bitstring_set_bit(&event_data.notificationParams.outOfRange. + statusFlags, STATUS_FLAG_IN_ALARM, + CurrentAI->Event_State ? true : false); + bitstring_set_bit(&event_data.notificationParams.outOfRange. + statusFlags, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&event_data.notificationParams.outOfRange. + statusFlags, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&event_data.notificationParams.outOfRange. + statusFlags, STATUS_FLAG_OUT_OF_SERVICE, + CurrentAI->Out_Of_Service); + /* Deadband used for limit checking. */ + event_data.notificationParams.outOfRange.deadband = + CurrentAI->Deadband; + /* Limit that was exceeded. */ + event_data.notificationParams.outOfRange.exceededLimit = + ExceededLimit; + } + + /* add data from notification class */ + Notification_Class_common_reporting_function(&event_data); + + /* Ack required */ + if ((event_data.notifyType != NOTIFY_ACK_NOTIFICATION) && + (event_data.ackRequired == true)) { + switch (event_data.toState) { + case EVENT_STATE_OFFNORMAL: + case EVENT_STATE_HIGH_LIMIT: + case EVENT_STATE_LOW_LIMIT: + CurrentAI->Acked_Transitions[TRANSITION_TO_OFFNORMAL]. + bIsAcked = false; + CurrentAI->Acked_Transitions[TRANSITION_TO_OFFNORMAL]. + Time_Stamp = event_data.timeStamp.value.dateTime; + break; + + case EVENT_STATE_FAULT: + CurrentAI->Acked_Transitions[TRANSITION_TO_FAULT]. + bIsAcked = false; + CurrentAI->Acked_Transitions[TRANSITION_TO_FAULT]. + Time_Stamp = event_data.timeStamp.value.dateTime; + break; + + case EVENT_STATE_NORMAL: + CurrentAI->Acked_Transitions[TRANSITION_TO_NORMAL]. + bIsAcked = false; + CurrentAI->Acked_Transitions[TRANSITION_TO_NORMAL]. + Time_Stamp = event_data.timeStamp.value.dateTime; + break; + } + } + } +#endif /* defined(INTRINSIC_REPORTING) */ +} + + +#if defined(INTRINSIC_REPORTING) +int Analog_Input_Event_Information( + unsigned index, + BACNET_GET_EVENT_INFORMATION_DATA * getevent_data) +{ + bool IsNotAckedTransitions; + bool IsActiveEvent; + int i; + + + /* check index */ + if (index < MAX_ANALOG_INPUTS) { + /* Event_State not equal to NORMAL */ + IsActiveEvent = (AI_Descr[index].Event_State != EVENT_STATE_NORMAL); + + /* Acked_Transitions property, which has at least one of the bits + (TO-OFFNORMAL, TO-FAULT, TONORMAL) set to FALSE. */ + IsNotAckedTransitions = + (AI_Descr[index].Acked_Transitions[TRANSITION_TO_OFFNORMAL]. + bIsAcked == + false) | (AI_Descr[index].Acked_Transitions[TRANSITION_TO_FAULT]. + bIsAcked == + false) | (AI_Descr[index].Acked_Transitions[TRANSITION_TO_NORMAL]. + bIsAcked == false); + } else + return -1; /* end of list */ + + if ((IsActiveEvent) || (IsNotAckedTransitions)) { + /* Object Identifier */ + getevent_data->objectIdentifier.type = OBJECT_ANALOG_INPUT; + getevent_data->objectIdentifier.instance = + Analog_Input_Index_To_Instance(index); + /* Event State */ + getevent_data->eventState = AI_Descr[index].Event_State; + /* Acknowledged Transitions */ + bitstring_init(&getevent_data->acknowledgedTransitions); + bitstring_set_bit(&getevent_data->acknowledgedTransitions, + TRANSITION_TO_OFFNORMAL, + AI_Descr[index].Acked_Transitions[TRANSITION_TO_OFFNORMAL]. + bIsAcked); + bitstring_set_bit(&getevent_data->acknowledgedTransitions, + TRANSITION_TO_FAULT, + AI_Descr[index].Acked_Transitions[TRANSITION_TO_FAULT].bIsAcked); + bitstring_set_bit(&getevent_data->acknowledgedTransitions, + TRANSITION_TO_NORMAL, + AI_Descr[index].Acked_Transitions[TRANSITION_TO_NORMAL].bIsAcked); + /* Event Time Stamps */ + for (i = 0; i < 3; i++) { + getevent_data->eventTimeStamps[i].tag = TIME_STAMP_DATETIME; + getevent_data->eventTimeStamps[i].value.dateTime = + AI_Descr[index].Event_Time_Stamps[i]; + } + /* Notify Type */ + getevent_data->notifyType = AI_Descr[index].Notify_Type; + /* Event Enable */ + bitstring_init(&getevent_data->eventEnable); + bitstring_set_bit(&getevent_data->eventEnable, TRANSITION_TO_OFFNORMAL, + (AI_Descr[index]. + Event_Enable & EVENT_ENABLE_TO_OFFNORMAL) ? true : false); + bitstring_set_bit(&getevent_data->eventEnable, TRANSITION_TO_FAULT, + (AI_Descr[index]. + Event_Enable & EVENT_ENABLE_TO_FAULT) ? true : false); + bitstring_set_bit(&getevent_data->eventEnable, TRANSITION_TO_NORMAL, + (AI_Descr[index]. + Event_Enable & EVENT_ENABLE_TO_NORMAL) ? true : false); + /* Event Priorities */ + Notification_Class_Get_Priorities(AI_Descr[index].Notification_Class, + getevent_data->eventPriorities); + + return 1; /* active event */ + } else + return 0; /* no active event at this index */ +} + + +int Analog_Input_Alarm_Ack( + BACNET_ALARM_ACK_DATA * alarmack_data, + BACNET_ERROR_CODE * error_code) +{ + ANALOG_INPUT_DESCR *CurrentAI; + unsigned int object_index; + + + object_index = + Analog_Input_Instance_To_Index(alarmack_data->eventObjectIdentifier. + instance); + + if (object_index < MAX_ANALOG_INPUTS) + CurrentAI = &AI_Descr[object_index]; + else { + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return -1; + } + + switch (alarmack_data->eventStateAcked) { + case EVENT_STATE_OFFNORMAL: + case EVENT_STATE_HIGH_LIMIT: + case EVENT_STATE_LOW_LIMIT: + if (CurrentAI->Acked_Transitions[TRANSITION_TO_OFFNORMAL]. + bIsAcked == false) { + if (alarmack_data->eventTimeStamp.tag != TIME_STAMP_DATETIME) { + *error_code = ERROR_CODE_INVALID_TIME_STAMP; + return -1; + } + if (datetime_compare(&CurrentAI-> + Acked_Transitions[TRANSITION_TO_OFFNORMAL].Time_Stamp, + &alarmack_data->eventTimeStamp.value.dateTime) > 0) { + *error_code = ERROR_CODE_INVALID_TIME_STAMP; + return -1; + } + + /* FIXME: Send ack notification */ + CurrentAI->Acked_Transitions[TRANSITION_TO_OFFNORMAL]. + bIsAcked = true; + } else { + *error_code = ERROR_CODE_INVALID_EVENT_STATE; + return -1; + } + break; + + case EVENT_STATE_FAULT: + if (CurrentAI->Acked_Transitions[TRANSITION_TO_FAULT].bIsAcked == + false) { + if (alarmack_data->eventTimeStamp.tag != TIME_STAMP_DATETIME) { + *error_code = ERROR_CODE_INVALID_TIME_STAMP; + return -1; + } + if (datetime_compare(&CurrentAI-> + Acked_Transitions[TRANSITION_TO_FAULT].Time_Stamp, + &alarmack_data->eventTimeStamp.value.dateTime) > 0) { + *error_code = ERROR_CODE_INVALID_TIME_STAMP; + return -1; + } + + /* FIXME: Send ack notification */ + CurrentAI->Acked_Transitions[TRANSITION_TO_FAULT].bIsAcked = + true; + } else { + *error_code = ERROR_CODE_INVALID_EVENT_STATE; + return -1; + } + break; + + case EVENT_STATE_NORMAL: + if (CurrentAI->Acked_Transitions[TRANSITION_TO_NORMAL].bIsAcked == + false) { + if (alarmack_data->eventTimeStamp.tag != TIME_STAMP_DATETIME) { + *error_code = ERROR_CODE_INVALID_TIME_STAMP; + return -1; + } + if (datetime_compare(&CurrentAI-> + Acked_Transitions[TRANSITION_TO_NORMAL].Time_Stamp, + &alarmack_data->eventTimeStamp.value.dateTime) > 0) { + *error_code = ERROR_CODE_INVALID_TIME_STAMP; + return -1; + } + + /* FIXME: Send ack notification */ + CurrentAI->Acked_Transitions[TRANSITION_TO_NORMAL].bIsAcked = + true; + } else { + *error_code = ERROR_CODE_INVALID_EVENT_STATE; + return -1; + } + break; + + default: + return -2; + } + CurrentAI->Ack_notify_data.bSendAckNotify = true; + CurrentAI->Ack_notify_data.EventState = alarmack_data->eventStateAcked; + + return 1; +} + +int Analog_Input_Alarm_Summary( + unsigned index, + BACNET_GET_ALARM_SUMMARY_DATA * getalarm_data) +{ + + /* check index */ + if (index < MAX_ANALOG_INPUTS) { + /* Event_State is not equal to NORMAL and + Notify_Type property value is ALARM */ + if ((AI_Descr[index].Event_State != EVENT_STATE_NORMAL) && + (AI_Descr[index].Notify_Type == NOTIFY_ALARM)) { + /* Object Identifier */ + getalarm_data->objectIdentifier.type = OBJECT_ANALOG_INPUT; + getalarm_data->objectIdentifier.instance = + Analog_Input_Index_To_Instance(index); + /* Alarm State */ + getalarm_data->alarmState = AI_Descr[index].Event_State; + /* Acknowledged Transitions */ + bitstring_init(&getalarm_data->acknowledgedTransitions); + bitstring_set_bit(&getalarm_data->acknowledgedTransitions, + TRANSITION_TO_OFFNORMAL, + AI_Descr[index].Acked_Transitions[TRANSITION_TO_OFFNORMAL]. + bIsAcked); + bitstring_set_bit(&getalarm_data->acknowledgedTransitions, + TRANSITION_TO_FAULT, + AI_Descr[index]. + Acked_Transitions[TRANSITION_TO_FAULT].bIsAcked); + bitstring_set_bit(&getalarm_data->acknowledgedTransitions, + TRANSITION_TO_NORMAL, + AI_Descr[index]. + Acked_Transitions[TRANSITION_TO_NORMAL].bIsAcked); + + return 1; /* active alarm */ + } else + return 0; /* no active alarm at this index */ + } else + return -1; /* end of list */ +} +#endif /* defined(INTRINSIC_REPORTING) */ + + +#ifdef TEST +#include +#include +#include "ctest.h" + +bool WPValidateArgType( + BACNET_APPLICATION_DATA_VALUE * pValue, + uint8_t ucExpectedTag, + BACNET_ERROR_CLASS * pErrorClass, + BACNET_ERROR_CODE * pErrorCode) +{ + bool bResult; + + /* + * start out assuming success and only set up error + * response if validation fails. + */ + bResult = true; + if (pValue->tag != ucExpectedTag) { + bResult = false; + *pErrorClass = ERROR_CLASS_PROPERTY; + *pErrorCode = ERROR_CODE_INVALID_DATA_TYPE; + } + + return (bResult); +} + +void testAnalogInput( + Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + uint32_t decoded_instance = 0; + uint16_t decoded_type = 0; + BACNET_READ_PROPERTY_DATA rpdata; + + Analog_Input_Init(); + rpdata.application_data = &apdu[0]; + rpdata.application_data_len = sizeof(apdu); + rpdata.object_type = OBJECT_ANALOG_INPUT; + rpdata.object_instance = 1; + rpdata.object_property = PROP_OBJECT_IDENTIFIER; + rpdata.array_index = BACNET_ARRAY_ALL; + len = Analog_Input_Read_Property(&rpdata); + ct_test(pTest, len != 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == rpdata.object_type); + ct_test(pTest, decoded_instance == rpdata.object_instance); + + return; +} + +#ifdef TEST_ANALOG_INPUT +int main( + void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Analog Input", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testAnalogInput); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_ANALOG_INPUT */ +#endif /* TEST */ diff --git a/ports/esp32/src/ai.h b/ports/esp32/src/ai.h new file mode 100644 index 0000000..1ed75c9 --- /dev/null +++ b/ports/esp32/src/ai.h @@ -0,0 +1,174 @@ +/************************************************************************** +* +* Copyright (C) 2005 Steve Karg +* Copyright (C) 2011 Krzysztof Malorny +* +* 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 AI_H +#define AI_H + +#include +#include +#include "bacdef.h" +#include "rp.h" +#include "wp.h" +#if defined(INTRINSIC_REPORTING) +#include "nc.h" +#include "getevent.h" +#include "alarm_ack.h" +#include "get_alarm_sum.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + typedef struct analog_input_descr { + unsigned Event_State:3; + float Present_Value; + BACNET_RELIABILITY Reliability; + bool Out_Of_Service; + uint8_t Units; + float Prior_Value; + float COV_Increment; + bool Changed; +#if defined(INTRINSIC_REPORTING) + uint32_t Time_Delay; + uint32_t Notification_Class; + float High_Limit; + float Low_Limit; + float Deadband; + unsigned Limit_Enable:2; + unsigned Event_Enable:3; + unsigned Notify_Type:1; + ACKED_INFO Acked_Transitions[MAX_BACNET_EVENT_TRANSITION]; + BACNET_DATE_TIME Event_Time_Stamps[MAX_BACNET_EVENT_TRANSITION]; + /* time to generate event notification */ + uint32_t Remaining_Time_Delay; + /* AckNotification informations */ + ACK_NOTIFICATION Ack_notify_data; +#endif + } ANALOG_INPUT_DESCR; + + void Analog_Input_Property_Lists( + const int **pRequired, + const int **pOptional, + const int **pProprietary); + + bool Analog_Input_Valid_Instance( + uint32_t object_instance); + unsigned Analog_Input_Count( + void); + uint32_t Analog_Input_Index_To_Instance( + unsigned index); + unsigned Analog_Input_Instance_To_Index( + uint32_t instance); + bool Analog_Input_Object_Instance_Add( + uint32_t instance); + + bool Analog_Input_Object_Name( + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name); + bool Analog_Input_Name_Set( + uint32_t object_instance, + char *new_name); + + char *Analog_Input_Description( + uint32_t instance); + bool Analog_Input_Description_Set( + uint32_t instance, + char *new_name); + + bool Analog_Input_Units_Set( + uint32_t instance, + uint16_t units); + uint16_t Analog_Input_Units( + uint32_t instance); + + int Analog_Input_Read_Property( + BACNET_READ_PROPERTY_DATA * rpdata); + bool Analog_Input_Write_Property( + BACNET_WRITE_PROPERTY_DATA * wp_data); + + float Analog_Input_Present_Value( + uint32_t object_instance); + void Analog_Input_Present_Value_Set( + uint32_t object_instance, + float value); + + bool Analog_Input_Out_Of_Service( + uint32_t object_instance); + void Analog_Input_Out_Of_Service_Set( + uint32_t object_instance, + bool oos_flag); + + bool Analog_Input_Change_Of_Value( + uint32_t instance); + void Analog_Input_Change_Of_Value_Clear( + uint32_t instance); + bool Analog_Input_Encode_Value_List( + uint32_t object_instance, + BACNET_PROPERTY_VALUE * value_list); + float Analog_Input_COV_Increment( + uint32_t instance); + void Analog_Input_COV_Increment_Set( + uint32_t instance, + float value); + + /* note: header of Intrinsic_Reporting function is required + even when INTRINSIC_REPORTING is not defined */ + void Analog_Input_Intrinsic_Reporting( + uint32_t object_instance); + +#if defined(INTRINSIC_REPORTING) + int Analog_Input_Event_Information( + unsigned index, + BACNET_GET_EVENT_INFORMATION_DATA * getevent_data); + + int Analog_Input_Alarm_Ack( + BACNET_ALARM_ACK_DATA * alarmack_data, + BACNET_ERROR_CODE * error_code); + + int Analog_Input_Alarm_Summary( + unsigned index, + BACNET_GET_ALARM_SUMMARY_DATA * getalarm_data); +#endif + + bool Analog_Input_Create( + uint32_t object_instance); + bool Analog_Input_Delete( + uint32_t object_instance); + void Analog_Input_Cleanup( + void); + void Analog_Input_Init( + void); + +#ifdef TEST +#include "ctest.h" + void testAnalogInput( + Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/ports/esp32/src/bip_init.c b/ports/esp32/src/bip_init.c new file mode 100644 index 0000000..81dd71d --- /dev/null +++ b/ports/esp32/src/bip_init.c @@ -0,0 +1,57 @@ +// +// Copyleft F.Chaxel 2017 +// + +#include "esp_log.h" +#include "esp_wifi.h" + +#include "lwip/sockets.h" +#include "lwip/netdb.h" + +#include "bip.h" + +long bip_getaddrbyname( + const char *host_name) +{ + return 0; +} + +void bip_set_interface(char *ifname) +{ +} + +void bip_cleanup (void) +{ + close(bip_socket()); + bip_set_socket(-1); +} + +bool bip_init(char *ifname) +{ + + tcpip_adapter_ip_info_t ip_info = { 0 }; + + int value = 1; + + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); + + bip_set_interface(ifname); + bip_set_port(htons(0xBAC0)); + bip_set_addr(ip_info.ip.addr); + bip_set_broadcast_addr((ip_info.ip.addr&ip_info.netmask.addr)|(~ip_info.netmask.addr)); + + int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + struct sockaddr_in saddr = { 0 }; + + saddr.sin_family = PF_INET; + saddr.sin_port = htons(0xBAC0); + saddr.sin_addr.s_addr = htonl(INADDR_ANY); + bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in)); + + setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *) &value, sizeof(value)); + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &value, sizeof(value)); + + bip_set_socket(sock); + + return true; +} \ No newline at end of file diff --git a/ports/esp32/src/bo.c b/ports/esp32/src/bo.c new file mode 100644 index 0000000..60f3b02 --- /dev/null +++ b/ports/esp32/src/bo.c @@ -0,0 +1,556 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ + +/* Binary Output Objects - customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bacapp.h" +#include "config.h" /* the custom stuff */ +#include "rp.h" +#include "wp.h" +#include "bo.h" +#include "handlers.h" + +#ifndef MAX_BINARY_OUTPUTS +#define MAX_BINARY_OUTPUTS 1 +#endif + +/* When all the priorities are level null, the present value returns */ +/* the Relinquish Default value */ +#define RELINQUISH_DEFAULT BINARY_INACTIVE +/* Here is our Priority Array.*/ +static BACNET_BINARY_PV + Binary_Output_Level[MAX_BINARY_OUTPUTS][BACNET_MAX_PRIORITY]; +/* Writable out-of-service allows others to play with our Present Value */ +/* without changing the physical output */ +static bool Out_Of_Service[MAX_BINARY_OUTPUTS]; + +/* These three arrays are used by the ReadPropertyMultiple handler */ +static const int Binary_Output_Properties_Required[] = { + PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, + PROP_OBJECT_TYPE, + PROP_PRESENT_VALUE, + PROP_STATUS_FLAGS, + PROP_EVENT_STATE, + PROP_OUT_OF_SERVICE, + PROP_POLARITY, + PROP_PRIORITY_ARRAY, + PROP_RELINQUISH_DEFAULT, + -1 +}; + +static const int Binary_Output_Properties_Optional[] = { + PROP_DESCRIPTION, + PROP_ACTIVE_TEXT, + PROP_INACTIVE_TEXT, + -1 +}; + +static const int Binary_Output_Properties_Proprietary[] = { + -1 +}; + +void Binary_Output_Property_Lists( + const int **pRequired, + const int **pOptional, + const int **pProprietary) +{ + if (pRequired) + *pRequired = Binary_Output_Properties_Required; + if (pOptional) + *pOptional = Binary_Output_Properties_Optional; + if (pProprietary) + *pProprietary = Binary_Output_Properties_Proprietary; + + return; +} + +void Binary_Output_Init( + void) +{ + unsigned i, j; + static bool initialized = false; + + if (!initialized) { + initialized = true; + + /* initialize all the analog output priority arrays to NULL */ + for (i = 0; i < MAX_BINARY_OUTPUTS; i++) { + for (j = 0; j < BACNET_MAX_PRIORITY; j++) { + Binary_Output_Level[i][j] = BINARY_NULL; + } + } + } + + return; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Binary_Output_Valid_Instance( + uint32_t object_instance) +{ + if (object_instance < MAX_BINARY_OUTPUTS) + return true; + + return false; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then count how many you have */ +unsigned Binary_Output_Count( + void) +{ + return MAX_BINARY_OUTPUTS; +} + +/* 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 Binary_Output_Index_To_Instance( + unsigned index) +{ + return index; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the index */ +/* that correlates to the correct instance number */ +unsigned Binary_Output_Instance_To_Index( + uint32_t object_instance) +{ + unsigned index = MAX_BINARY_OUTPUTS; + + if (object_instance < MAX_BINARY_OUTPUTS) + index = object_instance; + + return index; +} + +BACNET_BINARY_PV Binary_Output_Present_Value( + uint32_t object_instance) +{ + BACNET_BINARY_PV value = RELINQUISH_DEFAULT; + unsigned index = 0; + unsigned i = 0; + + index = Binary_Output_Instance_To_Index(object_instance); + if (index < MAX_BINARY_OUTPUTS) { + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + if (Binary_Output_Level[index][i] != BINARY_NULL) { + value = Binary_Output_Level[index][i]; + break; + } + } + } + + return value; +} + +bool Binary_Output_Out_Of_Service( + uint32_t object_instance) +{ + bool value = false; + unsigned index = 0; + + index = Binary_Output_Instance_To_Index(object_instance); + if (index < MAX_BINARY_OUTPUTS) { + value = Out_Of_Service[index]; + } + + return value; +} + +/* note: the object name must be unique within this device */ + +bool Binary_Output_Object_Name( + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name) +{ + static char text_string[32] = ""; + bool status = false; + + if (object_instance==0) + status=characterstring_init_ansi(object_name, "Led"); + else + { + if (object_instance < MAX_BINARY_OUTPUTS) { + sprintf(text_string, "BINARY OUTPUT %lu", + (unsigned long) object_instance); + status = characterstring_init_ansi(object_name, text_string); + } + } + return status; +} + + +/* return apdu len, or BACNET_STATUS_ERROR on error */ +int Binary_Output_Read_Property( + BACNET_READ_PROPERTY_DATA * rpdata) +{ + int len = 0; + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + BACNET_BINARY_PV present_value = BINARY_INACTIVE; + BACNET_POLARITY polarity = POLARITY_NORMAL; + unsigned object_index = 0; + unsigned i = 0; + bool state = false; + uint8_t *apdu = NULL; + + 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_BINARY_OUTPUT, + rpdata->object_instance); + break; + /* note: Name and Description don't have to be the same. + You could make Description writable and different */ + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + Binary_Output_Object_Name(rpdata->object_instance, &char_string); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = + encode_application_enumerated(&apdu[0], OBJECT_BINARY_OUTPUT); + break; + case PROP_PRESENT_VALUE: + present_value = + Binary_Output_Present_Value(rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], present_value); + break; + case PROP_STATUS_FLAGS: + /* note: see the details in the standard on how to use these */ + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + /* note: see the details in the standard on how to use this */ + apdu_len = + encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + object_index = + Binary_Output_Instance_To_Index(rpdata->object_instance); + state = Out_Of_Service[object_index]; + apdu_len = encode_application_boolean(&apdu[0], state); + break; + case PROP_POLARITY: + apdu_len = encode_application_enumerated(&apdu[0], polarity); + break; + case PROP_PRIORITY_ARRAY: + /* Array element zero is the number of elements in the array */ + if (rpdata->array_index == 0) + apdu_len = + encode_application_unsigned(&apdu[0], BACNET_MAX_PRIORITY); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. */ + else if (rpdata->array_index == BACNET_ARRAY_ALL) { + object_index = + Binary_Output_Instance_To_Index(rpdata->object_instance); + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + /* FIXME: check if we have room before adding it to APDU */ + if (Binary_Output_Level[object_index][i] == BINARY_NULL) + len = encode_application_null(&apdu[apdu_len]); + else { + present_value = Binary_Output_Level[object_index][i]; + len = + encode_application_enumerated(&apdu[apdu_len], + present_value); + } + /* add it if we have room */ + if ((apdu_len + len) < MAX_APDU) + apdu_len += len; + else { + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + apdu_len = BACNET_STATUS_ABORT; + break; + } + } + } else { + object_index = + Binary_Output_Instance_To_Index(rpdata->object_instance); + if (rpdata->array_index <= BACNET_MAX_PRIORITY) { + if (Binary_Output_Level[object_index][rpdata->array_index - + 1] == BINARY_NULL) + apdu_len = encode_application_null(&apdu[apdu_len]); + else { + present_value = Binary_Output_Level[object_index] + [rpdata->array_index - 1]; + apdu_len = + encode_application_enumerated(&apdu[apdu_len], + present_value); + } + } else { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = BACNET_STATUS_ERROR; + } + } + + break; + case PROP_RELINQUISH_DEFAULT: + present_value = RELINQUISH_DEFAULT; + apdu_len = encode_application_enumerated(&apdu[0], present_value); + break; + case PROP_ACTIVE_TEXT: + characterstring_init_ansi(&char_string, "on"); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_INACTIVE_TEXT: + characterstring_init_ansi(&char_string, "off"); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + 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_PRIORITY_ARRAY) && + (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; +} + +/* returns true if successful */ +bool Binary_Output_Write_Property( + BACNET_WRITE_PROPERTY_DATA * wp_data) +{ + bool status = false; /* return value */ + unsigned int object_index = 0; + unsigned int priority = 0; + BACNET_BINARY_PV level = BINARY_NULL; + 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); + /* FIXME: len < application_data_len: more data? */ + 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; + } + if ((wp_data->object_property != PROP_PRIORITY_ARRAY) && + (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; + } + switch (wp_data->object_property) { + case PROP_PRESENT_VALUE: + if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) { + priority = wp_data->priority; + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */ ) && + (value.type.Enumerated <= MAX_BINARY_PV)) { + level = (BACNET_BINARY_PV) value.type.Enumerated; + object_index = + Binary_Output_Instance_To_Index + (wp_data->object_instance); + priority--; + Binary_Output_Level[object_index][priority] = level; + /* Note: you could set the physical output here if we + are the highest priority. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } else if (priority == 6) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_NULL, + &wp_data->error_class, &wp_data->error_code); + if (status) { + level = BINARY_NULL; + object_index = + Binary_Output_Instance_To_Index + (wp_data->object_instance); + priority = wp_data->priority; + if (priority && (priority <= BACNET_MAX_PRIORITY)) { + priority--; + Binary_Output_Level[object_index][priority] = level; + /* Note: you could set the physical output here to the next + highest priority, or to the relinquish default if no + priorities are set. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + } + break; + case PROP_OUT_OF_SERVICE: + status = + WPValidateArgType(&value, BACNET_APPLICATION_TAG_BOOLEAN, + &wp_data->error_class, &wp_data->error_code); + if (status) { + object_index = + Binary_Output_Instance_To_Index(wp_data->object_instance); + Out_Of_Service[object_index] = + value.type.Boolean; + } + break; + case PROP_OBJECT_IDENTIFIER: + case PROP_OBJECT_NAME: + case PROP_OBJECT_TYPE: + case PROP_STATUS_FLAGS: + case PROP_RELIABILITY: + case PROP_EVENT_STATE: + case PROP_POLARITY: + case PROP_PRIORITY_ARRAY: + case PROP_RELINQUISH_DEFAULT: + case PROP_ACTIVE_TEXT: + case PROP_INACTIVE_TEXT: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + default: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + break; + } + + return status; +} + + +#ifdef TEST +#include +#include +#include "ctest.h" + +bool WPValidateArgType( + BACNET_APPLICATION_DATA_VALUE * pValue, + uint8_t ucExpectedTag, + BACNET_ERROR_CLASS * pErrorClass, + BACNET_ERROR_CODE * pErrorCode) +{ + pValue = pValue; + ucExpectedTag = ucExpectedTag; + pErrorClass = pErrorClass; + pErrorCode = pErrorCode; + + return false; +} + +void testBinaryOutput( + Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + uint16_t decoded_type = 0; + uint32_t decoded_instance = 0; + BACNET_READ_PROPERTY_DATA rpdata; + + Binary_Output_Init(); + rpdata.application_data = &apdu[0]; + rpdata.application_data_len = sizeof(apdu); + rpdata.object_type = OBJECT_BINARY_OUTPUT; + rpdata.object_instance = 1; + rpdata.object_property = PROP_OBJECT_IDENTIFIER; + rpdata.array_index = BACNET_ARRAY_ALL; + len = Binary_Output_Read_Property(&rpdata); + ct_test(pTest, len != 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == rpdata.object_type); + ct_test(pTest, decoded_instance == rpdata.object_instance); + + return; +} + +#ifdef TEST_BINARY_OUTPUT +int main( + void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Binary Output", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testBinaryOutput); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_BINARY_INPUT */ +#endif /* TEST */ diff --git a/ports/esp32/src/bo.h b/ports/esp32/src/bo.h new file mode 100644 index 0000000..3b0cf2e --- /dev/null +++ b/ports/esp32/src/bo.h @@ -0,0 +1,141 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#ifndef BO_H +#define BO_H + +#include +#include +#include "bacdef.h" +#include "bacerror.h" +#include "rp.h" +#include "wp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + void Binary_Output_Init( + void); + + void Binary_Output_Property_Lists( + const int **pRequired, + const int **pOptional, + const int **pProprietary); + + bool Binary_Output_Valid_Instance( + uint32_t object_instance); + unsigned Binary_Output_Count( + void); + uint32_t Binary_Output_Index_To_Instance( + unsigned index); + unsigned Binary_Output_Instance_To_Index( + uint32_t instance); + bool Binary_Output_Object_Instance_Add( + uint32_t instance); + + bool Binary_Output_Object_Name( + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name); + bool Binary_Output_Name_Set( + uint32_t object_instance, + char *new_name); + + char *Binary_Output_Description( + uint32_t instance); + bool Binary_Output_Description_Set( + uint32_t instance, + char *new_name); + + char *Binary_Output_Inactive_Text( + uint32_t instance); + bool Binary_Output_Inactive_Text_Set( + uint32_t instance, + char *new_name); + char *Binary_Output_Active_Text( + uint32_t instance); + bool Binary_Output_Active_Text_Set( + uint32_t instance, + char *new_name); + + BACNET_BINARY_PV Binary_Output_Present_Value( + uint32_t instance); + bool Binary_Output_Present_Value_Set( + uint32_t instance, + BACNET_BINARY_PV binary_value, + unsigned priority); + bool Binary_Output_Present_Value_Relinquish( + uint32_t instance, + unsigned priority); + unsigned Binary_Output_Present_Value_Priority( + uint32_t object_instance); + + BACNET_POLARITY Binary_Output_Polarity( + uint32_t instance); + bool Binary_Output_Polarity_Set( + uint32_t object_instance, + BACNET_POLARITY polarity); + + bool Binary_Output_Out_Of_Service( + uint32_t instance); + void Binary_Output_Out_Of_Service_Set( + uint32_t object_instance, + bool value); + + BACNET_BINARY_PV Binary_Output_Relinquish_Default( + uint32_t object_instance); + bool Binary_Output_Relinquish_Default_Set( + uint32_t object_instance, + BACNET_BINARY_PV value); + + bool Binary_Output_Encode_Value_List( + uint32_t object_instance, + BACNET_PROPERTY_VALUE * value_list); + bool Binary_Output_Change_Of_Value( + uint32_t instance); + void Binary_Output_Change_Of_Value_Clear( + uint32_t instance); + + int Binary_Output_Read_Property( + BACNET_READ_PROPERTY_DATA * rpdata); + bool Binary_Output_Write_Property( + BACNET_WRITE_PROPERTY_DATA * wp_data); + + bool Binary_Output_Create( + uint32_t object_instance); + bool Binary_Output_Delete( + uint32_t object_instance); + void Binary_Output_Cleanup( + void); + +#ifdef TEST +#include "ctest.h" + void testBinaryOutput( + Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/ports/esp32/src/device.c b/ports/esp32/src/device.c new file mode 100644 index 0000000..0bdca20 --- /dev/null +++ b/ports/esp32/src/device.c @@ -0,0 +1,2020 @@ +/************************************************************************** +* +* 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 "datalink.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bacapp.h" +#include "config.h" /* the custom stuff */ +#include "apdu.h" +#include "wp.h" /* WriteProperty handling */ +#include "rp.h" /* ReadProperty handling */ +#include "dcc.h" /* DeviceCommunicationControl handling */ +#include "version.h" +#include "device.h" /* me */ +#include "handlers.h" +#include "../lib/stack/address.h" +/* os specfic includes */ +#include "timer.h" +/* include the device object */ +#include "device.h" +#include "ai.h" +#include "bo.h" + +#if defined(INTRINSIC_REPORTING) +#include "nc.h" +#endif /* defined(INTRINSIC_REPORTING) */ +#if defined(BACFILE) +#include "bacfile.h" +#endif /* defined(BACFILE) */ +#if defined(BAC_UCI) +#include "ucix.h" +#endif /* defined(BAC_UCI) */ + + +/* Not included in time.h as specified by The Open Group */ +/* Difference from UTC and local standard time */ +long int timezone; + + +/* local forward (semi-private) and external prototypes */ +int Device_Read_Property_Local( + BACNET_READ_PROPERTY_DATA * rpdata); +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); + +/* may be overridden by outside table */ +static object_functions_t *Object_Table; + +static object_functions_t My_Object_Table[] = { + {OBJECT_DEVICE, + NULL /* Init - don't init Device or it will recourse! */ , + Device_Count, + Device_Index_To_Instance, + Device_Valid_Object_Instance_Number, + Device_Object_Name, + Device_Read_Property_Local, + Device_Write_Property_Local, + Device_Property_Lists, + DeviceGetRRInfo, + NULL /* Iterator */ , + NULL /* Value_Lists */ , + NULL , + NULL, + NULL /* Intrinsic Reporting */ }, + {OBJECT_ANALOG_INPUT, + Analog_Input_Init, + Analog_Input_Count, + Analog_Input_Index_To_Instance, + Analog_Input_Valid_Instance, + Analog_Input_Object_Name, + Analog_Input_Read_Property, + Analog_Input_Write_Property, + Analog_Input_Property_Lists, + NULL /* ReadRangeInfo */ , + NULL /* Iterator */ , + Analog_Input_Encode_Value_List, + Analog_Input_Change_Of_Value, + Analog_Input_Change_Of_Value_Clear, + Analog_Input_Intrinsic_Reporting}, + {OBJECT_BINARY_OUTPUT, + Binary_Output_Init, + Binary_Output_Count, + Binary_Output_Index_To_Instance, + Binary_Output_Valid_Instance, + Binary_Output_Object_Name, + Binary_Output_Read_Property, + Binary_Output_Write_Property, + Binary_Output_Property_Lists, + NULL /* ReadRangeInfo */ , + NULL /* Iterator */ , + NULL /* Value_Lists */ , + NULL /* COV */ , + NULL /* COV Clear */ , + NULL /* Intrinsic Reporting */ }, + {MAX_BACNET_OBJECT_TYPE, + NULL /* Init */ , + NULL /* Count */ , + NULL /* Index_To_Instance */ , + NULL /* Valid_Instance */ , + NULL /* Object_Name */ , + NULL /* Read_Property */ , + NULL /* Write_Property */ , + NULL /* Property_Lists */ , + NULL /* ReadRangeInfo */ , + NULL /* Iterator */ , + NULL /* Value_Lists */ , + NULL /* COV */ , + NULL /* COV Clear */ , + NULL /* Intrinsic Reporting */ } +}; +/** 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; + 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); +} + +/** 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) +{ + struct object_functions *pObject = NULL; + + (void)object_instance; + 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: + case BACNET_REINIT_WARMSTART: + dcc_set_status_duration(COMMUNICATION_ENABLE, 0); + 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, + PROP_DEVICE_ADDRESS_BINDING, + PROP_DATABASE_REVISION, + -1 +}; + +static const int Device_Properties_Optional[] = { +#if defined(BACDL_MSTP) + PROP_MAX_MASTER, + PROP_MAX_INFO_FRAMES, +#endif + PROP_DESCRIPTION, + PROP_LOCAL_TIME, + PROP_UTC_OFFSET, + PROP_LOCAL_DATE, + PROP_DAYLIGHT_SAVINGS_STATUS, + PROP_LOCATION, + PROP_ACTIVE_COV_SUBSCRIPTIONS, +#if defined(BACNET_TIME_MASTER) + PROP_TIME_SYNCHRONIZATION_RECIPIENTS, + PROP_TIME_SYNCHRONIZATION_INTERVAL, + PROP_ALIGN_INTERVALS, + PROP_INTERVAL_OFFSET, +#endif + -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; +} + +/* note: you really only need to define variables for + properties that are writable or that may change. + The properties that are constant can be hard coded + into the read-property encoding. */ + +static uint32_t Object_Instance_Number = 260001; +static BACNET_CHARACTER_STRING My_Object_Name; +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] = "ESP32"; +static char Application_Software_Version[MAX_DEV_VER_LEN + 1] = "1.0"; +static char Location[MAX_DEV_LOC_LEN + 1] = "FR"; +static char Description[MAX_DEV_DESC_LEN + 1] = "EPS32 by F. Chaxel, Stack S. Karg"; +static char *BACnet_Version = "0.8.2"; +/* 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 */ +#if defined(BACNET_TIME_MASTER) +static bool Align_Intervals; +static uint32_t Interval_Minutes; +static uint32_t Interval_Offset_Minutes; +/* Time_Synchronization_Recipients */ +#endif +/* List_Of_Session_Keys */ +/* 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; +} + +uint32_t Device_Index_To_Instance( + unsigned index) +{ + index = index; + return Object_Instance_Number; +} + +/* 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) +{ +#ifdef BAC_ROUTING + return Routed_Device_Object_Instance_Number(); +#else + return Object_Instance_Number; +#endif +} + +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 */ + Object_Instance_Number = object_id; + Device_Inc_Database_Revision(); + } else + status = false; + + return status; +} + +bool Device_Valid_Object_Instance_Number( + uint32_t object_id) +{ + return (Object_Instance_Number == object_id); +} + +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; +} + +bool Device_Object_Name_ANSI_Init(const char * value) +{ + return characterstring_init_ansi(&My_Object_Name, value); +} + +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 Description; +} + +bool Device_Set_Description( + const char *name, + size_t length) +{ + bool status = false; /*return value */ + + if (length < sizeof(Description)) { + memmove(Description, name, length); + Description[length] = 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; + 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( + uint32_t array_index, + int *object_type, + uint32_t * instance) +{ + bool status = false; + uint32_t count = 0; + uint32_t object_index = 0; + uint32_t 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; + 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( + BACNET_CHARACTER_STRING * object_name1, + int *object_type, + uint32_t * object_instance) +{ + bool found = false; + int type = 0; + uint32_t instance; + uint32_t max_objects = 0, i = 0; + bool check_id = false; + BACNET_CHARACTER_STRING object_name2; + struct object_functions *pObject = NULL; + + max_objects = Device_Object_List_Count(); + for (i = 1; i <= max_objects; i++) { + check_id = Device_Object_List_Identifier(i, &type, &instance); + if (check_id) { + pObject = Device_Objects_Find_Functions(type); + if ((pObject != NULL) && (pObject->Object_Name != NULL) && + (pObject->Object_Name(instance, &object_name2) && + characterstring_same(object_name1, &object_name2))) { + 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 True if found, else False if no such Object in this device. + */ +bool Device_Valid_Object_Id( + int object_type, + uint32_t object_instance) +{ + bool status = false; /* return value */ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if ((pObject != NULL) && (pObject->Object_Valid_Instance != NULL)) { + status = pObject->Object_Valid_Instance(object_instance); + } + + 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) +{ + struct object_functions *pObject = NULL; + bool found = false; + + pObject = Device_Objects_Find_Functions(object_type); + if ((pObject != NULL) && (pObject->Object_Name != NULL)) { + found = pObject->Object_Name(object_instance, object_name); + } + + return found; +} + +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 = (struct tm *)localtime(&tTemp); +#else + if (gettimeofday(&tv, NULL) == 0) { + tblock = (struct tm *)localtime((const time_t *)&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; + } +} + +void Device_getCurrentDateTime( + BACNET_DATE_TIME * DateTime) +{ + Update_Current_Time(); + + DateTime->date = Local_Date; + DateTime->time = Local_Time; +} + +int32_t Device_UTC_Offset(void) +{ + Update_Current_Time(); + + return UTC_Offset; +} + +void Device_UTC_Offset_Set(int16_t offset) +{ + UTC_Offset = offset; +} + +bool Device_Daylight_Savings_Status(void) +{ + return Daylight_Savings_Status; +} + +#if defined(BACNET_TIME_MASTER) +/** + * Sets the time sync interval in minutes + * + * @param flag + * This property, of type BOOLEAN, specifies whether (TRUE) + * or not (FALSE) clock-aligned periodic time synchronization is + * enabled. If periodic time synchronization is enabled and the + * time synchronization interval is a factor of (divides without + * remainder) an hour or day, then the beginning of the period + * specified for time synchronization shall be aligned to the hour or + * day, respectively. If this property is present, it shall be writable. + */ +bool Device_Align_Intervals_Set(bool flag) +{ + Align_Intervals = flag; + + return true; +} + +bool Device_Align_Intervals(void) +{ + return Align_Intervals; +} + +/** + * Sets the time sync interval in minutes + * + * @param minutes + * This property, of type Unsigned, specifies the periodic + * interval in minutes at which TimeSynchronization and + * UTCTimeSynchronization requests shall be sent. If this + * property has a value of zero, then periodic time synchronization is + * disabled. If this property is present, it shall be writable. + */ +bool Device_Time_Sync_Interval_Set(uint32_t minutes) +{ + Interval_Minutes = minutes; + + return true; +} + +uint32_t Device_Time_Sync_Interval(void) +{ + return Interval_Minutes; +} + +/** + * Sets the time sync interval offset value. + * + * @param minutes + * This property, of type Unsigned, specifies the offset in + * minutes from the beginning of the period specified for time + * synchronization until the actual time synchronization requests + * are sent. The offset used shall be the value of Interval_Offset + * modulo the value of Time_Synchronization_Interval; + * e.g., if Interval_Offset has the value 31 and + * Time_Synchronization_Interval is 30, the offset used shall be 1. + * Interval_Offset shall have no effect if Align_Intervals is + * FALSE. If this property is present, it shall be writable. + */ +bool Device_Interval_Offset_Set(uint32_t minutes) +{ + Interval_Offset_Minutes = minutes; + + return true; +} + +uint32_t Device_Interval_Offset(void) +{ + return Interval_Offset_Minutes; +} +#endif + +/* 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 */ + int len = 0; /* apdu len intermediate value */ + BACNET_BIT_STRING bit_string = { 0 }; + BACNET_CHARACTER_STRING char_string = { 0 }; + uint32_t i = 0; + int object_type = 0; + uint32_t instance = 0; + uint32_t count = 0; + uint8_t *apdu = NULL; + struct object_functions *pObject = NULL; + bool found = false; + uint16_t apdu_max = 0; + + if ((rpdata == NULL) || (rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + apdu = rpdata->application_data; + apdu_max = rpdata->application_data_len; + switch (rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = + encode_application_object_id(&apdu[0], OBJECT_DEVICE, + Object_Instance_Number); + break; + case PROP_OBJECT_NAME: + apdu_len = + encode_application_character_string(&apdu[0], &My_Object_Name); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_application_enumerated(&apdu[0], OBJECT_DEVICE); + break; + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, 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; + 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? Don't check for last entry */ + if ((i != count) && (apdu_len + len) >= apdu_max) { + /* 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: + apdu_len = address_list_encode(&apdu[0], apdu_max); + 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 +#if defined(BACNET_TIME_MASTER) + case PROP_TIME_SYNCHRONIZATION_RECIPIENTS: + apdu_len = handler_timesync_encode_recipients(&apdu[0], MAX_APDU); + if (apdu_len < 0) { + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + apdu_len = BACNET_STATUS_ABORT; + } + break; + case PROP_TIME_SYNCHRONIZATION_INTERVAL: + apdu_len = encode_application_unsigned(&apdu[0], + Device_Time_Sync_Interval()); + break; + case PROP_ALIGN_INTERVALS: + apdu_len = + encode_application_boolean(&apdu[0], + Device_Align_Intervals()); + break; + case PROP_INTERVAL_OFFSET: + apdu_len = encode_application_unsigned(&apdu[0], + Device_Interval_Offset()); + break; +#endif + case PROP_ACTIVE_COV_SUBSCRIPTIONS: + apdu_len = handler_cov_encode_subscriptions(&apdu[0], apdu_max); + 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; +#if (BACNET_PROTOCOL_REVISION >= 14) + struct special_property_list_t property_list; +#endif + + /* 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) { +#if (BACNET_PROTOCOL_REVISION >= 14) + if ((int)rpdata->object_property == 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); + } else +#endif + { + apdu_len = pObject->Object_Read_Property(rpdata); + } + } + } + } + + return apdu_len; +} + +/* returns true if successful */ +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 object_type = 0; + uint32_t object_instance = 0; + int temp; + + /* 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; + } + if ((wp_data->object_property != PROP_OBJECT_LIST) && + (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; + } + /* 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) && + (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_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, + characterstring_capacity(&My_Object_Name), false, + &wp_data->error_class, &wp_data->error_code); + if (status) { + /* All the object names in a device must be unique */ + if (Device_Valid_Object_Name(&value.type.Character_String, + &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 { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_DUPLICATE_NAME; + } + } else { + Device_Set_Object_Name(&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) { + Device_Set_Description(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(BACNET_TIME_MASTER) + case PROP_TIME_SYNCHRONIZATION_INTERVAL: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if (value.type.Unsigned_Int < 65535) { + minutes = value.type.Unsigned_Int; + Device_Time_Sync_Interval_Set(minutes); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_ALIGN_INTERVALS: + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + Device_Align_Intervals_Set(value.type.Boolean); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_INTERVAL_OFFSET: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if (value.type.Unsigned_Int < 65535) { + minutes = value.type.Unsigned_Int; + Device_Interval_Offset_Set(minutes); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; +#else + case PROP_TIME_SYNCHRONIZATION_INTERVAL: + case PROP_ALIGN_INTERVALS: + case PROP_INTERVAL_OFFSET: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + break; +#endif + case PROP_UTC_OFFSET: + if (value.tag == BACNET_APPLICATION_TAG_SIGNED_INT) { + if ((value.type.Signed_Int < (12*60)) && + (value.type.Signed_Int > (-12*60))) { + Device_UTC_Offset_Set(value.type.Signed_Int); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + 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; +#else + case PROP_MAX_INFO_FRAMES: + case PROP_MAX_MASTER: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + break; +#endif + case PROP_OBJECT_TYPE: + case PROP_VENDOR_NAME: + case PROP_FIRMWARE_REVISION: + case PROP_APPLICATION_SOFTWARE_VERSION: + case PROP_LOCAL_TIME: + case PROP_LOCAL_DATE: + case PROP_DAYLIGHT_SAVINGS_STATUS: + case PROP_PROTOCOL_VERSION: + case PROP_PROTOCOL_REVISION: + case PROP_PROTOCOL_SERVICES_SUPPORTED: + case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED: + case PROP_OBJECT_LIST: + case PROP_MAX_APDU_LENGTH_ACCEPTED: + case PROP_SEGMENTATION_SUPPORTED: + case PROP_DEVICE_ADDRESS_BINDING: + case PROP_DATABASE_REVISION: + case PROP_ACTIVE_COV_SUBSCRIPTIONS: +#if defined(BACNET_TIME_MASTER) + case PROP_TIME_SYNCHRONIZATION_RECIPIENTS: +#endif + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + default: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + 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) { +#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; + } else +#endif + { + 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); +} + +/** Looks up the requested Object, and fills the Property Value list. + * If the Object or Property can't be found, returns false. + * @ingroup ObjHelpers + * @param [in] The object type to be looked up. + * @param [in] The object instance number to be looked up. + * @param [out] The value list + * @return True if the object instance supports this feature and value changed. + */ +bool Device_Encode_Value_List( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_VALUE * value_list) +{ + bool status = false; /* Ever the pessamist! */ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(object_instance)) { + if (pObject->Object_Value_List) { + status = + pObject->Object_Value_List(object_instance, value_list); + } + } + } + + return (status); +} + +/** Checks the COV flag in the requested Object + * @ingroup ObjHelpers + * @param [in] The object type to be looked up. + * @param [in] The object instance to be looked up. + * @return True if the COV flag is set + */ +bool Device_COV( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance) +{ + bool status = false; /* Ever the pessamist! */ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(object_instance)) { + if (pObject->Object_COV) { + status = pObject->Object_COV(object_instance); + } + } + } + + return (status); +} + +/** Clears the COV flag in the requested Object + * @ingroup ObjHelpers + * @param [in] The object type to be looked up. + * @param [in] The object instance to be looked up. + */ +void Device_COV_Clear( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance) +{ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(object_instance)) { + if (pObject->Object_COV_Clear) { + pObject->Object_COV_Clear(object_instance); + } + } + } +} + +#if defined(INTRINSIC_REPORTING) +void Device_local_reporting( + void) +{ + struct object_functions *pObject; + uint32_t objects_count; + uint32_t object_instance; + int object_type; + uint32_t idx; + + objects_count = Device_Object_List_Count(); + + /* loop for all objects */ + for (idx = 1; idx <= objects_count; idx++) { + Device_Object_List_Identifier(idx, &object_type, &object_instance); + + pObject = Device_Objects_Find_Functions(object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(object_instance)) { + if (pObject->Object_Intrinsic_Reporting) { + pObject->Object_Intrinsic_Reporting(object_instance); + } + } + } + } +} +#endif + +/** Looks up the requested Object to see if the functionality is supported. + * @ingroup ObjHelpers + * @param [in] The object type to be looked up. + * @return True if the object instance supports this feature. + */ +bool Device_Value_List_Supported( + BACNET_OBJECT_TYPE object_type) +{ + bool status = false; /* Ever the pessamist! */ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + if (pObject != NULL) { + if (pObject->Object_Value_List) { + status = true; + } + } + + 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 object_functions *pObject = NULL; +#if defined(BAC_UCI) + const char *uciname; + struct uci_context *ctx; + fprintf(stderr, "Device_Init\n"); + ctx = ucix_init("bacnet_dev"); + if (!ctx) + fprintf(stderr, "Failed to load config file bacnet_dev\n"); + uciname = ucix_get_option(ctx, "bacnet_dev", "0", "Name"); + if (uciname != 0) { + characterstring_init_ansi(&My_Object_Name, uciname); + } else { +#endif /* defined(BAC_UCI) */ + characterstring_init_ansi(&My_Object_Name, "ESP32 SimpleServer"); +#if defined(BAC_UCI) + } + ucix_cleanup(ctx); +#endif /* defined(BAC_UCI) */ + + if (object_table) { + Object_Table = object_table; + } else { + Object_Table = &My_Object_Table[0]; + } + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Init) { + pObject->Object_Init(); + } + pObject++; + } +} + +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 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; + + /* 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; + 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 +#include "ctest.h" + +bool WPValidateArgType( + BACNET_APPLICATION_DATA_VALUE * pValue, + uint8_t ucExpectedTag, + BACNET_ERROR_CLASS * pErrorClass, + BACNET_ERROR_CODE * pErrorCode) +{ + pValue = pValue; + ucExpectedTag = ucExpectedTag; + pErrorClass = pErrorClass; + pErrorCode = pErrorCode; + + return false; +} + +bool WPValidateString( + BACNET_APPLICATION_DATA_VALUE * pValue, + int iMaxLen, + bool bEmptyAllowed, + BACNET_ERROR_CLASS * pErrorClass, + BACNET_ERROR_CODE * pErrorCode) +{ + pValue = pValue; + iMaxLen = iMaxLen; + bEmptyAllowed = bEmptyAllowed; + pErrorClass = pErrorClass; + pErrorCode = pErrorCode; + + return false; +} + +int handler_cov_encode_subscriptions( + uint8_t * apdu, + int max_apdu) +{ + apdu = apdu; + max_apdu = max_apdu; + + return 0; +} + +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/ports/esp32/src/device.h b/ports/esp32/src/device.h new file mode 100644 index 0000000..77dbaab --- /dev/null +++ b/ports/esp32/src/device.h @@ -0,0 +1,476 @@ +/************************************************************************** +* +* 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 object_instance [in] The object instance number to be looked up. + * @param object_name [in,out] Pointer to a character_string structure that + * will hold a copy of the object name if this is a valid object_instance. + * @return True if the object_instance is valid and object_name has been + * filled with a copy of the Object's name. + */ +typedef bool( + *object_name_function) + ( + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name); + +/** 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 contiguious 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); + +/** Look in the table of objects of this type, and get the COV Value List. + * @ingroup ObjHelpers + * @param [in] The object instance number to be looked up. + * @param [out] The value list + * @return True if the object instance supports this feature, and has changed. + */ +typedef bool( + *object_value_list_function) ( + uint32_t object_instance, + BACNET_PROPERTY_VALUE * value_list); + +/** Look in the table of objects for this instance to see if value changed. + * @ingroup ObjHelpers + * @param [in] The object instance number to be looked up. + * @return True if the object instance has changed. + */ +typedef bool( + *object_cov_function) ( + uint32_t object_instance); + +/** Look in the table of objects for this instance to clear the changed flag. + * @ingroup ObjHelpers + * @param [in] The object instance number to be looked up. + */ +typedef void ( + *object_cov_clear_function) ( + uint32_t object_instance); + +/** Intrinsic Reporting funcionality. + * @ingroup ObjHelpers + * @param [in] Object instance. + */ +typedef void ( + *object_intrinsic_reporting_function) ( + uint32_t object_instance); + + +/** 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, or configure the function as NULL. + * In both appearance and operation, this group of functions acts like + * they are member functions of a C++ Object base class. + */ +typedef 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_value_list_function Object_Value_List; + object_cov_function Object_COV; + object_cov_clear_function Object_COV_Clear; + object_intrinsic_reporting_function Object_Intrinsic_Reporting; +} object_functions_t; + +/* 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 */ + + void Device_Init( + object_functions_t * object_table); + + bool Device_Reinitialize( + BACNET_REINITIALIZE_DEVICE_DATA * rd_data); + bool Device_Reinitialize_State_Set(BACNET_REINITIALIZED_STATE state); + BACNET_REINITIALIZED_STATE Device_Reinitialized_State( + void); + + rr_info_function Device_Objects_RR_Info( + BACNET_OBJECT_TYPE object_type); + + void Device_getCurrentDateTime( + BACNET_DATE_TIME * DateTime); + + int32_t Device_UTC_Offset(void); + void Device_UTC_Offset_Set(int16_t offset); + + bool Device_Daylight_Savings_Status(void); + bool Device_Align_Intervals(void); + bool Device_Align_Intervals_Set(bool flag); + uint32_t Device_Time_Sync_Interval(void); + bool Device_Time_Sync_Interval_Set(uint32_t value); + uint32_t Device_Interval_Offset(void); + bool Device_Interval_Offset_Set(uint32_t value); + + void Device_Property_Lists( + const int **pRequired, + const int **pOptional, + const int **pProprietary); + void Device_Objects_Property_List( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + struct special_property_list_t *pPropertyList); + /* functions to support COV */ + bool Device_Encode_Value_List( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_VALUE * value_list); + bool Device_Value_List_Supported( + BACNET_OBJECT_TYPE object_type); + bool Device_COV( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance); + void Device_COV_Clear( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance); + + 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( + uint32_t array_index, + int *object_type, + uint32_t * instance); + + unsigned Device_Count( + void); + uint32_t Device_Index_To_Instance( + unsigned index); + + bool Device_Object_Name( + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name); + bool Device_Set_Object_Name( + BACNET_CHARACTER_STRING * object_name); + /* Copy a child object name, given its ID. */ + bool Device_Object_Name_Copy( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name); + bool Device_Object_Name_ANSI_Init(const char * object_name); + char * Device_Object_Name_ANSI(void); + + 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); + + 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( + BACNET_CHARACTER_STRING * object_name, + int *object_type, + uint32_t * object_instance); + bool 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 */ + + int Device_Read_Property_Local( + BACNET_READ_PROPERTY_DATA * rpdata); + bool Device_Write_Property_Local( + BACNET_WRITE_PROPERTY_DATA * wp_data); + +#if defined(INTRINSIC_REPORTING) + void Device_local_reporting( + void); +#endif + +/* 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, + BACNET_CHARACTER_STRING * Object_Name, + const char *Description); + DEVICE_OBJECT_DATA *Get_Routed_Device_Object( + int idx); + BACNET_ADDRESS *Get_Routed_Device_Address( + int idx); + + void routed_get_my_address( + BACNET_ADDRESS * my_address); + + bool Routed_Device_Address_Lookup( + int idx, + uint8_t address_len, + uint8_t * mac_adress); + bool Routed_Device_GetNext( + BACNET_ADDRESS * dest, + int *DNET_list, + int *cursor); + bool Routed_Device_Is_Valid_Network( + uint16_t dest_net, + int *DNET_list); + + uint32_t Routed_Device_Index_To_Instance( + unsigned index); + bool Routed_Device_Valid_Object_Instance_Number( + uint32_t object_id); + bool Routed_Device_Name( + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name); + 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( + uint8_t encoding, + const char *value, + size_t length); + bool Routed_Device_Set_Description( + const char *name, + size_t length); + void Routed_Device_Inc_Database_Revision( + void); + int Routed_Device_Service_Approval( + BACNET_CONFIRMED_SERVICE service, + int service_argument, + uint8_t * apdu_buff, + uint8_t invoke_id); + + + +#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/ports/esp32/src/main.c b/ports/esp32/src/main.c new file mode 100644 index 0000000..6a0296b --- /dev/null +++ b/ports/esp32/src/main.c @@ -0,0 +1,215 @@ +// +// Copyleft F.Chaxel 2017 +// + +#include "config.h" +#include "txbuf.h" +#include "client.h" + +#include "handlers.h" +#include "datalink.h" +#include "dcc.h" +#include "tsm.h" +// conflict filename address.h with another file in default include paths +#include "../lib/stack/address.h" +#include "bip.h" + +#include "device.h" +#include "ai.h" +#include "bo.h" + +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "nvs_flash.h" + +#include "driver/gpio.h" + +#include "lwip/sockets.h" +#include "lwip/netdb.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +// hidden function not in any .h files +extern uint8_t temprature_sens_read(); +extern uint32_t hall_sens_read(); + +// Wifi params +wifi_config_t wifi_config = { + .sta = { + .ssid = "myWifi", + .password = "myPass", + }, +}; + +// GPIO 5 has a Led on Sparkfun ESP32 board +#define BACNET_LED 5 + +uint8_t Handler_Transmit_Buffer[MAX_PDU] = { 0 }; +uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +EventGroupHandle_t wifi_event_group; +const static int CONNECTED_BIT = BIT0; + +/* BACnet handler, stack init, IAm */ +void StartBACnet() +{ + /* we need to handle who-is to support dynamic device binding */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); + + /* 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); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_SUBSCRIBE_COV, + handler_cov_subscribe); + + address_init(); + bip_init(NULL); + Send_I_Am(&Handler_Transmit_Buffer[0]); +} + +/* wifi events handler : start & stop bacnet with an event */ +esp_err_t wifi_event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_CONNECTED: + break ; + case SYSTEM_EVENT_STA_GOT_IP: + if (xEventGroupGetBits(wifi_event_group)!=CONNECTED_BIT) + { + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + StartBACnet(); + } + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + /* This is a workaround as ESP32 WiFi libs don't currently + auto-reassociate. */ + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + bip_cleanup(); + break; + default: + break; + } + return ESP_OK; +} + +/* tcpip & wifi station start */ + +void wifi_init_station(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + esp_event_loop_init(wifi_event_handler, NULL); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + esp_wifi_init(&cfg); + + esp_wifi_set_storage(WIFI_STORAGE_RAM); + esp_wifi_set_mode(WIFI_MODE_STA); + + esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config); + + esp_wifi_start() ; +} + +/* setup gpio & nv flash, call wifi init code */ +void setup() +{ + gpio_pad_select_gpio(BACNET_LED); + gpio_set_direction(BACNET_LED, GPIO_MODE_OUTPUT); + + gpio_set_level(BACNET_LED,0); + + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) + { + nvs_flash_erase(); + ret = nvs_flash_init(); + } + wifi_init_station(); +} + +/* Bacnet Task */ +void BACnetTask(void *pvParameters) +{ + uint16_t pdu_len = 0; + BACNET_ADDRESS src = { + 0 + }; + unsigned timeout = 1; + + // Init Bacnet objets dictionnary + Device_Init(NULL); + Device_Set_Object_Instance_Number(12); + + setup(); + + uint32_t tickcount=xTaskGetTickCount(); + + for (;;) + { + vTaskDelay(10 / portTICK_PERIOD_MS); // could be remove to speed the code + + // do nothing if not connected to wifi + xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY); + { + uint32_t newtick=xTaskGetTickCount(); + + // one second elapse at least (maybe much more if Wifi was deconnected for a long) + if ((newtick=configTICK_RATE_HZ)) + { + tickcount=newtick; + dcc_timer_seconds(1); + bvlc_maintenance_timer(1); + handler_cov_timer_seconds(1); + tsm_timer_milliseconds(1000); + + // Read analog values from internal sensors + Analog_Input_Present_Value_Set(0,temprature_sens_read()); + Analog_Input_Present_Value_Set(1,hall_sens_read()); + + } + + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + if (pdu_len) + { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + + if(Binary_Output_Present_Value(0)==BINARY_ACTIVE) + gpio_set_level(BACNET_LED,1); + else + gpio_set_level(BACNET_LED,0); + } + + handler_cov_task(); + } + } +} +/* Entry point */ +void app_main() +{ + // Cannot run BACnet code here, the default stack size is to small : 4096 byte + xTaskCreate( + BACnetTask, /* Function to implement the task */ + "BACnetTask", /* Name of the task */ + 10000, /* Stack size in words */ + NULL, /* Task input parameter */ + 20, /* Priority of the task */ + NULL); /* Task handle. */ +} \ No newline at end of file diff --git a/ports/esp32/src/sdkconfig.h b/ports/esp32/src/sdkconfig.h new file mode 100644 index 0000000..a3b619f --- /dev/null +++ b/ports/esp32/src/sdkconfig.h @@ -0,0 +1,125 @@ +/* + * + * Automatically generated file; DO NOT EDIT. + * Espressif IoT Development Framework Configuration + * + */ +#define CONFIG_GATTC_ENABLE 1 +#define CONFIG_ESP32_PHY_MAX_TX_POWER 20 +#define CONFIG_PHY_ENABLED 1 +#define CONFIG_TRACEMEM_RESERVE_DRAM 0x0 +#define CONFIG_FREERTOS_MAX_TASK_NAME_LEN 16 +#define CONFIG_BLE_SMP_ENABLE 1 +#define CONFIG_IPC_TASK_STACK_SIZE 1024 +#define CONFIG_ESPTOOLPY_FLASHFREQ "40m" +#define CONFIG_NEWLIB_STDOUT_ADDCR 1 +#define CONFIG_TASK_WDT_CHECK_IDLE_TASK 1 +#define CONFIG_ESPTOOLPY_FLASHSIZE "2MB" +#define CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER 1 +#define CONFIG_ETHERNET 1 +#define CONFIG_INT_WDT 1 +#define CONFIG_ESPTOOLPY_FLASHFREQ_40M 1 +#define CONFIG_LOG_BOOTLOADER_LEVEL_INFO 1 +#define CONFIG_ESPTOOLPY_FLASHSIZE_2MB 1 +#define CONFIG_AWS_IOT_MQTT_PORT 8883 +#define CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS 1 +#define CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM 10 +#define CONFIG_LOG_DEFAULT_LEVEL_INFO 1 +#define CONFIG_BT_RESERVE_DRAM 0x10000 +#define CONFIG_ESP32_PANIC_PRINT_REBOOT 1 +#define CONFIG_CONSOLE_UART_BAUDRATE 115200 +#define CONFIG_LWIP_MAX_SOCKETS 10 +#define CONFIG_EMAC_TASK_PRIORITY 20 +#define CONFIG_TIMER_TASK_STACK_DEPTH 2048 +#define CONFIG_FATFS_CODEPAGE 1 +#define CONFIG_ESP32_DEFAULT_CPU_FREQ_160 1 +#define CONFIG_ULP_COPROC_RESERVE_MEM 0 +#define CONFIG_ESPTOOLPY_BAUD 115200 +#define CONFIG_INT_WDT_CHECK_CPU1 1 +#define CONFIG_FLASHMODE_DIO 1 +#define CONFIG_ESPTOOLPY_AFTER_RESET 1 +#define CONFIG_TOOLPREFIX "xtensa-esp32-elf-" +#define CONFIG_FREERTOS_IDLE_TASK_STACKSIZE 1024 +#define CONFIG_ESP32_WIFI_AMPDU_ENABLED 1 +#define CONFIG_CONSOLE_UART_NUM 0 +#define CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC 1 +#define CONFIG_ESPTOOLPY_BAUD_115200B 1 +#define CONFIG_LWIP_THREAD_LOCAL_STORAGE_INDEX 0 +#define CONFIG_FOUR_UNIVERSAL_MAC_ADDRESS 1 +#define CONFIG_CONSOLE_UART_DEFAULT 1 +#define CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN 16384 +#define CONFIG_NUMBER_OF_UNIVERSAL_MAC_ADDRESS 4 +#define CONFIG_ESPTOOLPY_FLASHSIZE_DETECT 1 +#define CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE 1 +#define CONFIG_BTDM_CONTROLLER_RUN_CPU 0 +#define CONFIG_TCPIP_TASK_STACK_SIZE 2560 +#define CONFIG_TASK_WDT 1 +#define CONFIG_MAIN_TASK_STACK_SIZE 4096 +#define CONFIG_TASK_WDT_TIMEOUT_S 5 +#define CONFIG_INT_WDT_TIMEOUT_MS 300 +#define CONFIG_ESPTOOLPY_FLASHMODE "dio" +#define CONFIG_BTC_TASK_STACK_SIZE 3072 +#define CONFIG_BLUEDROID_ENABLED 1 +#define CONFIG_ESPTOOLPY_BEFORE "default_reset" +#define CONFIG_LOG_DEFAULT_LEVEL 3 +#define CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION 1 +#define CONFIG_TIMER_QUEUE_LENGTH 10 +#define CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM 32 +#define CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER 20 +#define CONFIG_ESP32_RTC_CLK_CAL_CYCLES 1024 +#define CONFIG_ESP32_WIFI_NVS_ENABLED 1 +#define CONFIG_AWS_IOT_SDK 1 +#define CONFIG_DMA_RX_BUF_NUM 10 +#define CONFIG_TCP_SYNMAXRTX 6 +#define CONFIG_PYTHON "python" +#define CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1 1 +#define CONFIG_ESPTOOLPY_COMPRESSED 1 +#define CONFIG_PARTITION_TABLE_FILENAME "partitions_singleapp.csv" +#define CONFIG_LWIP_DHCP_MAX_NTP_SERVERS 1 +#define CONFIG_PARTITION_TABLE_SINGLE_APP 1 +#define CONFIG_WIFI_ENABLED 1 +#define CONFIG_LWIP_DHCP_DOES_ARP_CHECK 1 +#define CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE 4096 +#define CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY 2000 +#define CONFIG_ESP32_APPTRACE_DEST_NONE 1 +#define CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET 0x10000 +#define CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM 32 +#define CONFIG_FATFS_CODEPAGE_ASCII 1 +#define CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1 1 +#define CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ 160 +#define CONFIG_FREERTOS_HZ 100 +#define CONFIG_LOG_COLORS 1 +#define CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE 1 +#define CONFIG_FREERTOS_ASSERT_FAIL_ABORT 1 +#define CONFIG_ESP32_XTAL_FREQ 0 +#define CONFIG_MONITOR_BAUD_115200B 1 +#define CONFIG_LOG_BOOTLOADER_LEVEL 3 +#define CONFIG_SMP_ENABLE 1 +#define CONFIG_ESPTOOLPY_BEFORE_RESET 1 +#define CONFIG_ESPTOOLPY_BAUD_OTHER_VAL 115200 +#define CONFIG_ESP32_XTAL_FREQ_AUTO 1 +#define CONFIG_TCP_MAXRTX 12 +#define CONFIG_ESPTOOLPY_AFTER "hard_reset" +#define CONFIG_DMA_TX_BUF_NUM 10 +#define CONFIG_ESP32_DEBUG_OCDAWARE 1 +#define CONFIG_TIMER_TASK_PRIORITY 1 +#define CONFIG_BT_ENABLED 1 +#define CONFIG_MONITOR_BAUD 115200 +#define CONFIG_FREERTOS_CORETIMER_0 1 +#define CONFIG_PARTITION_TABLE_CUSTOM_FILENAME "partitions.csv" +#define CONFIG_MBEDTLS_HAVE_TIME 1 +#define CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY 1 +#define CONFIG_GATTS_ENABLE 1 +#define CONFIG_FREERTOS_ISR_STACKSIZE 1536 +#define CONFIG_OPENSSL_ASSERT_DO_NOTHING 1 +#define CONFIG_AWS_IOT_MQTT_HOST "" +#define CONFIG_SYSTEM_EVENT_QUEUE_SIZE 32 +#define CONFIG_BT_ACL_CONNECTIONS 4 +#define CONFIG_FATFS_MAX_LFN 255 +#define CONFIG_ESP32_WIFI_TX_BUFFER_TYPE 1 +#define CONFIG_APP_OFFSET 0x10000 +#define CONFIG_MEMMAP_SMP 1 +#define CONFIG_SPI_FLASH_ROM_DRIVER_PATCH 1 +#define CONFIG_MONITOR_BAUD_OTHER_VAL 115200 +#define CONFIG_ESPTOOLPY_PORT "/dev/ttyUSB0" +#define CONFIG_OPTIMIZATION_LEVEL_RELEASE 1