Files
Steve Karg 7dfc840dfc Bugfix/using-uint16-for-units-property-storage (#1107)
* Fixed units property declaration in basic Analog Input header file to be uint16_t instead of uint8_t.

* Added range checking of units property in example objects WriteProperty handler.
2025-09-24 09:35:10 -05:00

605 lines
17 KiB
C

/**
* @brief This module manages the BACnet Analog Value objects
* @author Steve Karg <skarg@users.sourceforge.net>
* @date 2007
* @copyright SPDX-License-Identifier: MIT
*/
#include <stdbool.h>
#include <stdint.h>
#include "hardware.h"
#include "adc.h"
#include "nvdata.h"
#include "rs485.h"
#include "stack.h"
#include "bacnet/bacdef.h"
#include "bacnet/bacdcode.h"
#include "bacnet/bacenum.h"
#include "bacnet/bacapp.h"
#include "bacnet/config.h" /* the custom stuff */
#include "bacnet/wp.h"
#include "bacnet/basic/object/av.h"
/* functions to get the present value when requested */
typedef float (*object_present_value_read_callback)(void);
/* functions to get the present value when requested */
typedef bool (*object_present_value_write_callback)(float value);
/**
* @brief Return the present value for the ADC0 object.
* @return The present value.
*/
static float adc0_value(void)
{
return (float)adc_millivolts(0);
}
/**
* @brief Return the present value for the ADC1 object.
* @return The present value.
*/
static float adc1_value(void)
{
return (float)adc_millivolts(1);
}
/**
* @brief Return the present value for the ADC2 object.
* @return The present value.
*/
static float adc2_value(void)
{
return (float)adc_millivolts(2);
}
/**
* @brief Return the present value for the ADC3 object.
* @return The present value.
*/
static float adc3_value(void)
{
return (float)adc_millivolts(3);
}
/**
* @brief Return the present value for the stack size object.
* @return The present value.
*/
static float stack_size_value(void)
{
return (float)stack_size();
}
/**
* @brief Return the present value for the stack unused object.
* @return The present value.
*/
static float stack_unused_value(void)
{
return (float)stack_unused();
}
/**
* @brief Return the present value for the MS/TP baud rate object.
* @return The present value.
*/
static float mstp_baud(void)
{
uint8_t kbaud;
float value;
kbaud = nvdata_unsigned8(NV_EEPROM_MSTP_BAUD_K);
value = (float)RS485_Baud_Rate_From_Kilo(kbaud);
return value;
}
/**
* @brief Set the present value for the MS/TP baud rate object.
* @param value - The new value.
* @return true if the value was in range and written.
*/
static bool mstp_baud_write(float value)
{
bool status = false;
uint8_t kbaud;
int32_t baud;
baud = (int32_t)value;
if ((baud >= 9600L) && (baud <= 1152000L)) {
kbaud = (uint8_t)(baud / 1000UL);
nvdata_unsigned8_set(NV_EEPROM_MSTP_BAUD_K, kbaud);
status = true;
}
return status;
}
/**
* @brief Return the present value for the MS/TP MAC address object.
* @return The present value.
*/
static float mstp_mac(void)
{
return (float)nvdata_unsigned8(NV_EEPROM_MSTP_MAC);
}
/**
* @brief Set the present value for the MS/TP address object.
* @param value - The new value.
* @return true if the value was in range and written.
*/
static bool mstp_mac_write(float value)
{
bool status = false;
uint8_t mac = 0;
int32_t value32;
value32 = (int32_t)value;
if ((value32 >= 0L) && (value32 <= 127L)) {
mac = (uint8_t)value32;
nvdata_unsigned8_set(NV_EEPROM_MSTP_MAC, mac);
status = true;
}
return status;
}
/**
* @brief Return the present value for the MS/TP max manager object.
* @return The present value.
*/
static float mstp_manager(void)
{
return (float)nvdata_unsigned8(NV_EEPROM_MSTP_MAX_MASTER);
}
/**
* @brief Set the present value for the MS/TP max manager object.
* @param value - The new value.
* @return true if the value was in range and written.
*/
static bool mstp_manager_write(float value)
{
bool status = false;
uint8_t manager = 0;
int32_t value32;
value32 = (int32_t)value;
if ((value32 >= 0) && (value32 <= 127)) {
manager = (uint8_t)value32;
nvdata_unsigned8_set(NV_EEPROM_MSTP_MAX_MASTER, manager);
status = true;
}
return status;
}
/**
* @brief Return the present value for the Device ID object.
* @return The present value.
*/
static float device_id(void)
{
return (float)nvdata_unsigned24(NV_EEPROM_DEVICE_0);
}
/**
* @brief Set the present value for the Device ID object.
* @param value - The new value.
* @return true if the value was in range and written.
*/
static bool device_id_write(float value)
{
bool status = false;
int32_t value32;
value32 = (int32_t)value;
if ((value32 >= 0) && (value32 <= BACNET_MAX_INSTANCE)) {
nvdata_unsigned24_set(NV_EEPROM_DEVICE_0, value32);
status = true;
}
return status;
}
struct object_data {
const uint8_t object_id;
const char *object_name;
uint16_t units;
object_present_value_read_callback read_callback;
object_present_value_write_callback write_callback;
float present_value;
};
/* clang-format off */
static struct object_data Object_List[] = {
/* device ADC inputs */
{ 0, "ADC0", UNITS_MILLIVOLTS, adc0_value, NULL, 0.0f },
{ 1, "ADC1", UNITS_MILLIVOLTS, adc1_value, NULL, 0.0f },
{ 2, "ADC2", UNITS_MILLIVOLTS, adc2_value, NULL, 0.0f },
{ 3, "ADC3", UNITS_MILLIVOLTS, adc3_value, NULL, 0.0f },
/* device configuration */
{ 92, "Device ID", UNITS_NO_UNITS,
device_id, device_id_write, 0.0f },
{ 93, "MS/TP Baud", UNITS_BITS_PER_SECOND,
mstp_baud, mstp_baud_write, 0.0f },
{ 94, "MS/TP MAC", UNITS_NO_UNITS,
mstp_mac, mstp_mac_write, 0.0f },
{ 95, "MS/TP Max Manager", UNITS_NO_UNITS,
mstp_manager, mstp_manager_write, 0.0f },
/* device status */
{ 96, "MCU Frequency", UNITS_HERTZ, NULL, NULL, (float)F_CPU },
{ 97, "CStack Size", UNITS_NO_UNITS, stack_size_value, NULL, 0.0f },
{ 98, "CStack Unused", UNITS_NO_UNITS, stack_unused_value, NULL, 0.0f },
{ 99, "Uptime", UNITS_HOURS, NULL, NULL, 0.0f }
};
/* clang-format on */
/* number of objects */
static const unsigned Objects_Max =
sizeof(Object_List) / sizeof(Object_List[0]);
/**
* @brief Return the object or NULL if not found.
* @param object_instance - object-instance number of the object
* @return object if the instance is found, and NULL if not
*/
static struct object_data *Object_List_Element(uint32_t object_instance)
{
unsigned index;
uint32_t object_id;
for (index = 0; index < Objects_Max; index++) {
object_id = Object_List[index].object_id;
if (object_id == object_instance) {
return &Object_List[index];
}
}
return NULL;
}
/**
* @brief Determines if a given Analog 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 Analog_Value_Valid_Instance(uint32_t object_instance)
{
if (Object_List_Element(object_instance)) {
return true;
}
return false;
}
/**
* @brief Determines the number of objects
* @return Number of objects
*/
unsigned Analog_Value_Count(void)
{
return Objects_Max;
}
/**
* @brief Determines the object instance-number for a given 0..N index
* @param index - 0..N value
* @return object instance-number for the given index
*/
uint32_t Analog_Value_Index_To_Instance(unsigned index)
{
uint32_t object_instance = UINT32_MAX;
if (index < Objects_Max) {
object_instance = Object_List[index].object_id;
}
return object_instance;
}
/**
* @brief For a given object instance-number, determines a 0..N index
* @param object_instance - object-instance number of the object
* @return index for the given instance-number, or N if not valid.
*/
unsigned Analog_Value_Instance_To_Index(uint32_t object_instance)
{
unsigned index = 0;
for (index = 0; index < Objects_Max; index++) {
if (Object_List[index].object_id == object_instance) {
break;
}
}
return index;
}
/**
* @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 Analog_Value_Name_Set(uint32_t object_instance, const char *value)
{
struct object_data *object;
object = Object_List_Element(object_instance);
if (object) {
object->object_name = value;
return true;
}
return false;
}
/**
* @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 *Analog_Value_Name_ASCII(uint32_t object_instance)
{
const char *object_name = "AV-X";
struct object_data *object;
object = Object_List_Element(object_instance);
if (object) {
object_name = object->object_name;
}
return object_name;
}
/**
* @brief For a given object instance-number, determines the present-value
* @param object_instance - object-instance number of the object
* @return present-value of the object
*/
float Analog_Value_Present_Value(uint32_t object_instance)
{
float value = 0.0;
struct object_data *object;
object = Object_List_Element(object_instance);
if (object) {
if (object->read_callback) {
value = object->read_callback();
} else {
value = object->present_value;
}
}
return value;
}
/**
* @brief For a given object instance-number, sets the present-value
* @param object_instance - object-instance number of the object
* @param value - value to set
* @return true if value is within range and present-value is set.
*/
bool Analog_Value_Present_Value_Set(
uint32_t object_instance, float value, uint8_t priority)
{
bool status = false;
struct object_data *object;
(void)priority;
object = Object_List_Element(object_instance);
if (object) {
if (object->write_callback) {
status = object->write_callback(value);
} else {
object->present_value = value;
status = true;
}
}
return status;
}
/**
* @brief For a given object instance-number, determines the units
* @param object_instance - object-instance number of the object
* @return units from the given object, or UNITS_NO_UNITS if not found
*/
uint16_t Analog_Value_Units(uint32_t object_instance)
{
uint16_t units = UNITS_NO_UNITS;
struct object_data *object;
object = Object_List_Element(object_instance);
if (object) {
units = object->units;
}
return units;
}
/**
* @brief For a given object instance-number, sets the units
* @param object_instance - object-instance number of the object
* @param units - property value to set
* @return true if valid instance and property was set
*/
bool Analog_Value_Units_Set(uint32_t object_instance, uint16_t units)
{
struct object_data *object;
object = Object_List_Element(object_instance);
if (object) {
object->units = units;
return true;
}
return false;
}
/**
* @brief 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 Analog_Value_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;
apdu = rpdata->application_data;
switch (rpdata->object_property) {
case PROP_OBJECT_IDENTIFIER:
apdu_len = encode_application_object_id(
&apdu[0], OBJECT_ANALOG_VALUE, rpdata->object_instance);
break;
case PROP_OBJECT_NAME:
characterstring_init_ansi(
&char_string, Analog_Value_Name_ASCII(rpdata->object_instance));
apdu_len =
encode_application_character_string(&apdu[0], &char_string);
break;
case PROP_OBJECT_TYPE:
apdu_len =
encode_application_enumerated(&apdu[0], OBJECT_ANALOG_VALUE);
break;
case PROP_PRESENT_VALUE:
apdu_len = encode_application_real(
&apdu[0], Analog_Value_Present_Value(rpdata->object_instance));
break;
case PROP_STATUS_FLAGS:
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:
apdu_len =
encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
break;
case PROP_OUT_OF_SERVICE:
apdu_len = encode_application_boolean(&apdu[0], false);
break;
case PROP_UNITS:
apdu_len = encode_application_enumerated(
&apdu[0], Analog_Value_Units(rpdata->object_instance));
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->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;
}
/**
* @brief 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 Analog_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
{
bool status = false; /* return value */
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
if (!Analog_Value_Valid_Instance(wp_data->object_instance)) {
wp_data->error_class = ERROR_CLASS_OBJECT;
wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
return false;
}
/* 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_REAL) {
status = Analog_Value_Present_Value_Set(
wp_data->object_instance, value.type.Real,
wp_data->priority);
if (!status) {
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_UNITS:
if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) {
if (value.type.Enumerated <= UINT16_MAX) {
Analog_Value_Units_Set(
wp_data->object_instance, value.type.Enumerated);
} else {
status = false;
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_OBJECT_IDENTIFIER:
case PROP_OBJECT_NAME:
case PROP_OBJECT_TYPE:
case PROP_STATUS_FLAGS:
case PROP_EVENT_STATE:
case PROP_OUT_OF_SERVICE:
case PROP_DESCRIPTION:
case PROP_PRIORITY_ARRAY:
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;
}
/**
* Configure some analog pins for ADC operation
*/
void Analog_Value_Init(void)
{
adc_enable(0);
adc_enable(1);
adc_enable(2);
adc_enable(3);
}