Files
bacnet_stack/src/bacnet/basic/object/program.c
T
Hyeongjun Kim f408b6eb7f Add multi-device support for BACnet gateway routing (#1279)
* Expanded Object_List to Object_List[MAX_NUM_DEVICES] array to support per-device objects for virtual remote devices
* Added multi-device iteration for COV handler, device timer, and intrinsic reporting
* Added apps/gateway2 demo application
* When MAX_NUM_DEVICES == 1, behavior is identical to the original implementation
* Conditional compilation with macros ensures no impact on non-gateway applications

---------

Signed-off-by: Hyeongjun Kim <hjun1.kim@samsung.com>
Co-authored-by: haemeok-kang <haemeok.kang@samsung.com>
2026-03-30 15:16:17 -04:00

1483 lines
45 KiB
C

/**
* @file
* @author Steve Karg <skarg@users.sourceforge.net>
* @date March 2025
* @brief The Program object type defines a standardized object whose
* properties represent the externally visible characteristics of an
* application program.
* @copyright SPDX-License-Identifier: MIT
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <string.h>
#include <stdlib.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/bacdcode.h"
#include "bacnet/bacapp.h"
#include "bacnet/bactext.h"
#include "bacnet/proplist.h"
/* basic objects and services */
#include "bacnet/basic/object/device.h"
#include "bacnet/basic/services.h"
#include "bacnet/basic/sys/debug.h"
#include "bacnet/basic/sys/keylist.h"
/* me! */
#include "bacnet/basic/object/program.h"
/* Key List for storing the object data sorted by instance number */
static OS_Keylist Object_Lists[MAX_NUM_DEVICES];
#ifdef BAC_ROUTING
#define Object_List (Object_Lists[Routed_Device_Object_Index()])
#else
#define Object_List (Object_Lists[0])
#endif
/* common object type */
static const BACNET_OBJECT_TYPE Object_Type = OBJECT_PROGRAM;
struct object_data {
BACNET_PROGRAM_STATE Program_State;
BACNET_PROGRAM_REQUEST Program_Change;
BACNET_PROGRAM_ERROR Reason_For_Halt;
const char *Description_Of_Halt;
const char *Program_Location;
const char *Instance_Of;
const char *Description;
const char *Object_Name;
BACNET_RELIABILITY Reliability;
bool Out_Of_Service : 1;
bool Changed : 1;
void *Context;
/* return 0 for success, negative on error */
int (*Load)(void *context);
int (*Run)(void *context);
int (*Halt)(void *context);
int (*Restart)(void *context);
int (*Unload)(void *context);
};
/* These three arrays are used by the ReadPropertyMultiple handler */
static const int32_t Properties_Required[] = {
/* unordered list of required properties */
PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME,
PROP_OBJECT_TYPE, PROP_PROGRAM_STATE,
PROP_PROGRAM_CHANGE, PROP_STATUS_FLAGS,
PROP_OUT_OF_SERVICE, -1
};
static const int32_t Properties_Optional[] = {
/* unordered list of optional properties */
PROP_REASON_FOR_HALT,
PROP_DESCRIPTION_OF_HALT,
PROP_PROGRAM_LOCATION,
PROP_DESCRIPTION,
PROP_INSTANCE_OF,
PROP_RELIABILITY,
-1
};
static const int32_t Properties_Proprietary[] = { -1 };
/* Every object shall have a Writable Property_List property
which is a BACnetARRAY of property identifiers,
one property identifier for each property within this object
that is always writable. */
static const int32_t Writable_Properties[] = {
/* unordered list of always writable properties */
PROP_PROGRAM_CHANGE, PROP_OUT_OF_SERVICE, -1
};
/**
* Returns the list of required, optional, and proprietary properties.
* Used by ReadPropertyMultiple service.
*
* @param pRequired - pointer to list of int terminated by -1, of
* BACnet required properties for this object.
* @param pOptional - pointer to list of int terminated by -1, of
* BACnet optkional properties for this object.
* @param pProprietary - pointer to list of int terminated by -1, of
* BACnet proprietary properties for this object.
*/
void Program_Property_Lists(
const int32_t **pRequired,
const int32_t **pOptional,
const int32_t **pProprietary)
{
if (pRequired) {
*pRequired = Properties_Required;
}
if (pOptional) {
*pOptional = Properties_Optional;
}
if (pProprietary) {
*pProprietary = Properties_Proprietary;
}
return;
}
/**
* @brief Get the list of writable properties for a Program object
* @param object_instance - object-instance number of the object
* @param properties - Pointer to the pointer of writable properties.
*/
void Program_Writable_Property_List(
uint32_t object_instance, const int32_t **properties)
{
(void)object_instance;
if (properties) {
*properties = Writable_Properties;
}
}
/**
* @brief Gets an object from the list using an instance number as the key
* @param object_instance - object-instance number of the object
* @return object found in the list, or NULL if not found
*/
static struct object_data *Object_Data(uint32_t object_instance)
{
return Keylist_Data(Object_List, object_instance);
}
/**
* Determines if a given Integer Value instance is valid
*
* @param object_instance - object-instance number of the object
*
* @return true if the instance is valid, and false if not
*/
bool Program_Valid_Instance(uint32_t object_instance)
{
struct object_data *pObject = Object_Data(object_instance);
return (pObject != NULL);
}
/**
* Determines the number of Integer Value objects
*
* @return Number of Integer Value objects
*/
unsigned Program_Count(void)
{
return Keylist_Count(Object_List);
}
/**
* Determines the object instance-number for a given 0..N index
* of Integer Value objects where N is Program_Count().
*
* @param index - 0..MAX_PROGRAMS value
*
* @return object instance-number for the given index
*/
uint32_t Program_Index_To_Instance(unsigned index)
{
KEY key = UINT32_MAX;
Keylist_Index_Key(Object_List, index, &key);
return key;
}
/**
* For a given object instance-number, determines a 0..N index
* of Integer Value objects where N is Program_Count().
*
* @param object_instance - object-instance number of the object
*
* @return index for the given instance-number, or MAX_PROGRAMS
* if not valid.
*/
unsigned Program_Instance_To_Index(uint32_t object_instance)
{
return Keylist_Index(Object_List, object_instance);
}
/**
* For a given object instance-number, determines the program-state
*
* @param object_instance - object-instance number of the object
*
* @return program-state of the object
*/
BACNET_PROGRAM_STATE Program_State(uint32_t object_instance)
{
BACNET_PROGRAM_STATE value = 0;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
value = pObject->Program_State;
}
return value;
}
/**
* For a given object instance-number, sets the program-state
*
* @param object_instance - object-instance number of the object
* @param value - integer value
*
* @return true if values are within range and present-value is set.
*/
bool Program_State_Set(uint32_t object_instance, BACNET_PROGRAM_STATE value)
{
bool status = false;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
pObject->Program_State = value;
status = true;
}
return status;
}
/**
* For a given object instance-number, loads the object-name into
* a characterstring. Note that the object name must be unique
* within this device.
*
* @param object_instance - object-instance number of the object
* @param object_name - holds the object-name retrieved
*
* @return true if object-name was retrieved
*/
bool Program_Object_Name(
uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
{
char text[32] = "";
bool status = false;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
if (pObject->Object_Name) {
status =
characterstring_init_ansi(object_name, pObject->Object_Name);
} else {
snprintf(
text, sizeof(text), "PROGRAM-%lu",
(unsigned long)object_instance);
status = characterstring_init_ansi(object_name, text);
}
}
return status;
}
/**
* @brief For a given object instance-number, sets the object-name
* @param object_instance - object-instance number of the object
* @param new_name - holds the object-name to be set
* @return true if object-name was set
*/
bool Program_Name_Set(uint32_t object_instance, const char *new_name)
{
bool status = false;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
status = true;
pObject->Object_Name = new_name;
}
return status;
}
/**
* @brief Return the object name C string
* @param object_instance [in] BACnet object instance number
* @return object name or NULL if not found
*/
const char *Program_Name_ASCII(uint32_t object_instance)
{
const char *name = NULL;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
name = pObject->Object_Name;
}
return name;
}
/**
* For a given object instance-number, return the description.
* @param object_instance - object-instance number of the object
* @param description - description pointer
* @return true/false
*/
bool Program_Description(
uint32_t object_instance, BACNET_CHARACTER_STRING *description)
{
bool status = false;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
if (pObject->Description) {
status =
characterstring_init_ansi(description, pObject->Description);
} else {
status = characterstring_init_ansi(description, "");
}
}
return status;
}
/**
* @brief For a given object instance-number, sets the description
* @param object_instance - object-instance number of the object
* @param new_name - holds the description to be set
* @return true if string was set
*/
bool Program_Description_Set(uint32_t object_instance, const char *new_name)
{
bool status = false; /* return value */
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
status = true;
pObject->Description = new_name;
}
return status;
}
/**
* @brief For a given object instance-number, returns the description
* @param object_instance - object-instance number of the object
* @return description text or NULL if not found
*/
const char *Program_Description_ANSI(uint32_t object_instance)
{
const char *name = NULL;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
if (pObject->Description == NULL) {
name = "";
} else {
name = pObject->Description;
}
}
return name;
}
/**
* For a given object instance-number, return the Description_Of_Halt.
*
* @param object_instance - object-instance number of the object
* @param description - description pointer
*
* @return true/false
*/
bool Program_Description_Of_Halt(
uint32_t object_instance, BACNET_CHARACTER_STRING *description)
{
bool status = false;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
if (pObject->Description_Of_Halt) {
status = characterstring_init_ansi(
description, pObject->Description_Of_Halt);
} else {
status = characterstring_init_ansi(description, "");
}
}
return status;
}
/**
* @brief For a given object instance-number, sets the Description_Of_Halt
* @param object_instance - object-instance number of the object
* @param new_name - holds the description to be set
* @return true if string was set
*/
bool Program_Description_Of_Halt_Set(
uint32_t object_instance, const char *new_name)
{
bool status = false; /* return value */
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
status = true;
pObject->Description_Of_Halt = new_name;
}
return status;
}
/**
* @brief For a given object instance-number, returns the Description_Of_Halt
* @param object_instance - object-instance number of the object
* @return text or NULL if not found
*/
const char *Program_Description_Of_Halt_ANSI(uint32_t object_instance)
{
const char *name = NULL;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
if (pObject->Description_Of_Halt == NULL) {
name = "";
} else {
name = pObject->Description_Of_Halt;
}
}
return name;
}
/**
* For a given object instance-number, return the Description_Of_Halt.
*
* @param object_instance - object-instance number of the object
* @param description - description pointer
*
* @return true/false
*/
bool Program_Location(
uint32_t object_instance, BACNET_CHARACTER_STRING *description)
{
bool status = false;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
if (pObject->Program_Location) {
status = characterstring_init_ansi(
description, pObject->Program_Location);
} else {
status = characterstring_init_ansi(description, "");
}
}
return status;
}
/**
* @brief For a given object instance-number, sets the Program_Location
* @param object_instance - object-instance number of the object
* @param new_name - holds the description to be set
* @return true if string was set
*/
bool Program_Location_Set(uint32_t object_instance, const char *new_name)
{
bool status = false; /* return value */
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
status = true;
pObject->Program_Location = new_name;
}
return status;
}
/**
* @brief For a given object instance-number, returns the Program_Location
* @param object_instance - object-instance number of the object
* @return text or NULL if not found
*/
const char *Program_Location_ANSI(uint32_t object_instance)
{
const char *name = NULL;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
if (pObject->Program_Location == NULL) {
name = "";
} else {
name = pObject->Program_Location;
}
}
return name;
}
/**
* For a given object instance-number, return the Instance_Of string
*
* @param object_instance - object-instance number of the object
* @param description - description pointer
*
* @return true/false if the string was found
*/
bool Program_Instance_Of(
uint32_t object_instance, BACNET_CHARACTER_STRING *description)
{
bool status = false;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
if (pObject->Instance_Of) {
status =
characterstring_init_ansi(description, pObject->Instance_Of);
} else {
status = characterstring_init_ansi(description, "");
}
}
return status;
}
/**
* @brief For a given object instance-number, sets the Instance_Of
* @param object_instance - object-instance number of the object
* @param new_name - holds the description to be set
* @return true if string was set
*/
bool Program_Instance_Of_Set(uint32_t object_instance, const char *new_name)
{
bool status = false; /* return value */
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
status = true;
pObject->Instance_Of = new_name;
}
return status;
}
/**
* @brief For a given object instance-number, returns the Instance_Of
* @param object_instance - object-instance number of the object
* @return text or NULL if not found
*/
const char *Program_Instance_Of_ANSI(uint32_t object_instance)
{
const char *name = NULL;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
if (pObject->Instance_Of == NULL) {
name = "";
} else {
name = pObject->Instance_Of;
}
}
return name;
}
/**
* For a given object instance-number, returns the program change value
*
* @param object_instance - object-instance number of the object
*
* @return program change property value
*/
BACNET_PROGRAM_REQUEST Program_Change(uint32_t object_instance)
{
BACNET_PROGRAM_REQUEST program_change = PROGRAM_REQUEST_READY;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
program_change = pObject->Program_Change;
}
return program_change;
}
/**
* For a given object instance-number, sets the program change property value
*
* @param object_instance - object-instance number of the object
* @param program_change - property value
*
* @return true if the program change property value was set
*/
bool Program_Change_Set(
uint32_t object_instance, BACNET_PROGRAM_REQUEST program_change)
{
bool status = false;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
pObject->Program_Change = program_change;
status = true;
}
return status;
}
/**
* For a given object instance-number, writes the program change value
*
* Normally the value of the Program_Change property will be READY,
* meaning that the program is ready to accept a new
* request to change its operating state. If the Program_Change
* property is not READY, then it may not be written to and any
* attempt to write to the property shall return a Result(-).
* If it has one of the other enumerated values, then a previous
* request to change state has not yet been honored, so new requests
* cannot be accepted. When the request to change state is finally
* honored, then the Program_Change property value shall become
* READY and the new state shall be reflected in the Program_State property.
*
* @param object_instance - object-instance number of the object
* @param program_change - property value
* @param error_class - BACNET_ERROR_CLASS
* @param error_code - BACNET_ERROR_CODE
* @return true if the program change property value was written
*/
static bool Program_Change_Write(
uint32_t object_instance,
BACNET_PROGRAM_REQUEST program_change,
BACNET_ERROR_CLASS *error_class,
BACNET_ERROR_CODE *error_code)
{
bool status = false;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
if (pObject->Program_Change == PROGRAM_REQUEST_READY) {
if (program_change <= PROGRAM_REQUEST_MAX) {
pObject->Program_Change = program_change;
status = true;
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
}
} else {
*error_class = ERROR_CLASS_PROPERTY;
*error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
}
}
return status;
}
/**
* For a given object instance-number, returns the Reason_For_Halt
*
* @param object_instance - object-instance number of the object
*
* @return Reason_For_Halt property value
*/
BACNET_PROGRAM_ERROR Program_Reason_For_Halt(uint32_t object_instance)
{
BACNET_PROGRAM_ERROR reason = PROGRAM_ERROR_NORMAL;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
reason = pObject->Reason_For_Halt;
}
return reason;
}
/**
* For a given object instance-number, sets the Reason_For_Halt property value
*
* @param object_instance - object-instance number of the object
* @param program_change - property value
*
* @return true if the Reason_For_Halt property value was set
*/
bool Program_Reason_For_Halt_Set(
uint32_t object_instance, BACNET_PROGRAM_ERROR reason)
{
bool status = false;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
pObject->Reason_For_Halt = reason;
status = true;
}
return status;
}
/**
* For a given object instance-number, returns the out-of-service
* property value
*
* @param object_instance - object-instance number of the object
*
* @return out-of-service property value
*/
bool Program_Out_Of_Service(uint32_t object_instance)
{
struct object_data *pObject;
bool value = false;
pObject = Object_Data(object_instance);
if (pObject) {
value = pObject->Out_Of_Service;
}
return value;
}
/**
* For a given object instance-number, sets the out-of-service property value
*
* @param object_instance - object-instance number of the object
* @param value - boolean out-of-service value
*
* @return true if the out-of-service property value was set
*/
void Program_Out_Of_Service_Set(uint32_t object_instance, bool value)
{
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
pObject->Out_Of_Service = value;
}
}
/**
* @brief For a given object instance-number, gets the reliability.
* @param object_instance - object-instance number of the object
* @return reliability value
*/
BACNET_RELIABILITY Program_Reliability(uint32_t object_instance)
{
BACNET_RELIABILITY reliability = RELIABILITY_NO_FAULT_DETECTED;
struct object_data *pObject;
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
reliability = pObject->Reliability;
}
return reliability;
}
/**
* @brief For a given object instance-number, gets the Fault status flag
* @param object_instance - object-instance number of the object
* @return true the status flag is in Fault
*/
static bool Program_Fault(uint32_t object_instance)
{
struct object_data *pObject;
bool fault = false;
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
if (pObject->Reliability != RELIABILITY_NO_FAULT_DETECTED) {
fault = true;
}
}
return fault;
}
/**
* @brief For a given object instance-number, sets the reliability
* @param object_instance - object-instance number of the object
* @param value - reliability enumerated value
* @return true if values are within range and property is set.
*/
bool Program_Reliability_Set(uint32_t object_instance, BACNET_RELIABILITY value)
{
struct object_data *pObject;
bool status = false;
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
if (value <= 255) {
pObject->Reliability = value;
status = true;
}
}
return status;
}
/**
* ReadProperty handler for this object. For the given ReadProperty
* data, the application_data is loaded or the error flags are set.
*
* @param rpdata - BACNET_READ_PROPERTY_DATA data, including
* requested data and space for the reply, or error response.
*
* @return number of APDU bytes in the response, or
* BACNET_STATUS_ERROR on error.
*/
int Program_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
{
int apdu_len = 0; /* return value */
BACNET_BIT_STRING bit_string;
BACNET_CHARACTER_STRING char_string;
uint8_t *apdu = NULL;
uint32_t enum_value = 0;
bool state = false;
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_Type, rpdata->object_instance);
break;
case PROP_OBJECT_NAME:
Program_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_Type);
break;
case PROP_DESCRIPTION:
if (Program_Description(rpdata->object_instance, &char_string)) {
apdu_len =
encode_application_character_string(&apdu[0], &char_string);
}
break;
case PROP_STATUS_FLAGS:
bitstring_init(&bit_string);
bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
state = Program_Fault(rpdata->object_instance);
bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, state);
bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
state = Program_Out_Of_Service(rpdata->object_instance);
bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, state);
apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
break;
case PROP_OUT_OF_SERVICE:
state = Program_Out_Of_Service(rpdata->object_instance);
apdu_len = encode_application_boolean(&apdu[0], state);
break;
case PROP_PROGRAM_STATE:
enum_value = Program_State(rpdata->object_instance);
apdu_len = encode_application_enumerated(&apdu[0], enum_value);
break;
case PROP_PROGRAM_CHANGE:
enum_value = Program_Change(rpdata->object_instance);
apdu_len = encode_application_enumerated(&apdu[0], enum_value);
break;
case PROP_REASON_FOR_HALT:
enum_value = Program_Reason_For_Halt(rpdata->object_instance);
apdu_len = encode_application_enumerated(&apdu[0], enum_value);
break;
case PROP_DESCRIPTION_OF_HALT:
if (Program_Description_Of_Halt(
rpdata->object_instance, &char_string)) {
apdu_len =
encode_application_character_string(&apdu[0], &char_string);
}
break;
case PROP_PROGRAM_LOCATION:
if (Program_Location(rpdata->object_instance, &char_string)) {
apdu_len =
encode_application_character_string(&apdu[0], &char_string);
}
break;
case PROP_INSTANCE_OF:
if (Program_Instance_Of(rpdata->object_instance, &char_string)) {
apdu_len =
encode_application_character_string(&apdu[0], &char_string);
}
break;
case PROP_RELIABILITY:
enum_value = Program_Reliability(rpdata->object_instance);
apdu_len = encode_application_enumerated(&apdu[0], enum_value);
break;
default:
rpdata->error_class = ERROR_CLASS_PROPERTY;
rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
apdu_len = BACNET_STATUS_ERROR;
break;
}
return apdu_len;
}
/**
* WriteProperty handler for this object. For the given WriteProperty
* data, the application_data is loaded or the error flags are set.
*
* @param wp_data - BACNET_WRITE_PROPERTY_DATA data, including
* requested data and space for the reply, or error response.
*
* @return false if an error is loaded, true if no errors
*/
bool Program_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
{
bool status = false; /* return value */
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
/* 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;
}
switch (wp_data->object_property) {
case PROP_PROGRAM_CHANGE:
status = write_property_type_valid(
wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED);
if (status) {
status = Program_Change_Write(
wp_data->object_instance, value.type.Enumerated,
&wp_data->error_class, &wp_data->error_code);
}
break;
case PROP_OUT_OF_SERVICE:
status = write_property_type_valid(
wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN);
if (status) {
Program_Out_Of_Service_Set(
wp_data->object_instance, value.type.Boolean);
}
break;
default:
if (property_lists_member(
Properties_Required, Properties_Optional,
Properties_Proprietary, wp_data->object_property)) {
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_UNKNOWN_PROPERTY;
}
break;
}
return status;
}
/**
* @brief Set the context used with load, unload, run, halt, and restart
* @param object_instance [in] BACnet object instance number
* @param context [in] pointer to the context
*/
void *Program_Context_Get(uint32_t object_instance)
{
struct object_data *pObject = Object_Data(object_instance);
if (pObject) {
return pObject->Context;
}
return NULL;
}
/**
* @brief Set the context used with load, unload, run, halt, and restart
* @param object_instance [in] BACnet object instance number
* @param context [in] pointer to the context
*/
void Program_Context_Set(uint32_t object_instance, void *context)
{
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
pObject->Context = context;
}
}
/**
* @brief Set the Load function for the object
* @param object_instance [in] BACnet object instance number
* @param load [in] pointer to the Load function
* @note function should return 0 for success, negative on error
*/
void Program_Load_Set(uint32_t object_instance, int (*load)(void *context))
{
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
pObject->Load = load;
}
}
/**
* @brief Set the Run function for the object
* @param object_instance [in] BACnet object instance number
* @param run [in] pointer to the Run function
* @note function should return 0 for success, negative on error
*/
void Program_Run_Set(uint32_t object_instance, int (*run)(void *context))
{
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
pObject->Run = run;
}
}
/**
* @brief Set the Halt function for the object
* @param object_instance [in] BACnet object instance number
* @param halt [in] pointer to the Halt function
* @note function should return 0 for success, negative on error
*/
void Program_Halt_Set(uint32_t object_instance, int (*halt)(void *context))
{
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
pObject->Halt = halt;
}
}
/**
* @brief Set the Restart function for the object
* @param object_instance [in] BACnet object instance number
* @param restart [in] pointer to the Restart function
* @note function should return 0 for success, negative on error
*/
void Program_Restart_Set(
uint32_t object_instance, int (*restart)(void *context))
{
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
pObject->Restart = restart;
}
}
/**
* @brief Set the Unload function for the object
* @param object_instance [in] BACnet object instance number
* @param unload [in] pointer to the Unload function
* @note function should return 0 for success, negative on error
*/
void Program_Unload_Set(uint32_t object_instance, int (*unload)(void *context))
{
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
pObject->Unload = unload;
}
}
/**
* @brief Handle the IDLE state of the program
* @param pObject [in] pointer to the object data
*/
static void Program_State_Idle_Handler(struct object_data *pObject)
{
int err;
if (pObject->Program_Change == PROGRAM_REQUEST_LOAD) {
if (pObject->Load) {
err = pObject->Load(pObject->Context);
if (err == 0) {
pObject->Program_State = PROGRAM_STATE_LOADING;
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_LOAD_FAILED;
}
} else {
pObject->Program_State = PROGRAM_STATE_LOADING;
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
}
} else if (pObject->Program_Change == PROGRAM_REQUEST_RUN) {
if (pObject->Load) {
err = pObject->Load(pObject->Context);
if (err == 0) {
pObject->Program_State = PROGRAM_STATE_RUNNING;
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_LOAD_FAILED;
}
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_RUNNING;
}
} else if (pObject->Program_Change == PROGRAM_REQUEST_RESTART) {
if (pObject->Restart) {
err = pObject->Restart(pObject->Context);
if (err == 0) {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_RUNNING;
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_OTHER;
}
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_RUNNING;
}
}
}
/**
* @brief Handle the HALTED state of the program
* @param pObject [in] pointer to the object data
*/
static void Program_State_Halted_Handler(struct object_data *pObject)
{
int err;
if (pObject->Program_Change == PROGRAM_REQUEST_UNLOAD) {
if (pObject->Unload) {
err = pObject->Unload(pObject->Context);
if (err == 0) {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_UNLOADING;
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_LOAD_FAILED;
}
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_UNLOADING;
}
} else if (pObject->Program_Change == PROGRAM_REQUEST_LOAD) {
if (pObject->Load) {
err = pObject->Load(pObject->Context);
if (err == 0) {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_LOADING;
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_LOAD_FAILED;
}
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_LOADING;
}
} else if (pObject->Program_Change == PROGRAM_REQUEST_RUN) {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_RUNNING;
} else if (pObject->Program_Change == PROGRAM_REQUEST_RESTART) {
if (pObject->Restart) {
err = pObject->Restart(pObject->Context);
if (err == 0) {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_RUNNING;
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_OTHER;
}
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_RUNNING;
}
}
}
/**
* @brief Handle the RUNNING state of the program
* @param pObject [in] pointer to the object data
*/
static void Program_State_Running_Handler(struct object_data *pObject)
{
int err;
if (pObject->Program_Change == PROGRAM_REQUEST_UNLOAD) {
if (pObject->Unload) {
err = pObject->Unload(pObject->Context);
if (err == 0) {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_UNLOADING;
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_OTHER;
}
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_UNLOADING;
}
} else if (pObject->Program_Change == PROGRAM_REQUEST_LOAD) {
if (pObject->Load) {
err = pObject->Load(pObject->Context);
if (err == 0) {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_LOADING;
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_LOAD_FAILED;
}
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_LOADING;
}
} else if (pObject->Program_Change == PROGRAM_REQUEST_HALT) {
if (pObject->Halt) {
pObject->Halt(pObject->Context);
}
pObject->Reason_For_Halt = PROGRAM_ERROR_PROGRAM;
pObject->Program_State = PROGRAM_STATE_HALTED;
} else if (pObject->Program_Change == PROGRAM_REQUEST_RESTART) {
if (pObject->Restart) {
err = pObject->Restart(pObject->Context);
if (err == 0) {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_RUNNING;
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_OTHER;
}
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Program_State = PROGRAM_STATE_RUNNING;
}
} else {
if (pObject->Run) {
err = pObject->Run(pObject->Context);
if (err == 0) {
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
} else {
pObject->Reason_For_Halt = PROGRAM_ERROR_INTERNAL;
pObject->Program_State = PROGRAM_STATE_HALTED;
}
}
}
}
/**
* @brief Updates the object program operation
*
* 12.22.5 Program_Change
*
* This property, of type BACnetProgramRequest, is used to request changes
* to the operating state of the process this object represents.
* The Program_Change property provides one means for changing
* the operating state of this process. The process may change its own
* state as a consequence of execution as well.
*
* The values that may be taken on by this property are:
* READY ready for change request (the normal state)
* LOAD request that the application program be loaded, if not already loaded
* RUN request that the process begin executing, if not already running
* HALT request that the process halt execution
* RESTART request that the process restart at its initialization point
* UNLOAD request that the process halt execution and unload
*
* Normally the value of the Program_Change property will be READY,
* meaning that the program is ready to accept a new
* request to change its operating state. If the Program_Change property
* is not READY, then it may not be written to and any
* attempt to write to the property shall return a Result(-).
* If it has one of the other enumerated values, then a previous request to
* change state has not yet been honored, so new requests cannot
* be accepted. When the request to change state is finally
* honored, then the Program_Change property value shall become READY
* and the new state shall be reflected in the Program_State property.
* Depending on the current Program_State, certain requested values for
* Program_Change may be invalid and would also return a Result(-)
* if an attempt were made to write them.
*
* It is important to note that program loading could be terminated
* either due to an error or a request to HALT that occurs
* during loading. In either case, it is possible to have Program_State=HALTED
* and yet not have a complete or operable program in place.
* In this case, a request to RESTART is taken to mean LOAD instead.
* If a complete program is loaded but HALTED for any reason,
* then RESTART simply reenters program execution at its
* initialization entry point.
*
* There may be BACnet devices
* that support Program objects but do not require "loading"
* of the application programs, as these applications may be built in.
* In these cases, loading is taken to mean "preparing for execution,"
* the specifics of which are a local matter.
*
* @param object_instance - object-instance number of the object
* @param milliseconds - number of milliseconds elapsed
*/
void Program_Timer(uint32_t object_instance, uint16_t milliseconds)
{
struct object_data *pObject;
(void)milliseconds;
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
switch (pObject->Program_State) {
case PROGRAM_STATE_IDLE:
Program_State_Idle_Handler(pObject);
break;
case PROGRAM_STATE_LOADING:
pObject->Program_State = PROGRAM_STATE_HALTED;
break;
case PROGRAM_STATE_UNLOADING:
pObject->Program_State = PROGRAM_STATE_IDLE;
break;
case PROGRAM_STATE_HALTED:
Program_State_Halted_Handler(pObject);
break;
case PROGRAM_STATE_RUNNING:
Program_State_Running_Handler(pObject);
break;
case PROGRAM_STATE_WAITING:
Program_State_Running_Handler(pObject);
break;
default:
/* do nothing */
break;
}
pObject->Program_Change = PROGRAM_REQUEST_READY;
}
}
/**
* @brief Creates a Integer Value object
* @param object_instance - object-instance number of the object
* @return the object-instance that was created, or BACNET_MAX_INSTANCE
*/
uint32_t Program_Create(uint32_t object_instance)
{
struct object_data *pObject = NULL;
int index;
if (!Object_List) {
Object_List = Keylist_Create();
}
if (object_instance > BACNET_MAX_INSTANCE) {
return BACNET_MAX_INSTANCE;
} else if (object_instance == BACNET_MAX_INSTANCE) {
/* wildcard instance */
/* the Object_Identifier property of the newly created object
shall be initialized to a value that is unique within the
responding BACnet-user device. The method used to generate
the object identifier is a local matter.*/
object_instance = Keylist_Next_Empty_Key(Object_List, 1);
}
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
/* already exists - signal success but don't change data */
return object_instance;
}
pObject = calloc(1, sizeof(struct object_data));
if (!pObject) {
/* no RAM available - signal failure */
return BACNET_MAX_INSTANCE;
}
index = Keylist_Data_Add(Object_List, object_instance, pObject);
if (index < 0) {
/* unable to add to list - signal failure */
free(pObject);
return BACNET_MAX_INSTANCE;
}
pObject->Program_State = PROGRAM_STATE_IDLE;
pObject->Program_Change = PROGRAM_REQUEST_READY;
pObject->Reason_For_Halt = PROGRAM_ERROR_NORMAL;
pObject->Description_Of_Halt = NULL;
pObject->Program_Location = NULL;
pObject->Instance_Of = NULL;
pObject->Description = NULL;
pObject->Object_Name = NULL;
pObject->Reliability = RELIABILITY_NO_FAULT_DETECTED;
pObject->Out_Of_Service = false;
pObject->Context = NULL;
pObject->Load = NULL;
pObject->Run = NULL;
pObject->Halt = NULL;
pObject->Restart = NULL;
pObject->Unload = NULL;
return object_instance;
}
/**
* @brief Deletes an object-instance
* @param object_instance - object-instance number of the object
* @return true if the object-instance was deleted
*/
bool Program_Delete(uint32_t object_instance)
{
bool status = false;
struct object_data *pObject =
Keylist_Data_Delete(Object_List, object_instance);
if (pObject) {
free(pObject);
status = true;
}
return status;
}
/**
* @brief Deletes all the objects and their data
*/
void Program_Cleanup(void)
{
struct object_data *pObject;
uint16_t dev_id;
#ifdef BAC_ROUTING
uint16_t current_dev_id = Routed_Device_Object_Index();
#endif
for (dev_id = 0; dev_id < MAX_NUM_DEVICES; dev_id++) {
#ifdef BAC_ROUTING
Set_Routed_Device_Object_Index(dev_id);
#endif
if (Object_List) {
do {
pObject = Keylist_Data_Pop(Object_List);
if (pObject) {
free(pObject);
}
} while (pObject);
Keylist_Delete(Object_List);
Object_List = NULL;
}
}
#ifdef BAC_ROUTING
Set_Routed_Device_Object_Index(current_dev_id);
#endif
}
/**
* Initializes the object data
*/
void Program_Init(void)
{
uint16_t dev_id;
#ifdef BAC_ROUTING
uint16_t current_dev_id = Routed_Device_Object_Index();
#endif
for (dev_id = 0; dev_id < MAX_NUM_DEVICES; dev_id++) {
#ifdef BAC_ROUTING
Set_Routed_Device_Object_Index(dev_id);
#endif
if (!Object_List) {
Object_List = Keylist_Create();
}
}
#ifdef BAC_ROUTING
Set_Routed_Device_Object_Index(current_dev_id);
#endif
}