diff --git a/.github/workflows/raspi.yml b/.github/workflows/raspi.yml new file mode 100644 index 00000000..6d02912f --- /dev/null +++ b/.github/workflows/raspi.yml @@ -0,0 +1,75 @@ +name: Raspberry Pi + +on: + workflow_dispatch: + inputs: + logLevel: + description: 'Log level' + required: true + default: 'warning' + type: choice + options: + - info + - warning + - debug + tags: + description: 'Test scenario tags' + required: false + type: boolean + environment: + description: 'Environment to run tests against' + type: environment + required: true + +jobs: + log-the-inputs: + runs-on: raspios/base:latest + steps: + - run: | + echo "Log level: $LEVEL" + echo "Tags: $TAGS" + echo "Environment: $ENVIRONMENT" + env: + LEVEL: ${{ inputs.logLevel }} + TAGS: ${{ inputs.tags }} + ENVIRONMENT: ${{ inputs.environment }} + + raspi-bip-apps: + runs-on: raspios/base:latest + steps: + - uses: actions/checkout@v4 + - name: Create Build Environment + run: | + sudo apt-get update -qq + sudo apt-get install -qq libconfig-dev + - name: Build Demo Apps + run: | + gcc --version + make clean + make all + + piface: + runs-on: raspios/base:latest + steps: + - uses: actions/checkout@v4 + - name: Create Build Environment + run: | + sudo apt-get update -qq + - name: Build Demo Apps + run: | + gcc --version + cd apps/piface && ./configure && cd ../../ + make piface + + blinkt: + runs-on: raspios/base:latest + steps: + - uses: actions/checkout@v4 + - name: Create Build Environment + run: | + sudo apt-get update -qq + sudo apt-get install -qq libpigpio-dev libpigpiod-if-dev pigpiod + - name: Build Demo Apps + run: | + gcc --version + make blinkt diff --git a/.gitignore b/.gitignore index 25b102f0..9115a2b3 100644 --- a/.gitignore +++ b/.gitignore @@ -57,7 +57,6 @@ test-results.xml Debug/ settings/ *.componentinfo.xml -bin/ Backup* BACnet_BDT_table address_cache @@ -67,50 +66,29 @@ CMakeLists.txt.user **/out/* Obj/ Release/ -apps/piface/libmcp23s17/ -apps/piface/libpifacedigital/ /test/build/ /cmake-build-* /.idea -/apps/abort/bacabort -/apps/ack-alarm/bacackalarm -/apps/readfile/bacarf -/apps/writefile/bacawf -/apps/dcc/bacdcc -/apps/epics/bacepics -/apps/error/bacerror -/apps/event/bacevent -/apps/getevent/bacge -/apps/iam/baciam -/apps/iamrouter/baciamr -/apps/initrouter/bacinitr -/apps/netnumis/bacnni -/apps/server-client/bacpoll -/apps/readbdt/bacrbdt -/apps/reinit/bacrd -/apps/readfdt/bacrfdt -/apps/readprop/bacrp -/apps/readpropm/bacrpm -/apps/readrange/bacrr -/apps/scov/bacscov -/apps/server/bacserv -/apps/timesync/bacts -/apps/ucov/bacucov -/apps/uevent/bacuevent -/apps/uptransfer/bacupt -/apps/writebdt/bacwbdt -/apps/whohas/bacwh -/apps/whois/bacwi -/apps/whatisnetnum/bacwinn -/apps/whoisrouter/bacwir -/apps/writeprop/bacwp -/apps/writepropm/bacwpm -/apps/mstpcap/mstpcap -/apps/mstpcrc/mstpcrc -/apps/add-list-element/bacale -/apps/remove-list-element/bacrle -/apps/create-object/bacco -/apps/delete-object/bacdo +# exclude the bin folder, except for some files +bin/* +!bin/*.sh +!bin/*.bat +!bin/*.txt + +# exclude the apps folder, except for some source files +apps/* +!apps/Makefile +!apps/*.md +apps/**/* +!apps/**/ +!apps/**/*.c +!apps/**/*.h +!apps/**/*.txt +!apps/**/*.md +!apps/**/Makefile +!apps/**/CMakeLists.txt +apps/piface/libmcp23s17/ +apps/piface/libpifacedigital/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 54bd7810..91952f1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,14 +18,25 @@ The git repositories are hosted at the following sites: ### Added -Added [feature#14] EventTimeStamp decoding from ReadPropertyMultiple app +- Added [feature#14] EventTimeStamp decoding from ReadPropertyMultiple app. +- Added Channel, Color, Color Temperature, & Lighting Output demo app with Blinkt! +- Added pipeline build of piface and blinkt apps with Raspberry Pi OS image. +- Added linear interpolation library functions used in fading and ramping. ### Changed +- Added Device timer API to feed elapsed milliseconds to children objects. +- Changed gitignore to ease the maintainenance of source files in app folder +- Changed example server app device simulator to use mstimer instead of OS time. +- Changed example channel object to be dynamically created or deleted +- Changed example channel object to handle color & color temperature objects. + ### Fixed - Fixed datetime decode of invalid application tag. (#495) - Fixed extraneous SO_BINDTODEVICE error message in Linux BIP +- Fixed example Color, Color Temperature, and Lighting object fade, ramp, and step. +- Fixed and secured BACnetXYcolor and ColorCommand codecs. ## [1.2.0] - 2023-09-11 diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ff09b25..4b9307dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -362,6 +362,8 @@ add_library(${PROJECT_NAME} src/bacnet/basic/sys/key.h src/bacnet/basic/sys/keylist.c src/bacnet/basic/sys/keylist.h + src/bacnet/basic/sys/linear.c + src/bacnet/basic/sys/linear.h src/bacnet/basic/sys/mstimer.c src/bacnet/basic/sys/mstimer.h src/bacnet/basic/sys/ringbuf.c diff --git a/Makefile b/Makefile index c67a4aaf..02d2bfe2 100644 --- a/Makefile +++ b/Makefile @@ -69,6 +69,10 @@ ack-alarm: add-list-element: $(MAKE) -s -C apps $@ +.PHONY: blinkt +blinkt: + $(MAKE) -s -C apps $@ + .PHONY: dcc dcc: $(MAKE) -s -C apps $@ @@ -101,6 +105,10 @@ gateway: gateway-win32: $(MAKE) BACNET_PORT=win32 -s -C apps gateway +.PHONY: piface +piface: + $(MAKE) CSTANDARD="-std=gnu11" LEGACY=true -s -C apps $@ + .PHONY: readbdt readbdt: $(MAKE) -s -C apps $@ diff --git a/apps/Makefile b/apps/Makefile index ce264886..3edabab9 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -215,14 +215,14 @@ endif ##### # AFL -fuzz-afl: CC=afl-gcc +fuzz-afl: CC=afl-gcc fuzz-afl: FUZZ_FLAGS=-DFUZZING=1 fuzz-afl: LFLAGS += $(FUZZ_FLAGS) fuzz-afl: CFLAGS += $(FUZZ_FLAGS) fuzz-afl: export AFL_USE_ASAN=1 # LIBFUZZER -fuzz-libfuzzer: CC=clang +fuzz-libfuzzer: CC=clang fuzz-libfuzzer: FUZZ_FLAGS=-DFUZZING=1 -fsanitize=fuzzer,address -g3 -Og -fno-optimize-sibling-calls -fno-omit-frame-pointer fuzz-libfuzzer: LFLAGS += $(FUZZ_FLAGS) fuzz-libfuzzer: CFLAGS += $(FUZZ_FLAGS) @@ -263,6 +263,10 @@ ack-alarm: $(BACNET_LIB_TARGET) add-list-element: $(BACNET_LIB_TARGET) $(MAKE) -B -C $@ clean all +.PHONY: blinkt +blinkt: + $(MAKE) -C $@ + .PHONY: dcc dcc: $(BACNET_LIB_TARGET) $(MAKE) -B -C $@ @@ -311,6 +315,10 @@ mstpcap: mstpcrc: $(MAKE) -B -C $@ +.PHONY: piface +piface: + $(MAKE) -B -C $@ + .PHONY: ptransfer ptransfer: $(BACNET_LIB_TARGET) $(MAKE) -B -C $@ diff --git a/apps/blinkt/Blinkt-Raspberry-Pi-GPIO-Pinout.png b/apps/blinkt/Blinkt-Raspberry-Pi-GPIO-Pinout.png new file mode 100644 index 00000000..2c33c133 Binary files /dev/null and b/apps/blinkt/Blinkt-Raspberry-Pi-GPIO-Pinout.png differ diff --git a/apps/blinkt/Makefile b/apps/blinkt/Makefile new file mode 100644 index 00000000..d6fdb297 --- /dev/null +++ b/apps/blinkt/Makefile @@ -0,0 +1,56 @@ +############################################################################### +# Makefile for Project - BACnet Blinkt! +# sudo apt install libpigpio-dev libpigpiod-if-dev pigpiod +############################################################################### + +## General Flags +TARGET = bacblinkt +# BACnet objects that are used with this app +BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object +SRC = main.c \ + blinkt.c \ + device.c \ + $(BACNET_OBJECT_DIR)/netport.c \ + $(BACNET_OBJECT_DIR)/channel.c \ + $(BACNET_OBJECT_DIR)/color_object.c \ + $(BACNET_OBJECT_DIR)/color_temperature.c \ + $(BACNET_OBJECT_DIR)/lc.c \ + $(BACNET_OBJECT_DIR)/lo.c + +CFLAGS += -DMAX_TSM_TRANSACTIONS=1 + +# TARGET_EXT is defined in apps/Makefile as .exe or nothing +TARGET_BIN = ${TARGET}$(TARGET_EXT) + +# note: link to pigpio daemon so that our app can run without root +# start pigio daemon with the command 'sudo pigpiod' +LD_PIGPIO = -Wl,-pthread,-lpigpiod_if2 +LFLAGS += $(LD_PIGPIO) + +OBJS += ${SRC:.c=.o} + +all: ${BACNET_LIB_TARGET} Makefile ${TARGET_BIN} + +${TARGET_BIN}: ${OBJS} Makefile ${BACNET_LIB_TARGET} + ${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@ + size $@ + cp $@ ../../bin + +${BACNET_LIB_TARGET}: + ( cd ${BACNET_LIB_DIR} ; $(MAKE) clean ; $(MAKE) -s ) + +.c.o: + ${CC} -c ${CFLAGS} $*.c -o $@ + +.PHONY: depend +depend: + rm -f .depend + ${CC} -MM ${CFLAGS} *.c >> .depend + +.PHONY: clean +clean: + rm -f core ${TARGET_BIN} ${OBJS} $(TARGET).map ${BACNET_LIB_TARGET} + +.PHONY: include +include: .depend + diff --git a/apps/blinkt/README.md b/apps/blinkt/README.md new file mode 100644 index 00000000..8698e19b --- /dev/null +++ b/apps/blinkt/README.md @@ -0,0 +1,26 @@ +# Raspberry Pi connected to a Blinkt! RGB card demo + +This demo includes a BACnet server with Color objects and service handlers +for the eight RGB (red-green-blue) LEDs attached to the Blinkt! card. + +## Installation + +The demo uses pigpiod (Pi GPIO Daemon) and developer library. To install +and run the daemon at powerup (and immediately): + + $ sudo apt install libpigpio-dev libpigpiod-if-dev pigpiod + $ sudo systemctl enable pigpiod + $ sudo systemctl start pigpiod + +## Building + +Build from the root folder: + + $ make blinkt + +## Running + +Run from the bin/ folder: + + $ ./bin/bacblinkt 9009 + diff --git a/apps/blinkt/blinkt.c b/apps/blinkt/blinkt.c new file mode 100644 index 00000000..5e0953a8 --- /dev/null +++ b/apps/blinkt/blinkt.c @@ -0,0 +1,213 @@ +/** + * @file + * @brief API for Blinkt! daughter board for Raspberry Pi + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include "blinkt.h" + +#define BLINKT_DEFAULT_BRIGHTNESS 7 +#define BLINKT_NUM_LEDS 8 + +/* GPIO pin numbers */ +#define BLINKT_MOSI 23 +#define BLINKT_SCLK 24 + +/* RGBb data for each LED */ +static uint32_t Blinkt_LED[BLINKT_NUM_LEDS]; +/* handle to the pigpiod */ +static int Blinkt_Pi; + +/** + * @brief Get the number of LEDS + * @return Number of LEDs + */ +uint8_t blinkt_led_count(void) +{ + return BLINKT_NUM_LEDS; +} + +/** + * @brief Set all the LEDS to default brightness (7/31) + */ +void blinkt_clear(void) +{ + int x; + for (x = 0; x < BLINKT_NUM_LEDS; x++) { + Blinkt_LED[x] = BLINKT_DEFAULT_BRIGHTNESS; + } +} + +/** + * @brief Set one LED to specific RGB color + * @param led index 0..#BLINKT_NUM_LEDS + * @param r color red from 0..255 + * @param g color green from 0..255 + * @param b color blue from 0..255 + */ +void blinkt_set_pixel(uint8_t led, uint8_t r, uint8_t g, uint8_t b) +{ + if (led >= BLINKT_NUM_LEDS) + return; + + Blinkt_LED[led] = blinkt_rgbb(r, g, b, Blinkt_LED[led] & 0x1F); +} + +/** + * @brief Set one LED to specific intensity + * @param led index 0..#BLINKT_NUM_LEDS + * @param brightness intensity from 0..31, 0=OFF, 1=dimmest, 31=brightest + */ +void blinkt_set_pixel_brightness(uint8_t led, uint8_t brightness) +{ + if (led >= BLINKT_NUM_LEDS) + return; + + Blinkt_LED[led] = (Blinkt_LED[led] & 0xFFFFFF00) | (brightness & 0x1F); +} + +/** + * @brief Set one LED to RGB color and brightness + * @param led index 0..#BLINKT_NUM_LEDS + * @param color encoded as 32-bit RGBb (red | green | blue | brightness) + */ +void blinkt_set_pixel_uint32(uint8_t led, uint32_t color) +{ + if (led >= BLINKT_NUM_LEDS) + return; + + Blinkt_LED[led] = color; +} + +/** + * @brief Encode RGB color and brightness into 32-bit RGBb + * @param r color red from 0..255 + * @param g color green from 0..255 + * @param b color blue from 0..255 + * @param brightness intensity from 0..31, 0=OFF, 1=dimmest, 31=brightest + * @return color encoded as 32-bit RGBb (red | green | blue | brightness) + */ +uint32_t blinkt_rgbb(uint8_t r, uint8_t g, uint8_t b, uint8_t brightness) +{ + uint32_t result = 0; + result = (brightness & 0x1F); + result |= ((uint32_t)r << 24); + result |= ((uint32_t)g << 16); + result |= ((uint16_t)b << 8); + return result; +} + +/** + * @brief Encode RGB color at default brightness into 32-bit RGBb + * @param r color red from 0..255 + * @param g color green from 0..255 + * @param b color blue from 0..255 + * @return color encoded as 32-bit RGBb (red | green | blue | brightness) + */ +uint32_t blinkt_rgb(uint8_t r, uint8_t g, uint8_t b) +{ + return blinkt_rgbb(r, g, b, BLINKT_DEFAULT_BRIGHTNESS); +} + +/** + * @brief Write a byte via shift register to LEDs + * @param byte to be written + */ +inline static void write_byte(uint8_t byte) +{ + int n; + for (n = 0; n < 8; n++) { + gpio_write(Blinkt_Pi, BLINKT_MOSI, (byte & (1 << (7 - n))) > 0); + gpio_write(Blinkt_Pi, BLINKT_SCLK, 1); + gpio_write(Blinkt_Pi, BLINKT_SCLK, 0); + } +} + +/** + * @brief Shift LED values to the actual LEDs via shift registers + */ +void blinkt_show(void) +{ + int x; + + write_byte(0); + write_byte(0); + write_byte(0); + write_byte(0); + for (x = 0; x < BLINKT_NUM_LEDS; x++) { + write_byte(0xE0 | (Blinkt_LED[x] & 0x1F)); + write_byte((Blinkt_LED[x] >> 8) & 0xFF); + write_byte((Blinkt_LED[x] >> 16) & 0xFF); + write_byte((Blinkt_LED[x] >> 24) & 0xFF); + } + write_byte(0xff); +} + +/** + * @brief Disable the GPIO hardware to the Blinkt! board + */ +void blinkt_stop(void) +{ + /* Disconnect from local Pi. */ + pigpio_stop(Blinkt_Pi); + printf("PiGPIO stopped\n"); +} + +/** + * @brief Initialize the GPIO hardware for the Blinkt! board + */ +int blinkt_init(void) +{ + char *optHost = NULL; + char *optPort = NULL; + + /* Connect to local Pi. */ + Blinkt_Pi = pigpio_start(optHost, optPort); + if (Blinkt_Pi < 0) { + perror("PiGPIO failed to start!"); + return -1; + } + set_mode(Blinkt_Pi, BLINKT_MOSI, PI_OUTPUT); + gpio_write(Blinkt_Pi, BLINKT_MOSI, 0); + set_mode(Blinkt_Pi, BLINKT_SCLK, PI_OUTPUT); + gpio_write(Blinkt_Pi, BLINKT_SCLK, 0); + blinkt_clear(); + printf("PiGPIO started\n"); + + return 0; +} + +static int test_column = 0; +static int test_z, test_y; +/** + * @brief Test the Blinkt! board with a simple changing pattern + */ +void blinkt_test_task(void) +{ + for (test_z = 0; test_z < BLINKT_NUM_LEDS; test_z++) { + switch (test_column) { + case 0: + blinkt_set_pixel_uint32(test_z, blinkt_rgb(test_y, 0, 0)); + break; + case 1: + blinkt_set_pixel_uint32(test_z, blinkt_rgb(0, test_y, 0)); + break; + case 2: + blinkt_set_pixel_uint32(test_z, blinkt_rgb(0, 0, test_y)); + break; + default: + break; + } + } + blinkt_show(); + test_y += 1; + if (test_y > 254) + test_column++; + test_column %= 3; + test_y %= 255; +} diff --git a/apps/blinkt/blinkt.h b/apps/blinkt/blinkt.h new file mode 100644 index 00000000..1d312811 --- /dev/null +++ b/apps/blinkt/blinkt.h @@ -0,0 +1,23 @@ +/** + * @file + * @brief API for Blinkt! daughter board for Raspberry Pi + * + * SPDX-License-Identifier: MIT + */ +#ifndef BLINKT_H +#define BLINKT_H + +void blinkt_clear(void); +void blinkt_set_pixel_uint32(uint8_t led, uint32_t color); +void blinkt_set_pixel(uint8_t led, uint8_t r, uint8_t g, uint8_t b); +void blinkt_set_pixel_brightness(uint8_t led, uint8_t brightness); +uint32_t blinkt_rgbb(uint8_t r, uint8_t g, uint8_t b, uint8_t brightness); +uint32_t blinkt_rgb(uint8_t r, uint8_t g, uint8_t b); +void blinkt_stop(void); +void blinkt_show(void); +uint8_t blinkt_led_count(void); + +int blinkt_init(void); +void blinkt_test_task(void); + +#endif diff --git a/apps/blinkt/device.c b/apps/blinkt/device.c new file mode 100644 index 00000000..262f914a --- /dev/null +++ b/apps/blinkt/device.c @@ -0,0 +1,1951 @@ +/************************************************************************** + * + * 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 "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacapp.h" +#include "bacnet/config.h" /* the custom stuff */ +#include "bacnet/datetime.h" +#include "bacnet/apdu.h" +#include "bacnet/wp.h" /* WriteProperty handling */ +#include "bacnet/rp.h" /* ReadProperty handling */ +#include "bacnet/dcc.h" /* DeviceCommunicationControl handling */ +#include "bacnet/version.h" +#include "bacnet/basic/object/device.h" /* me */ +#include "bacnet/basic/services.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/basic/binding/address.h" +/* include the device object */ +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/object/lc.h" +#if (BACNET_PROTOCOL_REVISION >= 14) +#include "bacnet/basic/object/channel.h" +#include "bacnet/basic/object/lo.h" +#endif +#if (BACNET_PROTOCOL_REVISION >= 17) +#include "bacnet/basic/object/netport.h" +#endif +#if (BACNET_PROTOCOL_REVISION >= 24) +#include "bacnet/basic/object/color_object.h" +#include "bacnet/basic/object/color_temperature.h" +#endif + +/* 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 /* COV */, NULL /* COV Clear */, + NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, + NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, +#if (BACNET_PROTOCOL_REVISION >= 17) + { OBJECT_NETWORK_PORT, Network_Port_Init, Network_Port_Count, + Network_Port_Index_To_Instance, Network_Port_Valid_Instance, + Network_Port_Object_Name, Network_Port_Read_Property, + Network_Port_Write_Property, Network_Port_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, +#endif + { OBJECT_LOAD_CONTROL, Load_Control_Init, Load_Control_Count, + Load_Control_Index_To_Instance, Load_Control_Valid_Instance, + Load_Control_Object_Name, Load_Control_Read_Property, + Load_Control_Write_Property, Load_Control_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ , NULL /* Timer */ }, +#if (BACNET_PROTOCOL_REVISION >= 14) + { OBJECT_LIGHTING_OUTPUT, Lighting_Output_Init, Lighting_Output_Count, + Lighting_Output_Index_To_Instance, Lighting_Output_Valid_Instance, + Lighting_Output_Object_Name, Lighting_Output_Read_Property, + Lighting_Output_Write_Property, Lighting_Output_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Lighting_Output_Create, Lighting_Output_Delete, Lighting_Output_Timer}, + { OBJECT_CHANNEL, Channel_Init, Channel_Count, Channel_Index_To_Instance, + Channel_Valid_Instance, Channel_Object_Name, Channel_Read_Property, + Channel_Write_Property, Channel_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Channel_Create, Channel_Delete, NULL /* Timer */ }, +#endif +#if (BACNET_PROTOCOL_REVISION >= 24) + { OBJECT_COLOR, Color_Init, Color_Count, Color_Index_To_Instance, + Color_Valid_Instance, Color_Object_Name, Color_Read_Property, + Color_Write_Property, Color_Property_Lists, NULL /* ReadRangeInfo */, + NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, + NULL /* COV Clear */, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Color_Create, Color_Delete, Color_Timer}, + { OBJECT_COLOR_TEMPERATURE, Color_Temperature_Init, Color_Temperature_Count, + Color_Temperature_Index_To_Instance, Color_Temperature_Valid_Instance, + Color_Temperature_Object_Name, Color_Temperature_Read_Property, + Color_Temperature_Write_Property, Color_Temperature_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Color_Temperature_Create, Color_Temperature_Delete, + Color_Temperature_Timer}, +#endif + { 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 */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ } +}; + +/** 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; +} + +/* 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] = "GNU"; +static char Application_Software_Version[MAX_DEV_VER_LEN + 1] = "1.0"; +static const char *BACnet_Version = BACNET_VERSION_TEXT; +static char Location[MAX_DEV_LOC_LEN + 1] = "USA"; +static char Description[MAX_DEV_DESC_LEN + 1] = "server"; +/* 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 int16_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 */ +static BACNET_REINITIALIZED_STATE Reinitialize_State = BACNET_REINIT_IDLE; +static const char *Reinit_Password = "filister"; + +/** 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; + + /* From 16.4.1.1.2 Password + This optional parameter shall be a CharacterString of up to + 20 characters. For those devices that require the password as a + protection, the service request shall be denied if the parameter + is absent or if the password is incorrect. For those devices that + do not require a password, this parameter shall be ignored.*/ + if (characterstring_length(&rd_data->password) > 20) { + rd_data->error_class = ERROR_CLASS_SERVICES; + rd_data->error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; + } else if (characterstring_ansi_same(&rd_data->password, Reinit_Password)) { + /* Note: you could use a mix of state and password to + accomplish multiple things before restarting */ + switch (rd_data->state) { + case BACNET_REINIT_COLDSTART: + case BACNET_REINIT_WARMSTART: + dcc_set_status_duration(COMMUNICATION_ENABLE, 0); + /* note: you probably want to restart *after* the + simple ack has been sent from the return handler + so just set a flag from here */ + Reinitialize_State = rd_data->state; + status = true; + break; + case BACNET_REINIT_STARTBACKUP: + case BACNET_REINIT_ENDBACKUP: + case BACNET_REINIT_STARTRESTORE: + case BACNET_REINIT_ENDRESTORE: + case BACNET_REINIT_ABORTRESTORE: + if (dcc_communication_disabled()) { + rd_data->error_class = ERROR_CLASS_SERVICES; + rd_data->error_code = ERROR_CODE_COMMUNICATION_DISABLED; + } else { + rd_data->error_class = ERROR_CLASS_SERVICES; + rd_data->error_code = + ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED; + } + break; + default: + rd_data->error_class = ERROR_CLASS_SERVICES; + rd_data->error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; + break; + } + } else { + rd_data->error_class = ERROR_CLASS_SECURITY; + rd_data->error_code = ERROR_CODE_PASSWORD_FAILURE; + } + + return status; +} + +BACNET_REINITIALIZED_STATE Device_Reinitialized_State(void) +{ + return Reinitialize_State; +} + +unsigned Device_Count(void) +{ + return 1; +} + +uint32_t Device_Index_To_Instance(unsigned index) +{ + (void)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) +{ + return Object_Instance_Number; +} + +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, BACNET_OBJECT_TYPE *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; +} + +/** + * @brief Encode a BACnetARRAY property element + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index requested: + * 0 to N for individual array members + * @param apdu [out] Buffer in which the APDU contents are built, or NULL to + * return the length of buffer if it had been built + * @return The length of the apdu encoded or + * BACNET_STATUS_ERROR for ERROR_CODE_INVALID_ARRAY_INDEX + */ +int Device_Object_List_Element_Encode( + uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu) +{ + int apdu_len = BACNET_STATUS_ERROR; + BACNET_OBJECT_TYPE object_type; + uint32_t instance; + bool found; + + if (object_instance == Device_Object_Instance_Number()) { + /* single element is zero based, add 1 for BACnetARRAY which is one + * based */ + array_index++; + found = + Device_Object_List_Identifier(array_index, &object_type, &instance); + if (found) { + apdu_len = + encode_application_object_id(apdu, object_type, instance); + } + } + + return apdu_len; +} + +/** Determine if we have an object with the given object_name. + * If the object_type and object_instance pointers are not null, + * and the lookup succeeds, they will be given the resulting values. + * @param object_name [in] The desired Object Name to look for. + * @param object_type [out] The BACNET_OBJECT_TYPE of the matching Object. + * @param object_instance [out] The object instance number of the matching + * Object. + * @return True on success or else False if not found. + */ +bool Device_Valid_Object_Name(BACNET_CHARACTER_STRING *object_name1, + BACNET_OBJECT_TYPE *object_type, + uint32_t *object_instance) +{ + bool found = false; + BACNET_OBJECT_TYPE type = OBJECT_NONE; + 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( + BACNET_OBJECT_TYPE 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) +{ + datetime_local( + &Local_Date, &Local_Time, &UTC_Offset, &Daylight_Savings_Status); +} + +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 */ + BACNET_BIT_STRING bit_string = { 0 }; + BACNET_CHARACTER_STRING char_string = { 0 }; + uint32_t i = 0; + uint32_t count = 0; + uint8_t *apdu = NULL; + struct object_functions *pObject = NULL; + 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, (uint8_t)pObject->Object_Type, true); + } + pObject++; + } + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + case PROP_OBJECT_LIST: + count = Device_Object_List_Count(); + apdu_len = bacnet_array_encode(rpdata->object_instance, + rpdata->array_index, + Device_Object_List_Element_Encode, + count, apdu, apdu_max); + if (apdu_len == BACNET_STATUS_ABORT) { + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + } else if (apdu_len == BACNET_STATUS_ERROR) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + } + 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 common Object and Property, and encodes its Value in an + * APDU. Sets the error class and code if request is not appropriate. + * @param pObject - object table + * @param rpdata [in,out] Structure with the requested Object & Property info + * on entry, and APDU message on return. + * @return The length of the APDU on success, else BACNET_STATUS_ERROR + */ +static int Read_Property_Common( + struct object_functions *pObject, BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = BACNET_STATUS_ERROR; + BACNET_CHARACTER_STRING char_string; + uint8_t *apdu = NULL; +#if (BACNET_PROTOCOL_REVISION >= 14) + struct special_property_list_t property_list; +#endif + + if ((rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + apdu = rpdata->application_data; + if (property_list_common(rpdata->object_property)) { + apdu_len = property_list_common_encode(rpdata, Object_Instance_Number); + } else if (rpdata->object_property == PROP_OBJECT_NAME) { + /* only array properties can have array options */ + if (rpdata->array_index != BACNET_ARRAY_ALL) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } else { + characterstring_init_ansi(&char_string, ""); + if (pObject->Object_Name) { + (void)pObject->Object_Name( + rpdata->object_instance, &char_string); + } + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + } +#if (BACNET_PROTOCOL_REVISION >= 14) + } else if (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); +#endif + } else if (pObject->Object_Read_Property) { + apdu_len = pObject->Object_Read_Property(rpdata); + } + + 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; + + /* 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)) { + apdu_len = Read_Property_Common(pObject, rpdata); + } else { + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t object_instance = 0; + int result = 0; +#if defined(BACNET_TIME_MASTER) + uint32_t minutes = 0; +#endif + + /* 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 = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_OBJECT_ID); + 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 = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + /* FIXME: bounds check? */ + apdu_retries_set((uint8_t)value.type.Unsigned_Int); + } + break; + case PROP_APDU_TIMEOUT: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + /* FIXME: bounds check? */ + apdu_timeout_set((uint16_t)value.type.Unsigned_Int); + } + break; + case PROP_VENDOR_IDENTIFIER: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + /* FIXME: bounds check? */ + Device_Set_Vendor_Identifier((uint16_t)value.type.Unsigned_Int); + } + break; + case PROP_SYSTEM_STATUS: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + result = Device_Set_System_Status( + (BACNET_DEVICE_STATUS)value.type.Enumerated, false); + if (result != 0) { + /* result: - 0 = ok, -1 = bad value, -2 = not allowed */ + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + if (result == -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 = write_property_string_valid( + wp_data, &value, characterstring_capacity(&My_Object_Name)); + 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 = write_property_empty_string_valid( + wp_data, &value, MAX_DEV_LOC_LEN); + if (status) { + Device_Set_Location( + characterstring_value(&value.type.Character_String), + characterstring_length(&value.type.Character_String)); + } + break; + + case PROP_DESCRIPTION: + status = write_property_empty_string_valid( + wp_data, &value, MAX_DEV_DESC_LEN); + if (status) { + Device_Set_Description( + characterstring_value(&value.type.Character_String), + characterstring_length(&value.type.Character_String)); + } + break; + case PROP_MODEL_NAME: + status = write_property_empty_string_valid( + wp_data, &value, MAX_DEV_MOD_LEN); + 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: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + 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; + } + } + break; + case PROP_ALIGN_INTERVALS: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN); + if (status) { + Device_Align_Intervals_Set(value.type.Boolean); + status = true; + } + break; + case PROP_INTERVAL_OFFSET: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + 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; + } + } + 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: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_SIGNED_INT); + if (status) { + 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; + } + } + break; +#if defined(BACDL_MSTP) + case PROP_MAX_INFO_FRAMES: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + 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 = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + 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 pessimist! */ + 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); +} + +/** + * @brief AddListElement from an object list property + * @param list_element [in] Pointer to the BACnet_List_Element_Data structure, + * which is packed with the information from the request. + * @return The length of the apdu encoded or #BACNET_STATUS_ERROR or + * #BACNET_STATUS_ABORT or #BACNET_STATUS_REJECT. + */ +int Device_Add_List_Element( + BACNET_LIST_ELEMENT_DATA * list_element) +{ + int status = BACNET_STATUS_ERROR; + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(list_element->object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(list_element->object_instance)) { + if (pObject->Object_Add_List_Element) { + status = pObject->Object_Add_List_Element(list_element); + } else { + list_element->error_class = ERROR_CLASS_PROPERTY; + list_element->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } + } else { + list_element->error_class = ERROR_CLASS_OBJECT; + list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + list_element->error_class = ERROR_CLASS_OBJECT; + list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + +/** + * @brief RemoveListElement from an object list property + * @param list_element [in] Pointer to the BACnet_List_Element_Data structure, + * which is packed with the information from the request. + * @return The length of the apdu encoded or #BACNET_STATUS_ERROR or + * #BACNET_STATUS_ABORT or #BACNET_STATUS_REJECT. + */ +int Device_Remove_List_Element( + BACNET_LIST_ELEMENT_DATA * list_element) +{ + int status = BACNET_STATUS_ERROR; + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(list_element->object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(list_element->object_instance)) { + if (pObject->Object_Remove_List_Element) { + status = pObject->Object_Remove_List_Element(list_element); + } else { + list_element->error_class = ERROR_CLASS_PROPERTY; + list_element->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } + } else { + list_element->error_class = ERROR_CLASS_OBJECT; + list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + list_element->error_class = ERROR_CLASS_OBJECT; + list_element->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 pessimist! */ + 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); + } + } + } +} + +/** + * @brief Creates a child object, if supported + * @ingroup ObjHelpers + * @param data - CreateObject data, including error codes if failures + * @return true if object has been created + */ +bool Device_Create_Object( + BACNET_CREATE_OBJECT_DATA *data) +{ + bool status = false; + struct object_functions *pObject = NULL; + uint32_t object_instance; + + pObject = Device_Objects_Find_Functions(data->object_type); + if (pObject != NULL) { + if (!pObject->Object_Create) { + /* The device supports the object type and may have + sufficient space, but does not support the creation of the + object for some other reason.*/ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_DYNAMIC_CREATION_NOT_SUPPORTED; + } else if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(data->object_instance)) { + /* The object being created already exists */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_OBJECT_IDENTIFIER_ALREADY_EXISTS; + } else { + if (data->list_of_initial_values) { + /* FIXME: add support for writing to list of initial values */ + /* A property specified by the Property_Identifier in the + List of Initial Values does not support initialization + during the CreateObject service. */ + data->first_failed_element_number = 1; + data->error_class = ERROR_CLASS_PROPERTY; + data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + /* and the object shall not be created */ + } else { + object_instance = pObject->Object_Create(data->object_instance); + if (object_instance == BACNET_MAX_INSTANCE) { + /* The device cannot allocate the space needed + for the new object.*/ + data->error_class = ERROR_CLASS_RESOURCES; + data->error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + } else { + /* required by ACK */ + data->object_instance = object_instance; + Device_Inc_Database_Revision(); + status = true; + } + } + } + } else { + /* The device does not support the specified object type. */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE; + } + + return status; +} + +/** + * @brief Deletes a child object, if supported + * @ingroup ObjHelpers + * @param data - DeleteObject data, including error codes if failures + * @return true if object has been deleted + */ +bool Device_Delete_Object( + BACNET_DELETE_OBJECT_DATA *data) +{ + bool status = false; + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(data->object_type); + if (pObject != NULL) { + if (!pObject->Object_Delete) { + /* The device supports the object type + but does not support the deletion of the + object for some reason.*/ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED; + } else if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(data->object_instance)) { + /* The object being deleted must already exist */ + status = pObject->Object_Delete(data->object_instance); + if (status) { + Device_Inc_Database_Revision(); + } else { + /* The object exists but cannot be deleted. */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED; + } + } else { + /* The object to be deleted does not exist. */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + /* The device does not support the specified object type. */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE; + } + + return status; +} + +/** 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; + characterstring_init_ansi(&My_Object_Name, "Blinkt! Server"); + datetime_init(); + 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; +} + +/** + * @brief Updates all the object timers with elapsed milliseconds + * @param milliseconds - number of milliseconds elapsed + */ +void Device_Timer( + uint16_t milliseconds) +{ + struct object_functions *pObject; + unsigned count = 0; + uint32_t instance; + + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count) { + count = pObject->Object_Count(); + } + while (count) { + count--; + if ((pObject->Object_Timer) && + (pObject->Object_Index_To_Instance)) { + instance = pObject->Object_Index_To_Instance(count); + pObject->Object_Timer(instance, milliseconds); + } + } + pObject++; + } +} diff --git a/apps/blinkt/main.c b/apps/blinkt/main.c new file mode 100644 index 00000000..dcbd3ae4 --- /dev/null +++ b/apps/blinkt/main.c @@ -0,0 +1,465 @@ +/************************************************************************** + * + * Copyright (C) 2006 Steve Karg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + *********************************************************************/ +#include +#include +#include +#include +#include +#include "bacnet/config.h" +#include "bacnet/basic/binding/address.h" +#include "bacnet/bacdef.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/services.h" +#include "bacnet/datalink/dlenv.h" +#include "bacnet/bacdcode.h" +#include "bacnet/npdu.h" +#include "bacnet/apdu.h" +#include "bacnet/iam.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/object/bacfile.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/dcc.h" +#include "bacnet/getevent.h" +#include "bacnet/lighting.h" +#include "bacport.h" +#include "bacnet/basic/sys/mstimer.h" +#include "bacnet/basic/sys/color_rgb.h" +#include "bacnet/basic/sys/filename.h" +#include "bacnet/basic/sys/linear.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/version.h" +/* include the device object */ +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/object/lo.h" +#include "bacnet/basic/object/channel.h" +#include "bacnet/basic/object/color_object.h" +#include "bacnet/basic/object/color_temperature.h" +#include "blinkt.h" + +/** @file blinkt/main.c Example application using the BACnet Stack. */ + +/* (Doxygen note: The next two lines pull all the following Javadoc + * into the ServerDemo module.) */ +/** @addtogroup ServerDemo */ +/*@{*/ + +/** Buffer used for receiving */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; +/* current version of the BACnet stack */ +static const char *BACnet_Version = BACNET_VERSION_TEXT; +/* task timer for various BACnet timeouts */ +static struct mstimer BACnet_Task_Timer; +/* task timer for TSM timeouts */ +static struct mstimer BACnet_TSM_Timer; +/* task timer for address binding timeouts */ +static struct mstimer BACnet_Address_Timer; +/* task timer for object functionality */ +static struct mstimer BACnet_Object_Timer; + +/** Initialize the handlers we will utilize. + * @see Device_Init, apdu_set_unconfirmed_handler, apdu_set_confirmed_handler + */ +static void Init_Service_Handlers(void) +{ + Device_Init(NULL); + /* we need to handle who-is to support dynamic device binding */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, handler_who_has); + /* handle i-am to support binding to other devices */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, handler_i_am_bind); + /* 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_WRITE_PROP_MULTIPLE, handler_write_property_multiple); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_READ_RANGE, handler_read_range); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_REINITIALIZE_DEVICE, handler_reinitialize_device); + apdu_set_unconfirmed_handler( + SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION, handler_timesync_utc); + apdu_set_unconfirmed_handler( + SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION, handler_timesync); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_SUBSCRIBE_COV, handler_cov_subscribe); + apdu_set_unconfirmed_handler( + SERVICE_UNCONFIRMED_COV_NOTIFICATION, handler_ucov_notification); + /* handle communication so we can shutup when asked */ + apdu_set_confirmed_handler(SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + handler_device_communication_control); + /* handle the data coming back from private requests */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_PRIVATE_TRANSFER, + handler_unconfirmed_private_transfer); + /* configure the cyclic timers */ + mstimer_set(&BACnet_Task_Timer, 1000UL); + mstimer_set(&BACnet_TSM_Timer, 50UL); + mstimer_set(&BACnet_Address_Timer, 60UL * 1000UL); + mstimer_set(&BACnet_Object_Timer, 100UL); +} + +/** + * Clean up the Blinkt! interface + */ +static void blinkt_cleanup(void) +{ + blinkt_stop(); +} + +/** + * @brief Callback for tracking value + * @param object_instance - object-instance number of the object + * @param old_value - Color temperature value prior to write + * @param value - Color temperature value of the write + */ +static void Lighting_Output_Write_Value_Handler( + uint32_t object_instance, float old_value, float value) +{ + uint8_t index = 255; + uint8_t brightness; + + (void)old_value; + if (object_instance > 0) { + index = object_instance - 1; + } + if (index < blinkt_led_count()) { + /* brightness intensity from 0..31, 0=OFF, 1=dimmest, 31=brightest */ + if (isgreaterequal(value, 1.0)) { + brightness = linear_interpolate(1.0, value, 100.0, 1, 31); + } else { + brightness = 0; + } + blinkt_set_pixel_brightness(index, brightness); + printf("LED[%u]=%.1f%% (%u)\n", (unsigned)index, value, + (unsigned)brightness); + } +} + +/** + * @brief Callback for tracking value + * @param object_instance - object-instance number of the object + * @param old_value - Color temperature value prior to write + * @param value - Color temperature value of the write + */ +static void Color_Temperature_Write_Value_Handler( + uint32_t object_instance, uint32_t old_value, uint32_t value) +{ + uint8_t red, green, blue; + uint8_t index = 255; + + (void)old_value; + if (object_instance > 0) { + index = object_instance - 1; + } + if (index < blinkt_led_count()) { + color_rgb_from_temperature(value, &red, &green, &blue); + blinkt_set_pixel(index, red, green, blue); + printf("%u Kelvin RGB[%u]=%u,%u,%u\n", (unsigned)value, (unsigned)index, + (unsigned)red, (unsigned)green, (unsigned)blue); + } +} + +/** + * @brief Callback for tracking value + * @param object_instance - object-instance number of the object + * @param old_value - BACnetXYColor value prior to write + * @param value - BACnetXYColor value of the write + */ +static void Color_Write_Value_Handler(uint32_t object_instance, + BACNET_XY_COLOR *old_value, + BACNET_XY_COLOR *value) +{ + uint8_t red, green, blue; + float brightness_percent = 100.0; + uint8_t index = 255; + + (void)old_value; + if (object_instance > 0) { + index = object_instance - 1; + } + if (index < blinkt_led_count()) { + color_rgb_from_xy(&red, &green, &blue, value->x_coordinate, + value->y_coordinate, brightness_percent); + blinkt_set_pixel(index, red, green, blue); + printf("x,y=%0.2f,%0.2f(%.1f%%) RGB[%u]=%u,%u,%u\n", + value->x_coordinate, value->y_coordinate, brightness_percent, + (unsigned)index, (unsigned)red, (unsigned)green, (unsigned)blue); + } +} + +/** + * @brief Create the objects and configure the callbacks for BACnet objects + */ +static void bacnet_output_init(void) +{ + unsigned i = 0; + uint8_t led_max; + uint32_t object_instance = 1; + BACNET_COLOR_COMMAND command = { 0 }; + BACNET_OBJECT_ID object_id; + uint32_t light_channel_instance = 1; + uint32_t color_channel_instance = 2; + uint32_t temp_channel_instance = 3; + BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE member; + + Channel_Create(light_channel_instance); + Channel_Name_Set(light_channel_instance, "Lights"); + Channel_Create(color_channel_instance); + Channel_Name_Set(color_channel_instance, "Colors"); + Channel_Create(temp_channel_instance); + Channel_Name_Set(temp_channel_instance, "Color-Temperatures"); + led_max = blinkt_led_count(); + for (i = 0; i < led_max; i++) { + /* color */ + Color_Create(object_instance); + Color_Write_Enable(object_instance); + /* fade to black */ + Color_Command(object_instance, &command); + command.operation = BACNET_COLOR_OPERATION_FADE_TO_COLOR; + command.target.color.x_coordinate = 0.0; + command.target.color.y_coordinate = 0.0; + command.transit.fade_time = 0; + Color_Command_Set(object_instance, &command); + + /* configure channel members */ + member.objectIdentifier.type = OBJECT_COLOR; + member.objectIdentifier.instance = object_instance; + member.propertyIdentifier = PROP_PRESENT_VALUE; + member.arrayIndex = BACNET_ARRAY_ALL; + member.deviceIdentifier.type = OBJECT_DEVICE; + member.deviceIdentifier.instance = Device_Object_Instance_Number(); + Channel_Reference_List_Member_Element_Set( + color_channel_instance, 1 + i, &member); + + /* color temperature */ + Color_Temperature_Create(object_instance); + Color_Temperature_Write_Enable(object_instance); + /* stop the color temperature */ + Color_Temperature_Command(object_instance, &command); + command.operation = BACNET_COLOR_OPERATION_STOP; + Color_Temperature_Command_Set(object_instance, &command); + + /* configure channel members */ + member.objectIdentifier.type = OBJECT_COLOR_TEMPERATURE; + member.objectIdentifier.instance = object_instance; + member.propertyIdentifier = PROP_PRESENT_VALUE; + member.arrayIndex = BACNET_ARRAY_ALL; + member.deviceIdentifier.type = OBJECT_DEVICE; + member.deviceIdentifier.instance = Device_Object_Instance_Number(); + Channel_Reference_List_Member_Element_Set( + temp_channel_instance, 1 + i, &member); + + /* lighting output */ + Lighting_Output_Create(object_instance); + + /* configure references */ + object_id.type = OBJECT_COLOR; + object_id.instance = object_instance; + Lighting_Output_Color_Reference_Set(object_instance, &object_id); + + /* configure channel members */ + member.objectIdentifier.type = OBJECT_LIGHTING_OUTPUT; + member.objectIdentifier.instance = object_instance; + member.propertyIdentifier = PROP_PRESENT_VALUE; + member.arrayIndex = BACNET_ARRAY_ALL; + member.deviceIdentifier.type = OBJECT_DEVICE; + member.deviceIdentifier.instance = Device_Object_Instance_Number(); + Channel_Reference_List_Member_Element_Set( + light_channel_instance, 1 + i, &member); + + object_instance++; + } + Color_Write_Present_Value_Callback_Set(Color_Write_Value_Handler); + Color_Temperature_Write_Present_Value_Callback_Set( + Color_Temperature_Write_Value_Handler); + Lighting_Output_Write_Present_Value_Callback_Set( + Lighting_Output_Write_Value_Handler); + Channel_Write_Property_Internal_Callback_Set(Device_Write_Property); +} + +/** + * @brief Print the terse usage info + * @param filename - this application file name + */ +static void print_usage(const char *filename) +{ + printf("Usage: %s [device-instance]\n", filename); + printf(" [--device N][--test]\n"); + printf(" [--version][--help]\n"); +} + +/** + * @brief Print the verbose usage info + * @param filename - this application file name + */ +static void print_help(const char *filename) +{ + printf("BACnet Blinkt! server device.\n"); + printf("device-instance:\n" + "--device N:\n" + "BACnet Device Object Instance number of this device.\n" + "This number will be used when other devices\n" + "try and bind with this device using Who-Is and\n" + "I-Am services.\n"); + printf("\n"); + printf("--test:\n" + "Test the Blinkt! RGB LEDs with a cycling pattern.\n"); + printf("\n"); + printf("Example:\n" + "%s 9009\n", + filename); +} + +/** Main function of server demo. + * + * @see Device_Set_Object_Instance_Number, dlenv_init, Send_I_Am, + * datalink_receive, npdu_handler, + * dcc_timer_seconds, datalink_maintenance_timer, + * handler_cov_task, + * tsm_timer_milliseconds + * + * @param argc [in] Arg count. + * @param argv [in] Takes one argument: the Device Instance #. + * @return 0 on success. + */ +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + uint16_t pdu_len = 0; + unsigned timeout_ms = 1; + unsigned long seconds = 0; + unsigned long milliseconds; + bool blinkt_test = false; + unsigned int target_args = 0; + uint32_t device_id = BACNET_MAX_INSTANCE; + int argi = 0; + char *filename = NULL; + + filename = filename_remove_path(argv[0]); + for (argi = 1; argi < argc; argi++) { + if (strcmp(argv[argi], "--help") == 0) { + print_usage(filename); + print_help(filename); + return 0; + } + if (strcmp(argv[argi], "--version") == 0) { + printf("%s %s\n", filename, BACNET_VERSION_TEXT); + printf("Copyright (C) 2023 by Steve Karg and others.\n" + "This is free software; see the source for copying " + "conditions.\n" + "There is NO warranty; not even for MERCHANTABILITY or\n" + "FITNESS FOR A PARTICULAR PURPOSE.\n"); + return 0; + } + if (strcmp(argv[argi], "--device") == 0) { + if (++argi < argc) { + device_id = strtol(argv[argi], NULL, 0); + } + } else if (strcmp(argv[argi], "--test") == 0) { + blinkt_test = true; + } else { + if (target_args == 0) { + device_id = strtol(argv[argi], NULL, 0); + target_args++; + } + } + } + if (device_id > BACNET_MAX_INSTANCE) { + fprintf(stderr, "device=%u - it must be less than %u\n", device_id, + BACNET_MAX_INSTANCE); + return 1; + } + Device_Set_Object_Instance_Number(device_id); + printf("BACnet Raspberry Pi Blinkt! Demo\n" + "BACnet Stack Version %s\n" + "BACnet Device ID: %u\n" + "Max APDU: %d\n", + BACnet_Version, Device_Object_Instance_Number(), MAX_APDU); + /* load any static address bindings to show up + in our device bindings list */ + address_init(); + Init_Service_Handlers(); + dlenv_init(); + atexit(datalink_cleanup); + blinkt_init(); + atexit(blinkt_cleanup); + bacnet_output_init(); + /* configure the timeout values */ + /* broadcast an I-Am on startup */ + Send_I_Am(&Handler_Transmit_Buffer[0]); + /* loop forever */ + for (;;) { + /* input */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout_ms); + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + if (mstimer_expired(&BACnet_Task_Timer)) { + mstimer_reset(&BACnet_Task_Timer); + /* 1 second tasks */ + dcc_timer_seconds(1); + datalink_maintenance_timer(1); + dlenv_maintenance_timer(1); + handler_cov_timer_seconds(1); + } + if (mstimer_expired(&BACnet_TSM_Timer)) { + mstimer_reset(&BACnet_TSM_Timer); + tsm_timer_milliseconds(mstimer_interval(&BACnet_TSM_Timer)); + } + handler_cov_task(); + if (mstimer_expired(&BACnet_Address_Timer)) { + mstimer_reset(&BACnet_Address_Timer); + /* address cache */ + seconds = mstimer_interval(&BACnet_Address_Timer) / 1000; + address_cache_timer(seconds); + } + /* output/input */ + if (blinkt_test) { + blinkt_test_task(); + } else { + if (mstimer_expired(&BACnet_Object_Timer)) { + mstimer_reset(&BACnet_Object_Timer); + milliseconds = mstimer_interval(&BACnet_Object_Timer); + Device_Timer(milliseconds); + blinkt_show(); + } + } + } + + return 0; +} + +/* @} */ + +/* End group ServerDemo */ diff --git a/apps/piface/Makefile b/apps/piface/Makefile index 2403f937..c7ef0989 100644 --- a/apps/piface/Makefile +++ b/apps/piface/Makefile @@ -7,7 +7,7 @@ TARGET = bacpiface # BACnet objects that are used with this app BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object SRC = main.c \ - $(BACNET_OBJECT_DIR)/device.c \ + device.c \ $(BACNET_OBJECT_DIR)/netport.c \ $(BACNET_OBJECT_DIR)/bi.c \ $(BACNET_OBJECT_DIR)/bo.c @@ -24,6 +24,8 @@ PIFACE_INCLUDE = libpifacedigital/src PIFACE_LIB = libpifacedigital MCP23S17_LIB = libmcp23s17 +CFLAGS += -I$(PIFACE_INCLUDE) + LDPIFACE = -Wl,-L$(PIFACE_LIB),-lpifacedigital,-L$(MCP23S17_LIB),-lmcp23s17 LFLAGS += $(LDPIFACE) diff --git a/apps/piface/device.c b/apps/piface/device.c index 07b3fd93..4ab7c0a4 100644 --- a/apps/piface/device.c +++ b/apps/piface/device.c @@ -1,6 +1,6 @@ /************************************************************************** * - * Copyright (C) 2014 Steve Karg + * 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 @@ -22,15 +22,19 @@ * 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 "bacnet/bacdef.h" #include "bacnet/bacdcode.h" #include "bacnet/bacenum.h" #include "bacnet/bacapp.h" #include "bacnet/config.h" /* the custom stuff */ +#include "bacnet/datetime.h" #include "bacnet/apdu.h" #include "bacnet/wp.h" /* WriteProperty handling */ #include "bacnet/rp.h" /* ReadProperty handling */ @@ -41,8 +45,6 @@ #include "bacnet/datalink/datalink.h" #include "bacnet/basic/binding/address.h" /* include the OS specific */ -#include "bacnet/basic/sys/mstimer.h" -/* include the device object */ #include "bacnet/basic/object/device.h" #include "bacnet/basic/object/bi.h" #include "bacnet/basic/object/bo.h" @@ -67,14 +69,18 @@ static object_functions_t My_Object_Table[] = { Device_Read_Property_Local, Device_Write_Property_Local, Device_Property_Lists, DeviceGetRRInfo, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, - NULL /* Intrinsic Reporting */ }, + NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, + NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, #if (BACNET_PROTOCOL_REVISION >= 17) { OBJECT_NETWORK_PORT, Network_Port_Init, Network_Port_Count, Network_Port_Index_To_Instance, Network_Port_Valid_Instance, Network_Port_Object_Name, Network_Port_Read_Property, Network_Port_Write_Property, Network_Port_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, - NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, #endif { OBJECT_BINARY_INPUT, Binary_Input_Init, Binary_Input_Count, Binary_Input_Index_To_Instance, Binary_Input_Valid_Instance, @@ -82,20 +88,25 @@ static object_functions_t My_Object_Table[] = { Binary_Input_Write_Property, Binary_Input_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, Binary_Input_Encode_Value_List, Binary_Input_Change_Of_Value, - Binary_Input_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */ }, + Binary_Input_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, { 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 */ }, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Binary_Output_Create, Binary_Output_Delete, NULL /* Timer */}, { 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 */ } + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ , NULL /* Timer */} }; /** Glue function to let the Device object, when called by a handler, @@ -208,6 +219,10 @@ static const int Device_Properties_Optional[] = { #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 }; @@ -259,10 +274,15 @@ static BACNET_DATE Local_Date; /* rely on OS, if there is one */ 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 int16_t UTC_Offset = 5 * 60; static bool Daylight_Savings_Status = false; /* rely on OS */ -/* List_Of_Session_Keys */ +#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 */ @@ -296,8 +316,18 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) { bool status = false; - /* Note: you could use a mix of state and password to multiple things */ - if (characterstring_ansi_same(&rd_data->password, Reinit_Password)) { + /* From 16.4.1.1.2 Password + This optional parameter shall be a CharacterString of up to + 20 characters. For those devices that require the password as a + protection, the service request shall be denied if the parameter + is absent or if the password is incorrect. For those devices that + do not require a password, this parameter shall be ignored.*/ + if (characterstring_length(&rd_data->password) > 20) { + rd_data->error_class = ERROR_CLASS_SERVICES; + rd_data->error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; + } else if (characterstring_ansi_same(&rd_data->password, Reinit_Password)) { + /* Note: you could use a mix of state and password to + accomplish multiple things before restarting */ switch (rd_data->state) { case BACNET_REINIT_COLDSTART: case BACNET_REINIT_WARMSTART: @@ -347,7 +377,7 @@ unsigned Device_Count(void) uint32_t Device_Index_To_Instance(unsigned index) { - index = index; + (void)index; return Object_Instance_Number; } @@ -361,11 +391,7 @@ uint32_t Device_Index_To_Instance(unsigned index) */ 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) @@ -413,6 +439,11 @@ bool Device_Set_Object_Name(BACNET_CHARACTER_STRING *object_name) 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; @@ -813,57 +844,8 @@ bool Device_Object_Name_Copy(BACNET_OBJECT_TYPE object_type, 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; - } + datetime_local( + &Local_Date, &Local_Time, &UTC_Offset, &Daylight_Savings_Status); } void Device_getCurrentDateTime(BACNET_DATE_TIME *DateTime) @@ -881,11 +863,90 @@ int32_t Device_UTC_Offset(void) 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) @@ -1002,7 +1063,8 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) 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); + bitstring_set_bit( + &bit_string, (uint8_t)pObject->Object_Type, true); } pObject++; } @@ -1050,6 +1112,28 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) 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); @@ -1071,6 +1155,59 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) return apdu_len; } +/** Looks up the common Object and Property, and encodes its Value in an + * APDU. Sets the error class and code if request is not appropriate. + * @param pObject - object table + * @param rpdata [in,out] Structure with the requested Object & Property info + * on entry, and APDU message on return. + * @return The length of the APDU on success, else BACNET_STATUS_ERROR + */ +static int Read_Property_Common( + struct object_functions *pObject, BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = BACNET_STATUS_ERROR; + BACNET_CHARACTER_STRING char_string; + uint8_t *apdu = NULL; +#if (BACNET_PROTOCOL_REVISION >= 14) + struct special_property_list_t property_list; +#endif + + if ((rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + apdu = rpdata->application_data; + if (property_list_common(rpdata->object_property)) { + apdu_len = property_list_common_encode(rpdata, Object_Instance_Number); + } else if (rpdata->object_property == PROP_OBJECT_NAME) { + /* only array properties can have array options */ + if (rpdata->array_index != BACNET_ARRAY_ALL) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } else { + characterstring_init_ansi(&char_string, ""); + if (pObject->Object_Name) { + (void)pObject->Object_Name( + rpdata->object_instance, &char_string); + } + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + } +#if (BACNET_PROTOCOL_REVISION >= 14) + } else if (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); +#endif + } else if (pObject->Object_Read_Property) { + apdu_len = pObject->Object_Read_Property(rpdata); + } + + return apdu_len; +} + /** Looks up the requested Object and Property, and encodes its Value in an * APDU. * @ingroup ObjIntf @@ -1092,10 +1229,14 @@ int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) if (pObject != NULL) { if (pObject->Object_Valid_Instance && pObject->Object_Valid_Instance(rpdata->object_instance)) { - if (pObject->Object_Read_Property) { - apdu_len = pObject->Object_Read_Property(rpdata); + apdu_len = Read_Property_Common(pObject, rpdata); + } else { + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; } - } + } else { + rpdata->error_class = ERROR_CLASS_OBJECT; + rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; } return apdu_len; @@ -1109,7 +1250,10 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) BACNET_APPLICATION_DATA_VALUE value; BACNET_OBJECT_TYPE object_type = OBJECT_NONE; uint32_t object_instance = 0; - int temp; + int result = 0; +#if defined(BACNET_TIME_MASTER) + uint32_t minutes = 0; +#endif /* decode the some of the request */ len = bacapp_decode_application_data( @@ -1173,12 +1317,13 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) status = write_property_type_valid( wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); if (status) { - temp = Device_Set_System_Status( + result = Device_Set_System_Status( (BACNET_DEVICE_STATUS)value.type.Enumerated, false); - if (temp != 0) { + if (result != 0) { + /* result: - 0 = ok, -1 = bad value, -2 = not allowed */ status = false; wp_data->error_class = ERROR_CLASS_PROPERTY; - if (temp == -1) { + if (result == -1) { wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; } else { wp_data->error_code = @@ -1236,9 +1381,67 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) characterstring_length(&value.type.Character_String)); } break; - - case PROP_MAX_INFO_FRAMES: +#if defined(BACNET_TIME_MASTER) + case PROP_TIME_SYNCHRONIZATION_INTERVAL: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + 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; + } + } + break; + case PROP_ALIGN_INTERVALS: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN); + if (status) { + Device_Align_Intervals_Set(value.type.Boolean); + status = true; + } + break; + case PROP_INTERVAL_OFFSET: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + 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; + } + } + 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: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_SIGNED_INT); + if (status) { + 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; + } + } + break; #if defined(BACDL_MSTP) + case PROP_MAX_INFO_FRAMES: status = write_property_type_valid( wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); if (status) { @@ -1252,9 +1455,7 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) } } break; -#endif case PROP_MAX_MASTER: -#if defined(BACDL_MSTP) status = write_property_type_valid( wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); if (status) { @@ -1268,13 +1469,18 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) } } 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_UTC_OFFSET: case PROP_LOCAL_DATE: case PROP_DAYLIGHT_SAVINGS_STATUS: case PROP_PROTOCOL_VERSION: @@ -1287,6 +1493,9 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) 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; @@ -1321,7 +1530,15 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) 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; @@ -1338,6 +1555,76 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) return (status); } +/** + * @brief AddListElement from an object list property + * @param list_element [in] Pointer to the BACnet_List_Element_Data structure, + * which is packed with the information from the request. + * @return The length of the apdu encoded or #BACNET_STATUS_ERROR or + * #BACNET_STATUS_ABORT or #BACNET_STATUS_REJECT. + */ +int Device_Add_List_Element( + BACNET_LIST_ELEMENT_DATA * list_element) +{ + int status = BACNET_STATUS_ERROR; + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(list_element->object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(list_element->object_instance)) { + if (pObject->Object_Add_List_Element) { + status = pObject->Object_Add_List_Element(list_element); + } else { + list_element->error_class = ERROR_CLASS_PROPERTY; + list_element->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } + } else { + list_element->error_class = ERROR_CLASS_OBJECT; + list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + list_element->error_class = ERROR_CLASS_OBJECT; + list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + +/** + * @brief RemoveListElement from an object list property + * @param list_element [in] Pointer to the BACnet_List_Element_Data structure, + * which is packed with the information from the request. + * @return The length of the apdu encoded or #BACNET_STATUS_ERROR or + * #BACNET_STATUS_ABORT or #BACNET_STATUS_REJECT. + */ +int Device_Remove_List_Element( + BACNET_LIST_ELEMENT_DATA * list_element) +{ + int status = BACNET_STATUS_ERROR; + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(list_element->object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(list_element->object_instance)) { + if (pObject->Object_Remove_List_Element) { + status = pObject->Object_Remove_List_Element(list_element); + } else { + list_element->error_class = ERROR_CLASS_PROPERTY; + list_element->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } + } else { + list_element->error_class = ERROR_CLASS_OBJECT; + list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + list_element->error_class = ERROR_CLASS_OBJECT; + list_element->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 @@ -1411,33 +1698,110 @@ void Device_COV_Clear(BACNET_OBJECT_TYPE object_type, uint32_t object_instance) } } -#if defined(INTRINSIC_REPORTING) -void Device_local_reporting(void) +/** + * @brief Creates a child object, if supported + * @ingroup ObjHelpers + * @param data - CreateObject data, including error codes if failures + * @return true if object has been created + */ +bool Device_Create_Object( + BACNET_CREATE_OBJECT_DATA *data) { - struct object_functions *pObject; - uint32_t objects_count; + bool status = false; + struct object_functions *pObject = NULL; uint32_t object_instance; - BACNET_OBJECT_TYPE 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); + pObject = Device_Objects_Find_Functions(data->object_type); + if (pObject != NULL) { + if (!pObject->Object_Create) { + /* The device supports the object type and may have + sufficient space, but does not support the creation of the + object for some other reason.*/ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_DYNAMIC_CREATION_NOT_SUPPORTED; + } else if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(data->object_instance)) { + /* The object being created already exists */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_OBJECT_IDENTIFIER_ALREADY_EXISTS; + } else { + if (data->list_of_initial_values) { + /* FIXME: add support for writing to list of initial values */ + /* A property specified by the Property_Identifier in the + List of Initial Values does not support initialization + during the CreateObject service. */ + data->first_failed_element_number = 1; + data->error_class = ERROR_CLASS_PROPERTY; + data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + /* and the object shall not be created */ + } else { + object_instance = pObject->Object_Create(data->object_instance); + if (object_instance == BACNET_MAX_INSTANCE) { + /* The device cannot allocate the space needed + for the new object.*/ + data->error_class = ERROR_CLASS_RESOURCES; + data->error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + } else { + /* required by ACK */ + data->object_instance = object_instance; + Device_Inc_Database_Revision(); + status = true; } } } + } else { + /* The device does not support the specified object type. */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE; } + + return status; +} + +/** + * @brief Deletes a child object, if supported + * @ingroup ObjHelpers + * @param data - DeleteObject data, including error codes if failures + * @return true if object has been deleted + */ +bool Device_Delete_Object( + BACNET_DELETE_OBJECT_DATA *data) +{ + bool status = false; + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(data->object_type); + if (pObject != NULL) { + if (!pObject->Object_Delete) { + /* The device supports the object type + but does not support the deletion of the + object for some reason.*/ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED; + } else if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(data->object_instance)) { + /* The object being deleted must already exist */ + status = pObject->Object_Delete(data->object_instance); + if (status) { + Device_Inc_Database_Revision(); + } else { + /* The object exists but cannot be deleted. */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED; + } + } else { + /* The object to be deleted does not exist. */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + /* The device does not support the specified object type. */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE; + } + + return status; } -#endif /** Looks up the requested Object to see if the functionality is supported. * @ingroup ObjHelpers @@ -1470,24 +1834,8 @@ bool Device_Value_List_Supported(BACNET_OBJECT_TYPE object_type) 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, "PiFace Digital Demo"); -#if defined(BAC_UCI) - } - ucix_cleanup(ctx); -#endif /* defined(BAC_UCI) */ - + characterstring_init_ansi(&My_Object_Name, "PiFace"); + datetime_init(); if (object_table) { Object_Table = object_table; } else { @@ -1540,37 +1888,3 @@ bool DeviceGetRRInfo(BACNET_READ_RANGE_DATA *pRequest, /* Info on the request */ 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 */ diff --git a/apps/server/main.c b/apps/server/main.c index 682030ed..c17ae6bd 100644 --- a/apps/server/main.c +++ b/apps/server/main.c @@ -28,7 +28,6 @@ #include #include #include -#include #include "bacnet/config.h" #include "bacnet/bacdef.h" #include "bacnet/bacdcode.h" @@ -41,12 +40,21 @@ #include "bacnet/basic/services.h" #include "bacnet/datalink/dlenv.h" #include "bacnet/basic/sys/filename.h" +#include "bacnet/basic/sys/mstimer.h" #include "bacnet/basic/tsm/tsm.h" #include "bacnet/basic/tsm/tsm.h" #include "bacnet/datalink/datalink.h" #include "bacnet/basic/binding/address.h" /* include the device object */ #include "bacnet/basic/object/device.h" +/* objects that have tasks inside them */ +#if (BACNET_PROTOCOL_REVISION >= 14) +#include "bacnet/basic/object/lo.h" +#endif +#if (BACNET_PROTOCOL_REVISION >= 24) +#include "bacnet/basic/object/color_object.h" +#include "bacnet/basic/object/color_temperature.h" +#endif #include "bacnet/basic/object/lc.h" #include "bacnet/basic/object/trendlog.h" #if defined(INTRINSIC_REPORTING) @@ -68,7 +76,18 @@ /* current version of the BACnet stack */ static const char *BACnet_Version = BACNET_VERSION_TEXT; - +/* task timer for various BACnet timeouts */ +static struct mstimer BACnet_Task_Timer; +/* task timer for TSM timeouts */ +static struct mstimer BACnet_TSM_Timer; +/* task timer for address binding timeouts */ +static struct mstimer BACnet_Address_Timer; +#if defined(INTRINSIC_REPORTING) +/* task timer for notification recipient timeouts */ +static struct mstimer BACnet_Notification_Timer; +#endif +/* task timer for objects */ +static struct mstimer BACnet_Object_Timer; /** Buffer used for receiving */ static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; @@ -148,6 +167,14 @@ static void Init_Service_Handlers(void) SERVICE_CONFIRMED_CREATE_OBJECT, handler_create_object); apdu_set_confirmed_handler( SERVICE_CONFIRMED_DELETE_OBJECT, handler_delete_object); + /* configure the cyclic timers */ + mstimer_set(&BACnet_Task_Timer, 1000UL); + mstimer_set(&BACnet_TSM_Timer, 50UL); + mstimer_set(&BACnet_Address_Timer, 60UL*1000UL); + mstimer_set(&BACnet_Object_Timer, 100UL); +#if defined(INTRINSIC_REPORTING) + mstimer_set(&BACnet_Notification_Timer, NC_RESCAN_RECIPIENTS_SECS*1000UL); +#endif } static void print_usage(const char *filename) @@ -190,15 +217,9 @@ int main(int argc, char *argv[]) BACNET_ADDRESS src = { 0 }; /* address where message came from */ uint16_t pdu_len = 0; unsigned timeout = 1; /* milliseconds */ - time_t last_seconds = 0; - time_t current_seconds = 0; - uint32_t elapsed_seconds = 0; uint32_t elapsed_milliseconds = 0; - uint32_t address_binding_tmr = 0; + uint32_t elapsed_seconds = 0; BACNET_CHARACTER_STRING DeviceName; -#if defined(INTRINSIC_REPORTING) - uint32_t recipient_scan_tmr = 0; -#endif #if defined(BACNET_TIME_MASTER) BACNET_DATE_TIME bdatetime; #endif @@ -277,33 +298,27 @@ int main(int argc, char *argv[]) dlenv_init(); atexit(datalink_cleanup); - /* configure the timeout values */ - last_seconds = time(NULL); /* broadcast an I-Am on startup */ Send_I_Am(&Handler_Transmit_Buffer[0]); /* loop forever */ for (;;) { /* input */ - current_seconds = time(NULL); - - /* returns 0 bytes on timeout */ pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); /* process */ if (pdu_len) { npdu_handler(&src, &Rx_Buf[0], pdu_len); } - /* at least one second has passed */ - elapsed_seconds = (uint32_t)(current_seconds - last_seconds); - if (elapsed_seconds) { - last_seconds = current_seconds; + if (mstimer_expired(&BACnet_Task_Timer)) { + mstimer_reset(&BACnet_Task_Timer); + elapsed_milliseconds = mstimer_interval(&BACnet_Task_Timer); + elapsed_seconds = elapsed_milliseconds/1000; + /* 1 second tasks */ dcc_timer_seconds(elapsed_seconds); datalink_maintenance_timer(elapsed_seconds); dlenv_maintenance_timer(elapsed_seconds); - Load_Control_State_Machine_Handler(); - elapsed_milliseconds = elapsed_seconds * 1000; handler_cov_timer_seconds(elapsed_seconds); - tsm_timer_milliseconds(elapsed_milliseconds); + Load_Control_State_Machine_Handler(); trend_log_timer(elapsed_seconds); #if defined(INTRINSIC_REPORTING) Device_local_reporting(); @@ -313,24 +328,30 @@ int main(int argc, char *argv[]) handler_timesync_task(&bdatetime); #endif } - handler_cov_task(); - /* scan cache address */ - address_binding_tmr += elapsed_seconds; - if (address_binding_tmr >= 60) { - address_cache_timer(address_binding_tmr); - address_binding_tmr = 0; + if (mstimer_expired(&BACnet_TSM_Timer)) { + mstimer_reset(&BACnet_TSM_Timer); + elapsed_milliseconds = mstimer_interval(&BACnet_TSM_Timer); + tsm_timer_milliseconds(elapsed_milliseconds); } + if (mstimer_expired(&BACnet_Address_Timer)) { + mstimer_reset(&BACnet_Address_Timer); + elapsed_milliseconds = mstimer_interval(&BACnet_Address_Timer); + elapsed_seconds = elapsed_milliseconds/1000; + address_cache_timer(elapsed_seconds); + } + handler_cov_task(); #if defined(INTRINSIC_REPORTING) - /* try to find addresses of recipients */ - recipient_scan_tmr += elapsed_seconds; - if (recipient_scan_tmr >= NC_RESCAN_RECIPIENTS_SECS) { + if (mstimer_expired(&BACnet_Notification_Timer)) { + mstimer_reset(&BACnet_Notification_Timer); Notification_Class_find_recipient(); - recipient_scan_tmr = 0; } #endif /* output */ - - /* blink LEDs, Turn on or off outputs, etc */ + if (mstimer_expired(&BACnet_Object_Timer)) { + mstimer_reset(&BACnet_Object_Timer); + elapsed_milliseconds = mstimer_interval(&BACnet_Object_Timer); + Device_Timer(elapsed_milliseconds); + } } return 0; diff --git a/apps/writeprop/Makefile b/apps/writeprop/Makefile index bb0d8eed..79ee5472 100644 --- a/apps/writeprop/Makefile +++ b/apps/writeprop/Makefile @@ -2,6 +2,9 @@ # Executable file name TARGET = bacwp + +CFLAGS += -DBACAPP_COLOR_RGB_CONVERSION_ENABLED=1 + # BACnet objects that are used with this app BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object SRC = main.c \ diff --git a/apps/writepropm/Makefile b/apps/writepropm/Makefile index b458de02..2f4a092c 100644 --- a/apps/writepropm/Makefile +++ b/apps/writepropm/Makefile @@ -2,6 +2,9 @@ # Executable file name TARGET = bacwpm + +CFLAGS += -DBACAPP_COLOR_RGB_CONVERSION_ENABLED=1 + # BACnet objects that are used with this app BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object SRC = main.c \ diff --git a/src/bacnet/bacapp.c b/src/bacnet/bacapp.c index a97aba58..64635b9b 100644 --- a/src/bacnet/bacapp.c +++ b/src/bacnet/bacapp.c @@ -901,7 +901,7 @@ BACNET_APPLICATION_TAG bacapp_context_tag_type( time [0] Time, -- deprecated in version 1 revision 21 sequence-number [1] Unsigned (0..65535), datetime [2] BACnetDateTime - } + } */ switch (tag_number) { case TIME_STAMP_TIME: @@ -2155,6 +2155,16 @@ int bacapp_snprintf_value( ret_val = snprintf(str, str_len, "%s", bactext_node_type_name(value->type.Enumerated)); break; + case PROP_TRANSITION: + ret_val = snprintf(str, str_len, "%s", + bactext_lighting_transition( + value->type.Enumerated)); + break; + case PROP_IN_PROGRESS: + ret_val = snprintf(str, str_len, "%s", + bactext_lighting_in_progress( + value->type.Enumerated)); + break; default: ret_val = snprintf(str, str_len, "%lu", (unsigned long)value->type.Enumerated); @@ -2267,9 +2277,8 @@ int bacapp_snprintf_value( break; case BACNET_APPLICATION_TAG_XY_COLOR: /* BACnetxyColor */ - ret_val = snprintf(str, str_len, "(%f,%f)", - value->type.XY_Color.x_coordinate, - value->type.XY_Color.x_coordinate); + ret_val = xy_color_to_ascii(&value->type.XY_Color, str, + str_len); break; case BACNET_APPLICATION_TAG_COLOR_COMMAND: /* BACnetColorCommand */ @@ -2641,7 +2650,6 @@ bool bacapp_parse_application_data(BACNET_APPLICATION_TAG tag_number, int count = 0; #if defined(BACAPP_TYPES_EXTRA) unsigned a[4] = { 0 }, p = 0; - float x, y; #endif if (value && (tag_number != MAX_BACNET_APPLICATION_TAG)) { @@ -2793,13 +2801,7 @@ bool bacapp_parse_application_data(BACNET_APPLICATION_TAG tag_number, break; case BACNET_APPLICATION_TAG_XY_COLOR: /* BACnetxyColor */ - count = sscanf(argv, "%f,%f", &x, &y); - if (count == 2) { - value->type.XY_Color.x_coordinate = x; - value->type.XY_Color.y_coordinate = y; - } else { - status = false; - } + status = xy_color_from_ascii(&value->type.XY_Color, argv); break; case BACNET_APPLICATION_TAG_COLOR_COMMAND: /* FIXME: add parsing for BACnetColorCommand */ diff --git a/src/bacnet/bacdevobjpropref.c b/src/bacnet/bacdevobjpropref.c index 097ba22f..f3c3467a 100644 --- a/src/bacnet/bacdevobjpropref.c +++ b/src/bacnet/bacdevobjpropref.c @@ -100,29 +100,42 @@ int bacapp_encode_device_obj_property_ref( int len; int apdu_len = 0; + if (!value) { + return apdu_len; + } /* object-identifier [0] BACnetObjectIdentifier */ - len = encode_context_object_id(&apdu[apdu_len], 0, + len = encode_context_object_id(apdu, 0, value->objectIdentifier.type, value->objectIdentifier.instance); apdu_len += len; + if (apdu) { + apdu += len; + } /* property-identifier [1] BACnetPropertyIdentifier */ len = encode_context_enumerated( - &apdu[apdu_len], 1, value->propertyIdentifier); + apdu, 1, value->propertyIdentifier); apdu_len += len; + if (apdu) { + apdu += len; + } /* property-array-index [2] Unsigned OPTIONAL */ /* Check if needed before inserting */ if (value->arrayIndex != BACNET_ARRAY_ALL) { - len = encode_context_unsigned(&apdu[apdu_len], 2, value->arrayIndex); + len = encode_context_unsigned(apdu, 2, value->arrayIndex); apdu_len += len; + if (apdu) { + apdu += len; + } } /* device-identifier [3] BACnetObjectIdentifier OPTIONAL */ /* Likewise, device id is optional so see if needed * (set type to BACNET_NO_DEV_TYPE or something other than OBJECT_DEVICE to * omit */ if (value->deviceIdentifier.type == OBJECT_DEVICE) { - len = encode_context_object_id(&apdu[apdu_len], 3, + len = encode_context_object_id(apdu, 3, value->deviceIdentifier.type, value->deviceIdentifier.instance); apdu_len += len; } + return apdu_len; } diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h index b93acd8c..e3d56a48 100644 --- a/src/bacnet/bacenum.h +++ b/src/bacnet/bacenum.h @@ -2049,10 +2049,9 @@ typedef enum BACnetLightingInProgress { } BACNET_LIGHTING_IN_PROGRESS; typedef enum BACnetLightingTransition { - BACNET_LIGHTING_TRANSITION_IDLE = 0, + BACNET_LIGHTING_TRANSITION_NONE = 0, BACNET_LIGHTING_TRANSITION_FADE = 1, BACNET_LIGHTING_TRANSITION_RAMP = 2, - MAX_BACNET_LIGHTING_TRANSITION = 3, /* Enumerated values 0-63 are reserved for definition by ASHRAE. Enumerated values 64-255 may be used by others subject to the procedures and constraints described in Clause 23. */ diff --git a/src/bacnet/bactext.c b/src/bacnet/bactext.c index 5c07ebee..3c27c5ea 100644 --- a/src/bacnet/bactext.c +++ b/src/bacnet/bactext.c @@ -1689,14 +1689,14 @@ const char *bactext_life_safety_state_name(unsigned index) return indtext_by_index_default( life_safety_state_names, index, ASHRAE_Reserved_String); } else { - return "Invalid Safety State Message"; + return "Invalid BACnetLifeSafetyState"; } } INDTEXT_DATA lighting_in_progress[] = { { BACNET_LIGHTING_IDLE, "idle" }, { BACNET_LIGHTING_FADE_ACTIVE, "fade" }, { BACNET_LIGHTING_RAMP_ACTIVE, "ramp" }, - { BACNET_LIGHTING_NOT_CONTROLLED, "not" }, + { BACNET_LIGHTING_NOT_CONTROLLED, "not-controlled" }, { BACNET_LIGHTING_OTHER, "other" }, { BACNET_LIGHTING_TRIM_ACTIVE, "trim-active" }, { 0, NULL } }; @@ -1706,22 +1706,24 @@ const char *bactext_lighting_in_progress(unsigned index) return indtext_by_index_default( lighting_in_progress, index, ASHRAE_Reserved_String); } else { - return "Invalid Lighting In Progress Message"; + return "Invalid BACnetLightingInProgress"; } } -INDTEXT_DATA lighting_transition[] = { { BACNET_LIGHTING_TRANSITION_IDLE, - "idle" }, +INDTEXT_DATA lighting_transition[] = { + { BACNET_LIGHTING_TRANSITION_NONE, "none" }, { BACNET_LIGHTING_TRANSITION_FADE, "fade" }, { BACNET_LIGHTING_TRANSITION_RAMP, "ramp" }, { 0, NULL } }; const char *bactext_lighting_transition(unsigned index) { - if (index < MAX_BACNET_LIGHTING_TRANSITION) { + if (index < BACNET_LIGHTING_TRANSITION_PROPRIETARY_FIRST) { return indtext_by_index_default( lighting_transition, index, ASHRAE_Reserved_String); + } else if (index <= BACNET_LIGHTING_TRANSITION_PROPRIETARY_LAST) { + return Vendor_Proprietary_String; } else { - return "Invalid Lighting Transition Message"; + return "Invalid BACnetLightingTransition"; } } diff --git a/src/bacnet/basic/object/ao.c b/src/bacnet/basic/object/ao.c index 90d40677..2db64b87 100644 --- a/src/bacnet/basic/object/ao.c +++ b/src/bacnet/basic/object/ao.c @@ -1208,9 +1208,9 @@ uint32_t Analog_Output_Create(uint32_t object_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 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); } @@ -1235,9 +1235,7 @@ uint32_t Analog_Output_Create(uint32_t object_instance) pObject->Max_Pres_Value = 100; /* add to list */ index = Keylist_Data_Add(Object_List, object_instance, pObject); - if (index >= 0) { - Device_Inc_Database_Revision(); - } else { + if (index < 0) { free(pObject); return BACNET_MAX_INSTANCE; } @@ -1263,7 +1261,6 @@ bool Analog_Output_Delete(uint32_t object_instance) if (pObject) { free(pObject); status = true; - Device_Inc_Database_Revision(); } return status; @@ -1281,7 +1278,6 @@ void Analog_Output_Cleanup(void) pObject = Keylist_Data_Pop(Object_List); if (pObject) { free(pObject); - Device_Inc_Database_Revision(); } } while (pObject); Keylist_Delete(Object_List); diff --git a/src/bacnet/basic/object/bacfile.c b/src/bacnet/basic/object/bacfile.c index 51c2b939..6945dbfb 100644 --- a/src/bacnet/basic/object/bacfile.c +++ b/src/bacnet/basic/object/bacfile.c @@ -990,9 +990,9 @@ uint32_t bacfile_create(uint32_t object_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 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); } @@ -1010,9 +1010,7 @@ uint32_t bacfile_create(uint32_t object_instance) pObject->File_Access_Stream = true; /* add to list */ index = Keylist_Data_Add(Object_List, object_instance, pObject); - if (index >= 0) { - Device_Inc_Database_Revision(); - } else { + if (index < 0) { free(pObject); return BACNET_MAX_INSTANCE; } @@ -1038,7 +1036,6 @@ bool bacfile_delete(uint32_t object_instance) if (pObject) { free(pObject); status = true; - Device_Inc_Database_Revision(); } return status; @@ -1056,7 +1053,6 @@ void bacfile_cleanup(void) pObject = Keylist_Data_Pop(Object_List); if (pObject) { free(pObject); - Device_Inc_Database_Revision(); } } while (pObject); Keylist_Delete(Object_List); diff --git a/src/bacnet/basic/object/bo.c b/src/bacnet/basic/object/bo.c index cd6b0d54..f5ae225b 100644 --- a/src/bacnet/basic/object/bo.c +++ b/src/bacnet/basic/object/bo.c @@ -1183,9 +1183,9 @@ uint32_t Binary_Output_Create(uint32_t object_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 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); } @@ -1202,9 +1202,7 @@ uint32_t Binary_Output_Create(uint32_t object_instance) pObject->Changed = false; /* add to list */ index = Keylist_Data_Add(Object_List, object_instance, pObject); - if (index >= 0) { - Device_Inc_Database_Revision(); - } else { + if (index < 0) { free(pObject); return BACNET_MAX_INSTANCE; } @@ -1228,7 +1226,6 @@ void Binary_Output_Cleanup(void) pObject = Keylist_Data_Pop(Object_List); if (pObject) { free(pObject); - Device_Inc_Database_Revision(); } } while (pObject); Keylist_Delete(Object_List); @@ -1248,7 +1245,6 @@ bool Binary_Output_Delete(uint32_t object_instance) if (pObject) { free(pObject); status = true; - Device_Inc_Database_Revision(); } return status; diff --git a/src/bacnet/basic/object/channel.c b/src/bacnet/basic/object/channel.c index 173197bc..37c52760 100644 --- a/src/bacnet/basic/object/channel.c +++ b/src/bacnet/basic/object/channel.c @@ -31,10 +31,11 @@ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include +#include +#include #include #include -#include -#include #include "bacnet/bacdef.h" #include "bacnet/bacdcode.h" #include "bacnet/bacenum.h" @@ -43,18 +44,16 @@ #include "bacnet/wp.h" #include "bacnet/basic/services.h" #include "bacnet/proplist.h" -#include "bacnet/lighting.h" -#include "bacnet/basic/object/device.h" +#include "bacnet/basic/sys/keylist.h" #if defined(CHANNEL_LIGHTING_COMMAND) #include "bacnet/basic/object/lo.h" #endif +#if defined(CHANNEL_LIGHTING_COMMAND) || defined(CHANNEL_COLOR_COMMAND) +#include "bacnet/lighting.h" +#endif /* me! */ #include "bacnet/basic/object/channel.h" -#ifndef BACNET_CHANNELS_MAX -#define BACNET_CHANNELS_MAX 1 -#endif - #ifndef CONTROL_GROUPS_MAX #define CONTROL_GROUPS_MAX 8 #endif @@ -63,7 +62,7 @@ #define CHANNEL_MEMBERS_MAX 8 #endif -struct bacnet_channel_object { +struct object_data { bool Out_Of_Service : 1; BACNET_CHANNEL_VALUE Present_Value; unsigned Last_Priority; @@ -71,9 +70,14 @@ struct bacnet_channel_object { BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE Members[CHANNEL_MEMBERS_MAX]; uint16_t Number; uint32_t Control_Groups[CONTROL_GROUPS_MAX]; + const char *Object_Name; + const char *Description; }; -static struct bacnet_channel_object Channel[BACNET_CHANNELS_MAX]; +/* Key List for storing the object data sorted by instance number */ +static OS_Keylist Object_List; + +static write_property_function Write_Property_Internal_Callback; /* These arrays are used by the ReadPropertyMultiple handler property-list property (as of protocol-revision 14) */ @@ -123,10 +127,10 @@ void Channel_Property_Lists( */ bool Channel_Valid_Instance(uint32_t object_instance) { - unsigned int index; + struct object_data *pObject; - index = Channel_Instance_To_Index(object_instance); - if (index < BACNET_CHANNELS_MAX) { + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { return true; } @@ -140,7 +144,7 @@ bool Channel_Valid_Instance(uint32_t object_instance) */ unsigned Channel_Count(void) { - return BACNET_CHANNELS_MAX; + return Keylist_Count(Object_List); } /** @@ -153,11 +157,7 @@ unsigned Channel_Count(void) */ uint32_t Channel_Index_To_Instance(unsigned index) { - uint32_t instance = 1; - - instance += index; - - return instance; + return Keylist_Key(Object_List, index); } /** @@ -171,16 +171,7 @@ uint32_t Channel_Index_To_Instance(unsigned index) */ unsigned Channel_Instance_To_Index(uint32_t object_instance) { - unsigned index = BACNET_CHANNELS_MAX; - - if (object_instance) { - index = object_instance - 1; - if (index > BACNET_CHANNELS_MAX) { - index = BACNET_CHANNELS_MAX; - } - } - - return index; + return Keylist_Index(Object_List, object_instance); } /** @@ -191,12 +182,12 @@ unsigned Channel_Instance_To_Index(uint32_t object_instance) */ BACNET_CHANNEL_VALUE *Channel_Present_Value(uint32_t object_instance) { - unsigned index = 0; BACNET_CHANNEL_VALUE *cvalue = NULL; + struct object_data *pObject; - index = Channel_Instance_To_Index(object_instance); - if (index < BACNET_CHANNELS_MAX) { - cvalue = &Channel[index].Present_Value; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + cvalue = &pObject->Present_Value; } return cvalue; @@ -211,12 +202,12 @@ BACNET_CHANNEL_VALUE *Channel_Present_Value(uint32_t object_instance) */ unsigned Channel_Last_Priority(uint32_t object_instance) { - unsigned index = 0; unsigned priority = 0; + struct object_data *pObject; - index = Channel_Instance_To_Index(object_instance); - if (index < BACNET_CHANNELS_MAX) { - priority = Channel[index].Last_Priority; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + priority = pObject->Last_Priority; } return priority; @@ -231,12 +222,12 @@ unsigned Channel_Last_Priority(uint32_t object_instance) */ BACNET_WRITE_STATUS Channel_Write_Status(uint32_t object_instance) { - unsigned index = 0; BACNET_WRITE_STATUS write_status = BACNET_WRITE_STATUS_IDLE; + struct object_data *pObject; - index = Channel_Instance_To_Index(object_instance); - if (index < BACNET_CHANNELS_MAX) { - write_status = Channel[index].Write_Status; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + write_status = pObject->Write_Status; } return write_status; @@ -251,12 +242,12 @@ BACNET_WRITE_STATUS Channel_Write_Status(uint32_t object_instance) */ uint16_t Channel_Number(uint32_t object_instance) { - unsigned index = 0; uint16_t value = 0; + struct object_data *pObject; - index = Channel_Instance_To_Index(object_instance); - if (index < BACNET_CHANNELS_MAX) { - value = Channel[index].Number; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Number; } return value; @@ -274,17 +265,44 @@ uint16_t Channel_Number(uint32_t object_instance) bool Channel_Number_Set(uint32_t object_instance, uint16_t value) { bool status = false; - unsigned index = 0; + struct object_data *pObject; - index = Channel_Instance_To_Index(object_instance); - if (index < BACNET_CHANNELS_MAX) { - Channel[index].Number = value; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Number = value; status = true; } return status; } +/** + * @brief Encode a BACnetARRAY property element + * @param object_instance [in] BACnet object instance number + * @param array_index [in] array index requested: + * 0 to N for individual array members + * @param apdu [out] Buffer in which the APDU contents are built, or NULL to + * return the length of buffer if it had been built + * @return The length of the apdu encoded or + * BACNET_STATUS_ERROR for ERROR_CODE_INVALID_ARRAY_INDEX + */ +static int Channel_Reference_List_Member_Element_Encode( + uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu) +{ + int apdu_len = BACNET_STATUS_ERROR; + BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *value; + unsigned count = 0; + + count = Channel_Reference_List_Member_Count(object_instance); + if (array_index < count) { + value = Channel_Reference_List_Member_Element( + object_instance, array_index + 1); + apdu_len = bacapp_encode_device_obj_property_ref(apdu, value); + } + + return apdu_len; +} + /** * For a given object instance-number, determines the member count * @@ -315,22 +333,8 @@ static bool Channel_Reference_List_Member_Valid( */ unsigned Channel_Reference_List_Member_Count(uint32_t object_instance) { - BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL; - unsigned count = 0; - unsigned m = 0; - unsigned index = 0; - - index = Channel_Instance_To_Index(object_instance); - if (index < BACNET_CHANNELS_MAX) { - for (m = 0; m < CHANNEL_MEMBERS_MAX; m++) { - pMember = &Channel[index].Members[m]; - if (Channel_Reference_List_Member_Valid(pMember)) { - count++; - } - } - } - - return count; + (void)object_instance; + return CHANNEL_MEMBERS_MAX; } /** @@ -345,24 +349,17 @@ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *Channel_Reference_List_Member_Element( uint32_t object_instance, unsigned array_index) { BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL; - unsigned count = 0; - unsigned m = 0; - unsigned index = 0; + struct object_data *pObject; - index = Channel_Instance_To_Index(object_instance); - if (index < BACNET_CHANNELS_MAX) { - for (m = 0; m < CHANNEL_MEMBERS_MAX; m++) { - pMember = &Channel[index].Members[m]; - if (Channel_Reference_List_Member_Valid(pMember)) { - count++; - if (count == array_index) { - return pMember; - } - } + pObject = Keylist_Data(Object_List, object_instance); + if (pObject && (array_index > 0)) { + array_index--; + if (array_index < CHANNEL_MEMBERS_MAX) { + pMember = &pObject->Members[array_index]; } } - return NULL; + return pMember; } /** @@ -378,24 +375,17 @@ bool Channel_Reference_List_Member_Element_Set(uint32_t object_instance, BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMemberSrc) { BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL; - unsigned count = 0; - unsigned m = 0; - unsigned index = 0; bool status = false; + struct object_data *pObject; - index = Channel_Instance_To_Index(object_instance); - if (index < BACNET_CHANNELS_MAX) { - for (m = 0; m < CHANNEL_MEMBERS_MAX; m++) { - pMember = &Channel[index].Members[m]; - if (Channel_Reference_List_Member_Valid(pMember)) { - count++; - if (count == array_index) { - memcpy(pMember, pMemberSrc, - sizeof(BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE)); - status = true; - break; - } - } + pObject = Keylist_Data(Object_List, object_instance); + if (pObject && (array_index > 0)) { + array_index--; + if (array_index < CHANNEL_MEMBERS_MAX) { + pMember = &pObject->Members[array_index]; + memcpy(pMember, pMemberSrc, + sizeof(BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE)); + status = true; } } @@ -415,19 +405,17 @@ unsigned Channel_Reference_List_Member_Element_Add(uint32_t object_instance, BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMemberSrc) { BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL; - unsigned count = 0; + unsigned array_index = 0; unsigned m = 0; - unsigned index = 0; + struct object_data *pObject; - index = Channel_Instance_To_Index(object_instance); - if (index < BACNET_CHANNELS_MAX) { + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { for (m = 0; m < CHANNEL_MEMBERS_MAX; m++) { - pMember = &Channel[index].Members[m]; - if (Channel_Reference_List_Member_Valid(pMember)) { - count++; - } else { + pMember = &pObject->Members[m]; + if (!Channel_Reference_List_Member_Valid(pMember)) { /* first empty slot */ - count++; + array_index = 1 + m; memcpy(pMember, pMemberSrc, sizeof(BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE)); break; @@ -435,37 +423,7 @@ unsigned Channel_Reference_List_Member_Element_Add(uint32_t object_instance, } } - return count; -} - -/** - * For a given object instance-number, adds a member element - * - * @param object_instance - object-instance number of the object - * @param type - object type - * @param instance - object instance number - * @param propertyIdentifier - property identifier BACNET_PROPERTY_ID - * @param array_index - 1-based array index of object property - * - * @return array_index - 1-based array index value for added element, or - * zero if not added - */ -unsigned Channel_Reference_List_Member_Local_Add(uint32_t object_instance, - BACNET_OBJECT_TYPE type, - uint32_t instance, - BACNET_PROPERTY_ID propertyIdentifier, - uint32_t arrayIndex) -{ - BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE member = { 0 }; - - member.objectIdentifier.type = type; - member.objectIdentifier.instance = instance; - member.propertyIdentifier = propertyIdentifier; - member.arrayIndex = arrayIndex; - member.deviceIdentifier.type = OBJECT_DEVICE; - member.deviceIdentifier.instance = Device_Object_Instance_Number(); - - return Channel_Reference_List_Member_Element_Add(object_instance, &member); + return array_index; } /** @@ -474,19 +432,20 @@ unsigned Channel_Reference_List_Member_Local_Add(uint32_t object_instance, * @param object_instance - object-instance number of the object * @param array_index - 1-based array index * - * @return group number in the array + * @return group number in the array, or 0 if invalid */ uint16_t Channel_Control_Groups_Element( uint32_t object_instance, int32_t array_index) { - unsigned index = 0; uint16_t value = 0; + struct object_data *pObject; - index = Channel_Instance_To_Index(object_instance); - if ((index < BACNET_CHANNELS_MAX) && (array_index > 0) && - (array_index <= CONTROL_GROUPS_MAX)) { - array_index--; - value = Channel[index].Control_Groups[array_index]; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if ((array_index > 0) && (array_index <= CONTROL_GROUPS_MAX)) { + array_index--; + value = pObject->Control_Groups[array_index]; + } } return value; @@ -505,19 +464,47 @@ bool Channel_Control_Groups_Element_Set( uint32_t object_instance, int32_t array_index, uint16_t value) { bool status = false; - unsigned index = 0; + struct object_data *pObject; - index = Channel_Instance_To_Index(object_instance); - if ((index < BACNET_CHANNELS_MAX) && (array_index > 0) && - (array_index <= CONTROL_GROUPS_MAX)) { - array_index--; - Channel[index].Control_Groups[array_index] = value; - status = true; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if ((array_index > 0) && (array_index <= CONTROL_GROUPS_MAX)) { + array_index--; + pObject->Control_Groups[array_index] = value; + status = true; + } } return status; } +/** + * @brief Encode a BACnetARRAY property element + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index requested: + * 0 to N for individual array members + * @param apdu [out] Buffer in which the APDU contents are built, or NULL to + * return the length of buffer if it had been built + * @return The length of the apdu encoded or + * BACNET_STATUS_ERROR for ERROR_CODE_INVALID_ARRAY_INDEX + */ +static int Channel_Control_Groups_Element_Encode( + uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu) +{ + int apdu_len = BACNET_STATUS_ERROR; + uint16_t value = 1; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject && (array_index < CONTROL_GROUPS_MAX)) { + value = + Channel_Control_Groups_Element(object_instance, array_index + 1); + apdu_len = encode_application_unsigned(apdu, value); + } + + return apdu_len; +} + /** * For a given application value, copy to the channel value * @@ -610,7 +597,7 @@ bool Channel_Value_Copy( case BACNET_APPLICATION_TAG_DATE: cvalue->tag = value->tag; datetime_date_copy(&cvalue->type.Date, &value->type.Date); - apdu_len = encode_application_date(&apdu[0], &value->type.Date); + apdu_len = encode_application_date(apdu, &value->type.Date); status = true; break; #endif @@ -635,6 +622,21 @@ bool Channel_Value_Copy( &cvalue->type.Lighting_Command, &value->type.Lighting_Command); status = true; break; +#endif +#if defined(BACAPP_TYPES_EXTRA) && defined(CHANNEL_COLOR_COMMAND) + case BACNET_APPLICATION_TAG_COLOR_COMMAND: + cvalue->tag = value->tag; + color_command_copy( + &cvalue->type.Color_Command, &value->type.Color_Command); + status = true; + break; +#endif +#if defined(BACAPP_TYPES_EXTRA) && defined(CHANNEL_XY_COLOR) + case BACNET_APPLICATION_TAG_XY_COLOR: + cvalue->tag = value->tag; + xy_color_copy(&cvalue->type.XY_Color, &value->type.XY_Color); + status = true; + break; #endif default: break; @@ -646,7 +648,7 @@ bool Channel_Value_Copy( /** * For a given application value, copy to the channel value * - * @param apdu - APDU buffer for storing the encoded data + * @param apdu - APDU buffer for storing the encoded data, or NULL for length * @param apdu_max - size of APDU buffer available for storing data * @param value - BACNET_CHANNEL_VALUE value * @@ -658,86 +660,94 @@ int Channel_Value_Encode( int apdu_len = BACNET_STATUS_ERROR; (void)apdu_max; - if (!apdu || !value) { + if (!value) { return BACNET_STATUS_ERROR; } switch (value->tag) { case BACNET_APPLICATION_TAG_NULL: - apdu_len = encode_application_null(&apdu[0]); + apdu_len = encode_application_null(apdu); break; #if defined(CHANNEL_BOOLEAN) case BACNET_APPLICATION_TAG_BOOLEAN: - apdu_len = - encode_application_boolean(&apdu[0], value->type.Boolean); + apdu_len = encode_application_boolean(apdu, value->type.Boolean); break; #endif #if defined(CHANNEL_UNSIGNED) case BACNET_APPLICATION_TAG_UNSIGNED_INT: apdu_len = - encode_application_unsigned(&apdu[0], value->type.Unsigned_Int); + encode_application_unsigned(apdu, value->type.Unsigned_Int); break; #endif #if defined(CHANNEL_SIGNED) case BACNET_APPLICATION_TAG_SIGNED_INT: - apdu_len = - encode_application_signed(&apdu[0], value->type.Signed_Int); + apdu_len = encode_application_signed(apdu, value->type.Signed_Int); break; #endif #if defined(CHANNEL_REAL) case BACNET_APPLICATION_TAG_REAL: - apdu_len = encode_application_real(&apdu[0], value->type.Real); + apdu_len = encode_application_real(apdu, value->type.Real); break; #endif #if defined(CHANNEL_DOUBLE) case BACNET_APPLICATION_TAG_DOUBLE: - apdu_len = encode_application_double(&apdu[0], value->type.Double); + apdu_len = encode_application_double(apdu, value->type.Double); break; #endif #if defined(CHANNEL_OCTET_STRING) case BACNET_APPLICATION_TAG_OCTET_STRING: apdu_len = encode_application_octet_string( - &apdu[0], &value->type.Octet_String); + apdu, &value->type.Octet_String); break; #endif #if defined(CHANNEL_CHARACTER_STRING) case BACNET_APPLICATION_TAG_CHARACTER_STRING: apdu_len = encode_application_character_string( - &apdu[0], &value->type.Character_String); + apdu, &value->type.Character_String); break; #endif #if defined(CHANNEL_BIT_STRING) case BACNET_APPLICATION_TAG_BIT_STRING: apdu_len = - encode_application_bitstring(&apdu[0], &value->type.Bit_String); + encode_application_bitstring(apdu, &value->type.Bit_String); break; #endif #if defined(CHANNEL_ENUMERATED) case BACNET_APPLICATION_TAG_ENUMERATED: apdu_len = - encode_application_enumerated(&apdu[0], value->type.Enumerated); + encode_application_enumerated(apdu, value->type.Enumerated); break; #endif #if defined(CHANNEL_DATE) case BACNET_APPLICATION_TAG_DATE: - apdu_len = encode_application_date(&apdu[0], &value->type.Date); + apdu_len = encode_application_date(apdu, &value->type.Date); break; #endif #if defined(CHANNEL_TIME) case BACNET_APPLICATION_TAG_TIME: - apdu_len = encode_application_time(&apdu[0], &value->type.Time); + apdu_len = encode_application_time(apdu, &value->type.Time); break; #endif #if defined(CHANNEL_OBJECT_ID) case BACNET_APPLICATION_TAG_OBJECT_ID: - apdu_len = encode_application_object_id(&apdu[0], + apdu_len = encode_application_object_id(apdu, (int)value->type.Object_Id.type, value->type.Object_Id.instance); break; #endif #if defined(CHANNEL_LIGHTING_COMMAND) case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: - apdu_len = lighting_command_encode( - &apdu[0], &value->type.Lighting_Command); + apdu_len = + lighting_command_encode(apdu, &value->type.Lighting_Command); + break; +#endif +#if defined(CHANNEL_COLOR_COMMAND) + case BACNET_APPLICATION_TAG_COLOR_COMMAND: + apdu_len = color_command_encode(apdu, &value->type.Color_Command); + break; +#endif +#if defined(CHANNEL_XY_COLOR) + case BACNET_APPLICATION_TAG_XY_COLOR: + apdu_len = xy_color_encode(apdu, &value->type.XY_Color); break; #endif default: @@ -750,15 +760,13 @@ int Channel_Value_Encode( /** * For a given application value, coerce the encoding, if necessary * - * @param apdu - buffer to hold the encoding - * @param apdu_max - max size of the buffer to hold the encoding + * @param apdu - buffer to hold the encoding, or NULL for length * @param value - BACNET_APPLICATION_DATA_VALUE value * @param tag - application tag to be coerced, if possible * * @return number of bytes in the APDU, or BACNET_STATUS_ERROR if error. */ -int Channel_Coerce_Data_Encode(uint8_t *apdu, - unsigned max_apdu, +static int Coerce_Data_Encode(uint8_t *apdu, BACNET_APPLICATION_DATA_VALUE *value, BACNET_APPLICATION_TAG tag) { @@ -769,16 +777,18 @@ int Channel_Coerce_Data_Encode(uint8_t *apdu, int32_t signed_value = 0; bool boolean_value = false; - (void)max_apdu; - if (apdu && value) { + if (value) { switch (value->tag) { #if defined(BACAPP_NULL) case BACNET_APPLICATION_TAG_NULL: - if (tag == BACNET_APPLICATION_TAG_LIGHTING_COMMAND) { + if ((tag == BACNET_APPLICATION_TAG_LIGHTING_COMMAND) || + (tag == BACNET_APPLICATION_TAG_COLOR_COMMAND)) { apdu_len = BACNET_STATUS_ERROR; } else { /* no coercion */ - apdu[0] = value->tag; + if (apdu) { + *apdu = value->tag; + } apdu_len++; } break; @@ -786,37 +796,35 @@ int Channel_Coerce_Data_Encode(uint8_t *apdu, #if defined(BACAPP_BOOLEAN) case BACNET_APPLICATION_TAG_BOOLEAN: if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { - apdu_len = encode_application_boolean( - &apdu[0], value->type.Boolean); + apdu_len = + encode_application_boolean(apdu, value->type.Boolean); } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { if (value->type.Boolean) { unsigned_value = 1; } apdu_len = - encode_application_unsigned(&apdu[0], unsigned_value); + encode_application_unsigned(apdu, unsigned_value); } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { if (value->type.Boolean) { signed_value = 1; } - apdu_len = - encode_application_signed(&apdu[0], signed_value); + apdu_len = encode_application_signed(apdu, signed_value); } else if (tag == BACNET_APPLICATION_TAG_REAL) { if (value->type.Boolean) { float_value = 1; } - apdu_len = encode_application_real(&apdu[0], float_value); + apdu_len = encode_application_real(apdu, float_value); } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { if (value->type.Boolean) { double_value = 1; } - apdu_len = - encode_application_double(&apdu[0], double_value); + apdu_len = encode_application_double(apdu, double_value); } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { if (value->type.Boolean) { unsigned_value = 1; } apdu_len = - encode_application_enumerated(&apdu[0], unsigned_value); + encode_application_enumerated(apdu, unsigned_value); } else { apdu_len = BACNET_STATUS_ERROR; } @@ -828,36 +836,33 @@ int Channel_Coerce_Data_Encode(uint8_t *apdu, if (value->type.Unsigned_Int) { boolean_value = true; } - apdu_len = - encode_application_boolean(&apdu[0], boolean_value); + apdu_len = encode_application_boolean(apdu, boolean_value); } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { unsigned_value = value->type.Unsigned_Int; apdu_len = - encode_application_unsigned(&apdu[0], unsigned_value); + encode_application_unsigned(apdu, unsigned_value); } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { if (value->type.Unsigned_Int <= 2147483647) { signed_value = value->type.Unsigned_Int; apdu_len = - encode_application_signed(&apdu[0], signed_value); + encode_application_signed(apdu, signed_value); } else { apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_REAL) { if (value->type.Unsigned_Int <= 9999999) { float_value = (float)value->type.Unsigned_Int; - apdu_len = - encode_application_real(&apdu[0], float_value); + apdu_len = encode_application_real(apdu, float_value); } else { apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { double_value = (double)value->type.Unsigned_Int; - apdu_len = - encode_application_double(&apdu[0], double_value); + apdu_len = encode_application_double(apdu, double_value); } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { unsigned_value = value->type.Unsigned_Int; apdu_len = - encode_application_enumerated(&apdu[0], unsigned_value); + encode_application_enumerated(apdu, unsigned_value); } else { apdu_len = BACNET_STATUS_ERROR; } @@ -869,37 +874,33 @@ int Channel_Coerce_Data_Encode(uint8_t *apdu, if (value->type.Signed_Int) { boolean_value = true; } - apdu_len = - encode_application_boolean(&apdu[0], boolean_value); + apdu_len = encode_application_boolean(apdu, boolean_value); } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { if ((value->type.Signed_Int >= 0) && (value->type.Signed_Int <= 2147483647)) { unsigned_value = value->type.Signed_Int; - apdu_len = encode_application_unsigned( - &apdu[0], unsigned_value); + apdu_len = + encode_application_unsigned(apdu, unsigned_value); } else { apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { signed_value = value->type.Signed_Int; - apdu_len = - encode_application_signed(&apdu[0], signed_value); + apdu_len = encode_application_signed(apdu, signed_value); } else if (tag == BACNET_APPLICATION_TAG_REAL) { if (value->type.Signed_Int <= 9999999) { float_value = (float)value->type.Signed_Int; - apdu_len = - encode_application_real(&apdu[0], float_value); + apdu_len = encode_application_real(apdu, float_value); } else { apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { double_value = (double)value->type.Signed_Int; - apdu_len = - encode_application_double(&apdu[0], double_value); + apdu_len = encode_application_double(apdu, double_value); } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { unsigned_value = value->type.Signed_Int; apdu_len = - encode_application_enumerated(&apdu[0], unsigned_value); + encode_application_enumerated(apdu, unsigned_value); } else { apdu_len = BACNET_STATUS_ERROR; } @@ -911,14 +912,13 @@ int Channel_Coerce_Data_Encode(uint8_t *apdu, if (islessgreater(value->type.Real, 0.0F)) { boolean_value = true; } - apdu_len = - encode_application_boolean(&apdu[0], boolean_value); + apdu_len = encode_application_boolean(apdu, boolean_value); } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { if ((value->type.Real >= 0.0F) && (value->type.Real <= 2147483000.0F)) { unsigned_value = (uint32_t)value->type.Real; - apdu_len = encode_application_unsigned( - &apdu[0], unsigned_value); + apdu_len = + encode_application_unsigned(apdu, unsigned_value); } else { apdu_len = BACNET_STATUS_ERROR; } @@ -927,23 +927,22 @@ int Channel_Coerce_Data_Encode(uint8_t *apdu, (value->type.Real <= 214783000.0F)) { signed_value = (int32_t)value->type.Real; apdu_len = - encode_application_signed(&apdu[0], signed_value); + encode_application_signed(apdu, signed_value); } else { apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_REAL) { float_value = value->type.Real; - apdu_len = encode_application_real(&apdu[0], float_value); + apdu_len = encode_application_real(apdu, float_value); } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { double_value = value->type.Real; - apdu_len = - encode_application_double(&apdu[0], double_value); + apdu_len = encode_application_double(apdu, double_value); } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { if ((value->type.Real >= 0.0F) && (value->type.Real <= 2147483000.0F)) { unsigned_value = (uint32_t)value->type.Real; - apdu_len = encode_application_enumerated( - &apdu[0], unsigned_value); + apdu_len = + encode_application_enumerated(apdu, unsigned_value); } else { apdu_len = BACNET_STATUS_ERROR; } @@ -958,14 +957,13 @@ int Channel_Coerce_Data_Encode(uint8_t *apdu, if (islessgreater(value->type.Double, 0.0)) { boolean_value = true; } - apdu_len = - encode_application_boolean(&apdu[0], boolean_value); + apdu_len = encode_application_boolean(apdu, boolean_value); } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { if ((value->type.Double >= 0.0) && (value->type.Double <= 2147483000.0)) { unsigned_value = (uint32_t)value->type.Double; - apdu_len = encode_application_unsigned( - &apdu[0], unsigned_value); + apdu_len = + encode_application_unsigned(apdu, unsigned_value); } else { apdu_len = BACNET_STATUS_ERROR; } @@ -974,7 +972,7 @@ int Channel_Coerce_Data_Encode(uint8_t *apdu, (value->type.Double <= 214783000.0)) { signed_value = (int32_t)value->type.Double; apdu_len = - encode_application_signed(&apdu[0], signed_value); + encode_application_signed(apdu, signed_value); } else { apdu_len = BACNET_STATUS_ERROR; } @@ -982,21 +980,19 @@ int Channel_Coerce_Data_Encode(uint8_t *apdu, if ((value->type.Double >= 3.4E-38) && (value->type.Double <= 3.4E+38)) { float_value = (float)value->type.Double; - apdu_len = - encode_application_real(&apdu[0], float_value); + apdu_len = encode_application_real(apdu, float_value); } else { apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { double_value = value->type.Double; - apdu_len = - encode_application_double(&apdu[0], double_value); + apdu_len = encode_application_double(apdu, double_value); } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { if ((value->type.Double >= 0.0) && (value->type.Double <= 2147483000.0)) { unsigned_value = (uint32_t)value->type.Double; - apdu_len = encode_application_enumerated( - &apdu[0], unsigned_value); + apdu_len = + encode_application_enumerated(apdu, unsigned_value); } else { apdu_len = BACNET_STATUS_ERROR; } @@ -1011,36 +1007,33 @@ int Channel_Coerce_Data_Encode(uint8_t *apdu, if (value->type.Enumerated) { boolean_value = true; } - apdu_len = - encode_application_boolean(&apdu[0], boolean_value); + apdu_len = encode_application_boolean(apdu, boolean_value); } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { unsigned_value = value->type.Enumerated; apdu_len = - encode_application_unsigned(&apdu[0], unsigned_value); + encode_application_unsigned(apdu, unsigned_value); } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { if (value->type.Enumerated <= 2147483647) { signed_value = value->type.Enumerated; apdu_len = - encode_application_signed(&apdu[0], signed_value); + encode_application_signed(apdu, signed_value); } else { apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_REAL) { if (value->type.Enumerated <= 9999999) { float_value = (float)value->type.Enumerated; - apdu_len = - encode_application_real(&apdu[0], float_value); + apdu_len = encode_application_real(apdu, float_value); } else { apdu_len = BACNET_STATUS_ERROR; } } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { double_value = (double)value->type.Enumerated; - apdu_len = - encode_application_double(&apdu[0], double_value); + apdu_len = encode_application_double(apdu, double_value); } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { unsigned_value = value->type.Enumerated; apdu_len = - encode_application_enumerated(&apdu[0], unsigned_value); + encode_application_enumerated(apdu, unsigned_value); } else { apdu_len = BACNET_STATUS_ERROR; } @@ -1050,7 +1043,22 @@ int Channel_Coerce_Data_Encode(uint8_t *apdu, case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: if (tag == BACNET_APPLICATION_TAG_LIGHTING_COMMAND) { apdu_len = lighting_command_encode( - &apdu[0], &value->type.Lighting_Command); + apdu, &value->type.Lighting_Command); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + break; + case BACNET_APPLICATION_TAG_COLOR_COMMAND: + if (tag == BACNET_APPLICATION_TAG_COLOR_COMMAND) { + apdu_len = + color_command_encode(apdu, &value->type.Color_Command); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + break; + case BACNET_APPLICATION_TAG_XY_COLOR: + if (tag == BACNET_APPLICATION_TAG_XY_COLOR) { + apdu_len = xy_color_encode(apdu, &value->type.XY_Color); } else { apdu_len = BACNET_STATUS_ERROR; } @@ -1065,6 +1073,32 @@ int Channel_Coerce_Data_Encode(uint8_t *apdu, return apdu_len; } +/** + * For a given application value, coerce the encoding, if necessary + * + * @param apdu - buffer to hold the encoding, or null for length + * @param value - BACNET_APPLICATION_DATA_VALUE value + * @param tag - application tag to be coerced, if possible + * + * @return number of bytes in the APDU, or BACNET_STATUS_ERROR if error. + */ +int Channel_Coerce_Data_Encode(uint8_t *apdu, + size_t apdu_size, + BACNET_APPLICATION_DATA_VALUE *value, + BACNET_APPLICATION_TAG tag) +{ + int len; + + len = Coerce_Data_Encode(NULL, value, tag); + if ((len > 0) && (len <= apdu_size)) { + len = Coerce_Data_Encode(apdu, value, tag); + } else { + len = BACNET_STATUS_ERROR; + } + + return len; +} + /** * For a given object instance-number, sets the present-value at a given * priority 1..16. @@ -1136,6 +1170,34 @@ bool Channel_Write_Member_Value( status = true; } } + } else if (wp_data->object_type == OBJECT_COLOR) { + if ((wp_data->object_property == PROP_PRESENT_VALUE) && + (wp_data->array_index == BACNET_ARRAY_ALL)) { + apdu_len = Channel_Coerce_Data_Encode(wp_data->application_data, + wp_data->application_data_len, value, + BACNET_APPLICATION_TAG_XY_COLOR); + if (apdu_len != BACNET_STATUS_ERROR) { + wp_data->application_data_len = apdu_len; + status = true; + } + } else if ((wp_data->object_property == PROP_COLOR_COMMAND) && + (wp_data->array_index == BACNET_ARRAY_ALL)) { + apdu_len = Channel_Coerce_Data_Encode(wp_data->application_data, + wp_data->application_data_len, value, + BACNET_APPLICATION_TAG_COLOR_COMMAND); + if (apdu_len != BACNET_STATUS_ERROR) { + wp_data->application_data_len = apdu_len; + status = true; + } + } + } else if (wp_data->object_type == OBJECT_COLOR_TEMPERATURE) { + apdu_len = Channel_Coerce_Data_Encode(wp_data->application_data, + wp_data->application_data_len, value, + BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (apdu_len != BACNET_STATUS_ERROR) { + wp_data->application_data_len = apdu_len; + status = true; + } } } @@ -1146,11 +1208,13 @@ bool Channel_Write_Member_Value( * For a given object instance-number, sets the present-value at a given * priority 1..16. * - * @param wp_data - all of the WriteProperty data structure + * @param pObject - object instance data + * @param value - application value + * @param priority - BACnet priority 0=none,1..16 * * @return true if values are within range and present-value is sent. */ -static bool Channel_Write_Members(struct bacnet_channel_object *pChannel, +static bool Channel_Write_Members(struct object_data *pObject, BACNET_APPLICATION_DATA_VALUE *value, uint8_t priority) { @@ -1159,10 +1223,10 @@ static bool Channel_Write_Members(struct bacnet_channel_object *pChannel, unsigned m = 0; BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL; - if (pChannel && value) { - pChannel->Write_Status = BACNET_WRITE_STATUS_IN_PROGRESS; + if (pObject && value) { + pObject->Write_Status = BACNET_WRITE_STATUS_IN_PROGRESS; for (m = 0; m < CHANNEL_MEMBERS_MAX; m++) { - pMember = &pChannel->Members[m]; + pMember = &pObject->Members[m]; /* NOTE: our implementation is for internal objects only */ /* NOTE: we could check to match our Device ID, but then we would need to update all channels when our device ID @@ -1179,14 +1243,16 @@ static bool Channel_Write_Members(struct bacnet_channel_object *pChannel, wp_data.application_data_len = sizeof(wp_data.application_data); status = Channel_Write_Member_Value(&wp_data, value); if (status) { - status = Device_Write_Property(&wp_data); + if (Write_Property_Internal_Callback) { + status = Write_Property_Internal_Callback(&wp_data); + } } else { - pChannel->Write_Status = BACNET_WRITE_STATUS_FAILED; + pObject->Write_Status = BACNET_WRITE_STATUS_FAILED; } } } - if (pChannel->Write_Status == BACNET_WRITE_STATUS_IN_PROGRESS) { - pChannel->Write_Status = BACNET_WRITE_STATUS_SUCCESSFUL; + if (pObject->Write_Status == BACNET_WRITE_STATUS_IN_PROGRESS) { + pObject->Write_Status = BACNET_WRITE_STATUS_SUCCESSFUL; } } @@ -1197,26 +1263,25 @@ static bool Channel_Write_Members(struct bacnet_channel_object *pChannel, * For a given object instance-number, sets the present-value at a given * priority 1..16. * - * @param wp_data - all of the WriteProperty data structure - * - * @return true if values are within range and present-value is sent. + * @param wp_data - all of the WriteProperty data structure + * @param value - application value + * @return true if values are within range and present-value is sent. */ bool Channel_Present_Value_Set( BACNET_WRITE_PROPERTY_DATA *wp_data, BACNET_APPLICATION_DATA_VALUE *value) { - unsigned index = 0; bool status = false; + struct object_data *pObject; - index = Channel_Instance_To_Index(wp_data->object_instance); - if (index < BACNET_CHANNELS_MAX) { + pObject = Keylist_Data(Object_List, wp_data->object_instance); + if (pObject) { if ((wp_data->priority > 0) && (wp_data->priority <= BACNET_MAX_PRIORITY)) { if (wp_data->priority != 6 /* reserved */) { - status = - Channel_Value_Copy(&Channel[index].Present_Value, value); + status = Channel_Value_Copy(&pObject->Present_Value, value); (void)status; - status = Channel_Write_Members( - &Channel[index], value, wp_data->priority); + status = + Channel_Write_Members(pObject, value, wp_data->priority); (void)status; status = true; } else { @@ -1248,14 +1313,42 @@ bool Channel_Present_Value_Set( bool Channel_Object_Name( uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) { - char text_string[32] = ""; bool status = false; - unsigned index = 0; + char name_text[24] = "CHANNEL-4194303"; + struct object_data *pObject; - index = Channel_Instance_To_Index(object_instance); - if (index < BACNET_CHANNELS_MAX) { - sprintf(text_string, "CHANNEL %lu", (unsigned long)object_instance); - status = characterstring_init_ansi(object_name, text_string); + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (pObject->Object_Name) { + status = + characterstring_init_ansi(object_name, pObject->Object_Name); + } else { + snprintf(name_text, sizeof(name_text), "CHANNEL-%lu", + (unsigned long)object_instance); + status = characterstring_init_ansi(object_name, name_text); + } + } + + return status; +} + +/** + * 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 Channel_Name_Set(uint32_t object_instance, char *new_name) +{ + bool status = false; /* return value */ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject && new_name) { + status = true; + pObject->Object_Name = new_name; } return status; @@ -1269,14 +1362,14 @@ bool Channel_Object_Name( * * @return out-of-service property value */ -bool Channel_Out_Of_Service(uint32_t instance) +bool Channel_Out_Of_Service(uint32_t object_instance) { - unsigned int index = 0; bool value = false; + struct object_data *pObject; - index = Channel_Instance_To_Index(instance); - if (index < BACNET_CHANNELS_MAX) { - value = Channel[index].Out_Of_Service; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Out_Of_Service; } return value; @@ -1290,13 +1383,13 @@ bool Channel_Out_Of_Service(uint32_t instance) * * @return true if the out-of-service property value was set */ -void Channel_Out_Of_Service_Set(uint32_t instance, bool value) +void Channel_Out_Of_Service_Set(uint32_t object_instance, bool value) { - unsigned int index = 0; + struct object_data *pObject; - index = Channel_Instance_To_Index(instance); - if (index < BACNET_CHANNELS_MAX) { - Channel[index].Out_Of_Service = value; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Out_Of_Service = value; } } @@ -1312,16 +1405,14 @@ void Channel_Out_Of_Service_Set(uint32_t instance, bool value) */ int Channel_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_CHANNEL_VALUE *cvalue = NULL; uint32_t unsigned_value = 0; - unsigned i = 0; unsigned count = 0; bool state = false; - BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL; + int apdu_size = 0; uint8_t *apdu = NULL; if ((rpdata == NULL) || (rpdata->application_data == NULL) || @@ -1329,34 +1420,34 @@ int Channel_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) return 0; } apdu = rpdata->application_data; + apdu_size = rpdata->application_data_len; switch (rpdata->object_property) { case PROP_OBJECT_IDENTIFIER: apdu_len = encode_application_object_id( - &apdu[0], OBJECT_CHANNEL, rpdata->object_instance); + apdu, OBJECT_CHANNEL, rpdata->object_instance); break; case PROP_OBJECT_NAME: Channel_Object_Name(rpdata->object_instance, &char_string); - apdu_len = - encode_application_character_string(&apdu[0], &char_string); + apdu_len = encode_application_character_string(apdu, &char_string); break; case PROP_OBJECT_TYPE: - apdu_len = encode_application_enumerated(&apdu[0], OBJECT_CHANNEL); + apdu_len = encode_application_enumerated(apdu, OBJECT_CHANNEL); break; case PROP_PRESENT_VALUE: cvalue = Channel_Present_Value(rpdata->object_instance); - apdu_len = Channel_Value_Encode(&apdu[0], MAX_APDU, cvalue); + apdu_len = Channel_Value_Encode(apdu, MAX_APDU, cvalue); if (apdu_len == BACNET_STATUS_ERROR) { - apdu_len = encode_application_null(&apdu[0]); + apdu_len = encode_application_null(apdu); } break; case PROP_LAST_PRIORITY: unsigned_value = Channel_Last_Priority(rpdata->object_instance); - apdu_len = encode_application_unsigned(&apdu[0], unsigned_value); + apdu_len = encode_application_unsigned(apdu, unsigned_value); break; case PROP_WRITE_STATUS: unsigned_value = (BACNET_WRITE_STATUS)Channel_Write_Status( rpdata->object_instance); - apdu_len = encode_application_enumerated(&apdu[0], unsigned_value); + apdu_len = encode_application_enumerated(apdu, unsigned_value); break; case PROP_STATUS_FLAGS: bitstring_init(&bit_string); @@ -1365,95 +1456,41 @@ int Channel_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); state = Channel_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); + apdu_len = encode_application_bitstring(apdu, &bit_string); break; case PROP_OUT_OF_SERVICE: state = Channel_Out_Of_Service(rpdata->object_instance); - apdu_len = encode_application_boolean(&apdu[0], state); + apdu_len = encode_application_boolean(apdu, state); break; case PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES: - if (rpdata->array_index == 0) { - /* Array element zero is the number of elements in the array */ - count = Channel_Reference_List_Member_Count( - rpdata->object_instance); - apdu_len = encode_application_unsigned(&apdu[0], count); - } else if (rpdata->array_index == BACNET_ARRAY_ALL) { - /* if no index was specified, then try to encode the entire list - */ - /* into one packet. */ - count = Channel_Reference_List_Member_Count( - rpdata->object_instance); - for (i = 1; i <= count; i++) { - pMember = Channel_Reference_List_Member_Element( - rpdata->object_instance, i); - len = bacapp_encode_device_obj_property_ref( - &apdu[apdu_len], pMember); - /* 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 { - /* a specific element was requested */ - count = Channel_Reference_List_Member_Count( - rpdata->object_instance); - if (rpdata->array_index <= count) { - pMember = Channel_Reference_List_Member_Element( - rpdata->object_instance, rpdata->array_index); - apdu_len += bacapp_encode_device_obj_property_ref( - &apdu[0], pMember); - } else { - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; - apdu_len = BACNET_STATUS_ERROR; - } + count = + Channel_Reference_List_Member_Count(rpdata->object_instance); + apdu_len = bacnet_array_encode(rpdata->object_instance, + rpdata->array_index, + Channel_Reference_List_Member_Element_Encode, count, apdu, + apdu_size); + if (apdu_len == BACNET_STATUS_ABORT) { + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + } else if (apdu_len == BACNET_STATUS_ERROR) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; } break; case PROP_CHANNEL_NUMBER: unsigned_value = Channel_Number(rpdata->object_instance); - apdu_len = encode_application_unsigned(&apdu[0], unsigned_value); + apdu_len = encode_application_unsigned(apdu, unsigned_value); break; case PROP_CONTROL_GROUPS: - if (rpdata->array_index == 0) { - /* Array element zero is the number of elements in the array */ - apdu_len = - encode_application_unsigned(&apdu[0], CONTROL_GROUPS_MAX); - } else if (rpdata->array_index == BACNET_ARRAY_ALL) { - /* if no index was specified, then try to encode the entire list - */ - /* into one packet. */ - for (i = 1; i <= CONTROL_GROUPS_MAX; i++) { - unsigned_value = Channel_Control_Groups_Element( - rpdata->object_instance, i); - len = encode_application_unsigned( - &apdu[apdu_len], unsigned_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 { - /* a specific element was requested */ - if (rpdata->array_index <= CONTROL_GROUPS_MAX) { - unsigned_value = Channel_Control_Groups_Element( - rpdata->object_instance, rpdata->array_index); - apdu_len = encode_application_unsigned( - &apdu[apdu_len], unsigned_value); - } else { - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; - apdu_len = BACNET_STATUS_ERROR; - } + apdu_len = bacnet_array_encode(rpdata->object_instance, + rpdata->array_index, Channel_Control_Groups_Element_Encode, + CONTROL_GROUPS_MAX, apdu, apdu_size); + if (apdu_len == BACNET_STATUS_ABORT) { + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + } else if (apdu_len == BACNET_STATUS_ERROR) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; } break; default: @@ -1463,7 +1500,7 @@ int Channel_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) break; } /* only array properties can have array options */ - if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) && + if ((apdu_len >= 0) && (rpdata->object_property != PROP_CONTROL_GROUPS) && (rpdata->object_property != PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES) && (rpdata->array_index != BACNET_ARRAY_ALL)) { rpdata->error_class = ERROR_CLASS_PROPERTY; @@ -1502,7 +1539,8 @@ bool Channel_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; return false; } - if ((wp_data->object_property != PROP_PRIORITY_ARRAY) && + if ((wp_data->object_property != PROP_CONTROL_GROUPS) && + (wp_data->object_property != PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES) && (wp_data->array_index != BACNET_ARRAY_ALL)) { /* only array properties can have array options */ wp_data->error_class = ERROR_CLASS_PROPERTY; @@ -1619,31 +1657,116 @@ bool Channel_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) } /** - * Initializes the Channel object data + * @brief Sets a callback used when present-value is written from BACnet + * @param cb - callback used to provide indications */ -void Channel_Init(void) +void Channel_Write_Property_Internal_Callback_Set(write_property_function cb) { - unsigned i, m, g; + Write_Property_Internal_Callback = cb; +} - for (i = 0; i < BACNET_CHANNELS_MAX; i++) { - Channel[i].Present_Value.tag = BACNET_APPLICATION_TAG_EMPTYLIST; - Channel[i].Out_Of_Service = false; - Channel[i].Last_Priority = BACNET_NO_PRIORITY; - Channel[i].Write_Status = BACNET_WRITE_STATUS_IDLE; - for (m = 0; m < CHANNEL_MEMBERS_MAX; m++) { - Channel[i].Members[m].objectIdentifier.type = - OBJECT_LIGHTING_OUTPUT; - Channel[i].Members[m].objectIdentifier.instance = i + 1; - Channel[i].Members[m].propertyIdentifier = PROP_LIGHTING_COMMAND; - Channel[i].Members[m].arrayIndex = BACNET_ARRAY_ALL; - Channel[i].Members[m].deviceIdentifier.type = OBJECT_DEVICE; - Channel[i].Members[m].deviceIdentifier.instance = 0; - } - Channel[i].Number = 0; - for (g = 0; g < CONTROL_GROUPS_MAX; g++) { - Channel[i].Control_Groups[g] = 0; +/** + * @brief Creates a new object + * @param object_instance - object-instance number of the object + * @return the object-instance that was created, or BACNET_MAX_INSTANCE + */ +uint32_t Channel_Create(uint32_t object_instance) +{ + struct object_data *pObject = NULL; + int index = 0; + unsigned m, g; + + 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) { + pObject = calloc(1, sizeof(struct object_data)); + if (pObject) { + /* channel defaults */ + pObject->Object_Name = NULL; + pObject->Present_Value.tag = BACNET_APPLICATION_TAG_EMPTYLIST; + pObject->Out_Of_Service = false; + pObject->Last_Priority = BACNET_NO_PRIORITY; + pObject->Write_Status = BACNET_WRITE_STATUS_IDLE; + for (m = 0; m < CHANNEL_MEMBERS_MAX; m++) { + pObject->Members[m].objectIdentifier.type = + OBJECT_LIGHTING_OUTPUT; + pObject->Members[m].objectIdentifier.instance = + BACNET_MAX_INSTANCE; + pObject->Members[m].propertyIdentifier = PROP_PRESENT_VALUE; + pObject->Members[m].arrayIndex = BACNET_ARRAY_ALL; + pObject->Members[m].deviceIdentifier.type = OBJECT_DEVICE; + pObject->Members[m].deviceIdentifier.instance = + BACNET_MAX_INSTANCE; + } + pObject->Number = 0; + for (g = 0; g < CONTROL_GROUPS_MAX; g++) { + pObject->Control_Groups[g] = 0; + } + /* add to list */ + index = Keylist_Data_Add(Object_List, object_instance, pObject); + if (index < 0) { + free(pObject); + return BACNET_MAX_INSTANCE; + } + } else { + return BACNET_MAX_INSTANCE; } } - return; + return object_instance; +} + +/** + * Deletes a dynamically created object + * @param object_instance - object-instance number of the object + * @return true if the object is deleted + */ +bool Channel_Delete(uint32_t object_instance) +{ + bool status = false; + struct object_data *pObject = NULL; + + pObject = Keylist_Data_Delete(Object_List, object_instance); + if (pObject) { + free(pObject); + status = true; + } + + return status; +} + +/** + * Deletes all the dynamic objects and their data + */ +void Channel_Cleanup(void) +{ + struct object_data *pObject; + + if (Object_List) { + do { + pObject = Keylist_Data_Pop(Object_List); + if (pObject) { + free(pObject); + } + } while (pObject); + Keylist_Delete(Object_List); + Object_List = NULL; + } +} + +/** + * Initializes the object data + */ +void Channel_Init(void) +{ + Object_List = Keylist_Create(); } diff --git a/src/bacnet/basic/object/channel.h b/src/bacnet/basic/object/channel.h index fad67f30..c15aa572 100644 --- a/src/bacnet/basic/object/channel.h +++ b/src/bacnet/basic/object/channel.h @@ -7,8 +7,8 @@ * @section DESCRIPTION * * The Channel object is a command object without a priority array, and the - * present-value property uses a priority array and a single precision floating point - * data type. + * present-value property uses a priority array and a single precision floating + * point data type. * * @section LICENSE * @@ -42,31 +42,21 @@ #include "bacnet/wp.h" #include "bacnet/basic/object/lo.h" -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - /* BACNET_CHANNEL_VALUE decodes WriteProperty service requests Choose the datatypes that your application supports */ -#if !(defined(CHANNEL_NUMERIC) || \ - defined(CHANNEL_NULL) || \ - defined(CHANNEL_BOOLEAN) || \ - defined(CHANNEL_UNSIGNED) || \ - defined(CHANNEL_SIGNED) || \ - defined(CHANNEL_REAL) || \ - defined(CHANNEL_DOUBLE) || \ - defined(CHANNEL_OCTET_STRING) || \ - defined(CHANNEL_CHARACTER_STRING) || \ - defined(CHANNEL_BIT_STRING) || \ - defined(CHANNEL_ENUMERATED) || \ - defined(CHANNEL_DATE) || \ - defined(CHANNEL_TIME) || \ - defined(CHANNEL_OBJECT_ID) || \ - defined(CHANNEL_LIGHTING_COMMAND)) +#if !(defined(CHANNEL_NUMERIC) || defined(CHANNEL_NULL) || \ + defined(CHANNEL_BOOLEAN) || defined(CHANNEL_UNSIGNED) || \ + defined(CHANNEL_SIGNED) || defined(CHANNEL_REAL) || \ + defined(CHANNEL_DOUBLE) || defined(CHANNEL_OCTET_STRING) || \ + defined(CHANNEL_CHARACTER_STRING) || defined(CHANNEL_BIT_STRING) || \ + defined(CHANNEL_ENUMERATED) || defined(CHANNEL_DATE) || \ + defined(CHANNEL_TIME) || defined(CHANNEL_OBJECT_ID) || \ + defined(CHANNEL_LIGHTING_COMMAND) || defined(CHANNEL_XY_COLOR) || \ + defined(CHANNEL_COLOR_COMMAND)) #define CHANNEL_NUMERIC #endif -#if defined (CHANNEL_NUMERIC) +#if defined(CHANNEL_NUMERIC) #define CHANNEL_NULL #define CHANNEL_BOOLEAN #define CHANNEL_UNSIGNED @@ -75,152 +65,156 @@ extern "C" { #define CHANNEL_DOUBLE #define CHANNEL_ENUMERATED #define CHANNEL_LIGHTING_COMMAND +#define CHANNEL_COLOR_COMMAND +#define CHANNEL_XY_COLOR #endif - typedef struct BACnet_Channel_Value_t { - uint8_t tag; - union { - /* NULL - not needed as it is encoded in the tag alone */ -#if defined (CHANNEL_BOOLEAN) - bool Boolean; +typedef struct BACnet_Channel_Value_t { + uint8_t tag; + union { + /* NULL - not needed as it is encoded in the tag alone */ +#if defined(CHANNEL_BOOLEAN) + bool Boolean; #endif -#if defined (CHANNEL_UNSIGNED) - uint32_t Unsigned_Int; +#if defined(CHANNEL_UNSIGNED) + uint32_t Unsigned_Int; #endif -#if defined (CHANNEL_SIGNED) - int32_t Signed_Int; +#if defined(CHANNEL_SIGNED) + int32_t Signed_Int; #endif -#if defined (CHANNEL_REAL) - float Real; +#if defined(CHANNEL_REAL) + float Real; #endif -#if defined (CHANNEL_DOUBLE) - double Double; +#if defined(CHANNEL_DOUBLE) + double Double; #endif -#if defined (CHANNEL_OCTET_STRING) - BACNET_OCTET_STRING Octet_String; +#if defined(CHANNEL_OCTET_STRING) + BACNET_OCTET_STRING Octet_String; #endif -#if defined (CHANNEL_CHARACTER_STRING) - BACNET_CHARACTER_STRING Character_String; +#if defined(CHANNEL_CHARACTER_STRING) + BACNET_CHARACTER_STRING Character_String; #endif -#if defined (CHANNEL_BIT_STRING) - BACNET_BIT_STRING Bit_String; +#if defined(CHANNEL_BIT_STRING) + BACNET_BIT_STRING Bit_String; #endif -#if defined (CHANNEL_ENUMERATED) - uint32_t Enumerated; +#if defined(CHANNEL_ENUMERATED) + uint32_t Enumerated; #endif -#if defined (CHANNEL_DATE) - BACNET_DATE Date; +#if defined(CHANNEL_DATE) + BACNET_DATE Date; #endif -#if defined (CHANNEL_TIME) - BACNET_TIME Time; +#if defined(CHANNEL_TIME) + BACNET_TIME Time; #endif -#if defined (CHANNEL_OBJECT_ID) - BACNET_OBJECT_ID Object_Id; +#if defined(CHANNEL_OBJECT_ID) + BACNET_OBJECT_ID Object_Id; #endif -#if defined (CHANNEL_LIGHTING_COMMAND) - BACNET_LIGHTING_COMMAND Lighting_Command; +#if defined(CHANNEL_LIGHTING_COMMAND) + BACNET_LIGHTING_COMMAND Lighting_Command; #endif - } type; - /* simple linked list if needed */ - struct BACnet_Channel_Value_t *next; - } BACNET_CHANNEL_VALUE; +#if defined(CHANNEL_COLOR_COMMAND) + BACNET_COLOR_COMMAND Color_Command; +#endif +#if defined(CHANNEL_XY_COLOR) + BACNET_XY_COLOR XY_Color; +#endif + } type; + /* simple linked list if needed */ + struct BACnet_Channel_Value_t *next; +} BACNET_CHANNEL_VALUE; - BACNET_STACK_EXPORT - void Channel_Property_Lists(const int **pRequired, - const int **pOptional, - const int **pProprietary); - BACNET_STACK_EXPORT - bool Channel_Valid_Instance(uint32_t object_instance); - BACNET_STACK_EXPORT - unsigned Channel_Count(void); - BACNET_STACK_EXPORT - uint32_t Channel_Index_To_Instance(unsigned index); - BACNET_STACK_EXPORT - unsigned Channel_Instance_To_Index(uint32_t instance); - BACNET_STACK_EXPORT - bool Channel_Object_Instance_Add(uint32_t instance); +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +BACNET_STACK_EXPORT +void Channel_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary); +BACNET_STACK_EXPORT +bool Channel_Valid_Instance(uint32_t object_instance); +BACNET_STACK_EXPORT +unsigned Channel_Count(void); +BACNET_STACK_EXPORT +uint32_t Channel_Index_To_Instance(unsigned index); +BACNET_STACK_EXPORT +unsigned Channel_Instance_To_Index(uint32_t instance); +BACNET_STACK_EXPORT +bool Channel_Object_Instance_Add(uint32_t instance); - BACNET_STACK_EXPORT - bool Channel_Object_Name(uint32_t object_instance, - BACNET_CHARACTER_STRING * object_name); - BACNET_STACK_EXPORT - bool Channel_Name_Set(uint32_t object_instance, - char *new_name); +BACNET_STACK_EXPORT +bool Channel_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name); +BACNET_STACK_EXPORT +bool Channel_Name_Set(uint32_t object_instance, char *new_name); - BACNET_STACK_EXPORT - int Channel_Read_Property(BACNET_READ_PROPERTY_DATA * rpdata); - BACNET_STACK_EXPORT - bool Channel_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data); +BACNET_STACK_EXPORT +int Channel_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata); +BACNET_STACK_EXPORT +bool Channel_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data); - BACNET_STACK_EXPORT - BACNET_CHANNEL_VALUE * Channel_Present_Value(uint32_t object_instance); - BACNET_STACK_EXPORT - bool Channel_Present_Value_Set( - BACNET_WRITE_PROPERTY_DATA * wp_data, - BACNET_APPLICATION_DATA_VALUE * value); +BACNET_STACK_EXPORT +BACNET_CHANNEL_VALUE *Channel_Present_Value(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Channel_Present_Value_Set( + BACNET_WRITE_PROPERTY_DATA *wp_data, BACNET_APPLICATION_DATA_VALUE *value); - BACNET_STACK_EXPORT - bool Channel_Out_Of_Service(uint32_t object_instance); - BACNET_STACK_EXPORT - void Channel_Out_Of_Service_Set(uint32_t object_instance, - bool oos_flag); +BACNET_STACK_EXPORT +bool Channel_Out_Of_Service(uint32_t object_instance); +BACNET_STACK_EXPORT +void Channel_Out_Of_Service_Set(uint32_t object_instance, bool oos_flag); - BACNET_STACK_EXPORT - unsigned Channel_Last_Priority(uint32_t object_instance); - BACNET_STACK_EXPORT - BACNET_WRITE_STATUS Channel_Write_Status(uint32_t object_instance); - uint16_t Channel_Number(uint32_t object_instance); - BACNET_STACK_EXPORT - bool Channel_Number_Set(uint32_t object_instance, uint16_t value); +BACNET_STACK_EXPORT +unsigned Channel_Last_Priority(uint32_t object_instance); +BACNET_STACK_EXPORT +BACNET_WRITE_STATUS Channel_Write_Status(uint32_t object_instance); +uint16_t Channel_Number(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Channel_Number_Set(uint32_t object_instance, uint16_t value); - BACNET_STACK_EXPORT - unsigned Channel_Reference_List_Member_Count(uint32_t object_instance); - BACNET_STACK_EXPORT - BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE * - Channel_Reference_List_Member_Element(uint32_t object_instance, - unsigned element); - BACNET_STACK_EXPORT - bool Channel_Reference_List_Member_Element_Set(uint32_t object_instance, - unsigned array_index, - BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMemberSrc); - BACNET_STACK_EXPORT - unsigned Channel_Reference_List_Member_Element_Add(uint32_t object_instance, - BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMemberSrc); - BACNET_STACK_EXPORT - unsigned Channel_Reference_List_Member_Local_Add( - uint32_t object_instance, - BACNET_OBJECT_TYPE type, - uint32_t instance, - BACNET_PROPERTY_ID propertyIdentifier, - uint32_t arrayIndex); - BACNET_STACK_EXPORT - uint16_t Channel_Control_Groups_Element( - uint32_t object_instance, - int32_t array_index); - BACNET_STACK_EXPORT - bool Channel_Control_Groups_Element_Set( - uint32_t object_instance, - int32_t array_index, - uint16_t value); - BACNET_STACK_EXPORT - bool Channel_Value_Copy(BACNET_CHANNEL_VALUE * cvalue, - BACNET_APPLICATION_DATA_VALUE * value); - BACNET_STACK_EXPORT - int Channel_Value_Encode(uint8_t *apdu, int apdu_max, - BACNET_CHANNEL_VALUE * value); - BACNET_STACK_EXPORT - int Channel_Coerce_Data_Encode( - uint8_t * apdu, - unsigned max_apdu, - BACNET_APPLICATION_DATA_VALUE * value, - BACNET_APPLICATION_TAG tag); - BACNET_STACK_EXPORT - bool Channel_Write_Member_Value( - BACNET_WRITE_PROPERTY_DATA * wp_data, - BACNET_APPLICATION_DATA_VALUE * value); +BACNET_STACK_EXPORT +unsigned Channel_Reference_List_Member_Count(uint32_t object_instance); +BACNET_STACK_EXPORT +BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *Channel_Reference_List_Member_Element( + uint32_t object_instance, unsigned element); +BACNET_STACK_EXPORT +bool Channel_Reference_List_Member_Element_Set(uint32_t object_instance, + unsigned array_index, + BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMemberSrc); +BACNET_STACK_EXPORT +unsigned Channel_Reference_List_Member_Element_Add(uint32_t object_instance, + BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMemberSrc); +BACNET_STACK_EXPORT +uint16_t Channel_Control_Groups_Element( + uint32_t object_instance, int32_t array_index); +BACNET_STACK_EXPORT +bool Channel_Control_Groups_Element_Set( + uint32_t object_instance, int32_t array_index, uint16_t value); +BACNET_STACK_EXPORT +bool Channel_Value_Copy( + BACNET_CHANNEL_VALUE *cvalue, BACNET_APPLICATION_DATA_VALUE *value); +BACNET_STACK_EXPORT +int Channel_Value_Encode( + uint8_t *apdu, int apdu_max, BACNET_CHANNEL_VALUE *value); +BACNET_STACK_EXPORT +int Channel_Coerce_Data_Encode(uint8_t *apdu, + size_t apdu_size, + BACNET_APPLICATION_DATA_VALUE *value, + BACNET_APPLICATION_TAG tag); +BACNET_STACK_EXPORT +bool Channel_Write_Member_Value( + BACNET_WRITE_PROPERTY_DATA *wp_data, BACNET_APPLICATION_DATA_VALUE *value); - BACNET_STACK_EXPORT - void Channel_Init(void); +BACNET_STACK_EXPORT +void Channel_Write_Property_Internal_Callback_Set( + write_property_function cb); + +BACNET_STACK_EXPORT +uint32_t Channel_Create(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Channel_Delete(uint32_t object_instance); +BACNET_STACK_EXPORT +void Channel_Cleanup(void); +BACNET_STACK_EXPORT +void Channel_Init(void); #ifdef __cplusplus } diff --git a/src/bacnet/basic/object/client/device-client.c b/src/bacnet/basic/object/client/device-client.c index bb74d75a..e3497b8c 100644 --- a/src/bacnet/basic/object/client/device-client.c +++ b/src/bacnet/basic/object/client/device-client.c @@ -118,7 +118,7 @@ static object_functions_t Object_Table[] = { NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, #if (BACNET_PROTOCOL_REVISION >= 17) { OBJECT_NETWORK_PORT, Network_Port_Init, Network_Port_Count, Network_Port_Index_To_Instance, Network_Port_Valid_Instance, @@ -127,7 +127,7 @@ static object_functions_t Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, #endif { MAX_BACNET_OBJECT_TYPE, NULL /* Init */, NULL /* Count */, NULL /* Index_To_Instance */, NULL /* Valid_Instance */, @@ -137,7 +137,7 @@ static object_functions_t Object_Table[] = { NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, }; /** Glue function to let the Device object, when called by a handler, @@ -1042,6 +1042,34 @@ int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) return apdu_len; } +/** + * @brief Updates all the object timers with elapsed milliseconds + * @param milliseconds - number of milliseconds elapsed + */ +void Device_Timer( + uint16_t milliseconds) +{ + struct object_functions *pObject; + unsigned count = 0; + uint32_t instance; + + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count) { + count = pObject->Object_Count(); + } + while (count) { + count--; + if ((pObject->Object_Timer) && + (pObject->Object_Index_To_Instance)) { + instance = pObject->Object_Index_To_Instance(count); + pObject->Object_Timer(instance, milliseconds); + } + } + pObject++; + } +} + /** Initialize the Device Object. Initialize the group of object helper functions for any supported Object. Initialize each of the Device Object child Object instances. diff --git a/src/bacnet/basic/object/color_object.c b/src/bacnet/basic/object/color_object.c index 2f275217..e576c1fe 100644 --- a/src/bacnet/basic/object/color_object.c +++ b/src/bacnet/basic/object/color_object.c @@ -39,18 +39,29 @@ #include "bacnet/basic/object/device.h" #include "bacnet/basic/services.h" #include "bacnet/basic/sys/keylist.h" +#include "bacnet/basic/sys/linear.h" /* me! */ -#include "color_object.h" +#include "bacnet/basic/object/color_object.h" struct object_data { bool Changed : 1; bool Write_Enabled : 1; + /* indicate the target color value for the color output */ BACNET_XY_COLOR Present_Value; + /* indicates the components of the object's actual color output */ BACNET_XY_COLOR Tracking_Value; + /* used to request specific behaviors */ BACNET_COLOR_COMMAND Color_Command; + /* indicates that there may be processes in the color object that may + cause the Tracking_Value and Present_Value to differ temporarily. */ BACNET_COLOR_OPERATION_IN_PROGRESS In_Progress; + /* the color to be used for the color output when the device is restarted + until such time as Present_Value or Color_Command are written */ BACNET_XY_COLOR Default_Color; + /* indicates the amount of time in milliseconds over which changes + to the color output reflected in the Tracking_Value property */ uint32_t Default_Fade_Time; + /* The transition may be NONE or FADE. */ BACNET_COLOR_TRANSITION Transition; const char *Object_Name; const char *Description; @@ -198,10 +209,10 @@ bool Color_Present_Value_Set(uint32_t object_instance, BACNET_XY_COLOR *value) } /** - * For a given object instance-number, sets the present-value + * For a given object instance-number, writes to the present-value * * @param object_instance - object-instance number of the object - * @param value - floating point Color + * @param value - property value to be written * @param priority - priority-array index value 1..16 * @param error_class - the BACnet error class * @param error_code - BACnet Error code @@ -216,23 +227,21 @@ static bool Color_Present_Value_Write(uint32_t object_instance, { bool status = false; struct object_data *pObject; - BACNET_XY_COLOR old_value = { 0.0, 0.0 }; pObject = Keylist_Data(Object_List, object_instance); if (pObject) { (void)priority; - if (pObject->Write_Enabled) { - xy_color_copy(&old_value, &pObject->Present_Value); - xy_color_copy(&pObject->Present_Value, value); - if (Color_Write_Present_Value_Callback) { - Color_Write_Present_Value_Callback( - object_instance, &old_value, value); - } - status = true; + xy_color_copy(&pObject->Present_Value, value); + /* configure the color-command to perform the transition */ + if (pObject->Transition == BACNET_COLOR_TRANSITION_FADE) { + pObject->Color_Command.transit.fade_time = + pObject->Default_Fade_Time; } else { - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + pObject->Color_Command.transit.fade_time = 0; } + pObject->Color_Command.operation = BACNET_COLOR_OPERATION_FADE_TO_COLOR; + xy_color_copy(&pObject->Color_Command.target.color, value); + status = true; } else { *error_class = ERROR_CLASS_OBJECT; *error_code = ERROR_CODE_UNKNOWN_OBJECT; @@ -326,6 +335,44 @@ bool Color_Command_Set(uint32_t object_instance, BACNET_COLOR_COMMAND *value) return status; } +/** + * For a given object instance-number, writes to the present-value + * + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Color_Command_Write(uint32_t object_instance, + BACNET_COLOR_COMMAND *value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if (pObject->Write_Enabled) { + color_command_copy(&pObject->Color_Command, value); + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + /** * For a given object instance-number, gets the property value * @@ -413,11 +460,50 @@ bool Color_Default_Color_Set(uint32_t object_instance, BACNET_XY_COLOR *value) return status; } +/** + * Handle a WriteProperty to a specific property. + * + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Color_Default_Color_Write(uint32_t object_instance, + BACNET_XY_COLOR *value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if (pObject->Write_Enabled) { + xy_color_copy(&pObject->Default_Color, value); + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + /** * For a given object instance-number, gets the property value * * @param object_instance - object-instance number of the object - * @return property value + * @return the amount of time in milliseconds over which changes + * to the Color reflected in the Tracking_Value property */ uint32_t Color_Default_Fade_Time(uint32_t object_instance) { @@ -436,7 +522,8 @@ uint32_t Color_Default_Fade_Time(uint32_t object_instance) * For a given object instance-number, sets the property value * * @param object_instance - object-instance number of the object - * @param value - BACNET_COLOR_OPERATION_IN_PROGRESS + * @param value - the amount of time in milliseconds over which changes + * to the Color reflected in the Tracking_Value property * @return true if values are within range and value is set. */ bool Color_Default_Fade_Time_Set(uint32_t object_instance, uint32_t value) @@ -457,6 +544,51 @@ bool Color_Default_Fade_Time_Set(uint32_t object_instance, uint32_t value) return status; } +/** + * Handle a WriteProperty to a specific property. + * + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Color_Default_Fade_Time_Write(uint32_t object_instance, + BACNET_UNSIGNED_INTEGER value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if (pObject->Write_Enabled) { + if ((value == 0) || + ((value >= BACNET_COLOR_FADE_TIME_MIN) && + (value <= BACNET_COLOR_FADE_TIME_MAX))) { + pObject->Default_Fade_Time = value; + 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; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + /** * For a given object instance-number, gets the property value * @@ -500,6 +632,49 @@ bool Color_Transition_Set( return status; } +/** + * Handle a WriteProperty to a specific property. + * + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Color_Transition_Write(uint32_t object_instance, + uint32_t value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if (pObject->Write_Enabled) { + if (value < BACNET_COLOR_TRANSITION_MAX) { + pObject->Transition = value; + 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; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + /** * For a given object instance-number, loads the object-name into * a characterstring. Note that the object name must be unique @@ -618,6 +793,95 @@ bool Color_Description_Set(uint32_t object_instance, char *new_name) return status; } +/** + * Updates the color object tracking value while fading + * + * Transitioning from one color to another is supported by writing a + * FADE_TO_COLOR command to the property Color_Command. + * The current color is always indicated in the + * Tracking_Value property. If a color command is + * currently in progress and the Present_Value is written, + * the color command shall be halted. + * + * @param object_instance - object-instance number of the object + * @param milliseconds - number of milliseconds elapsed + */ +static void Color_Fade_To_Color_Handler( + uint32_t object_instance, uint16_t milliseconds) +{ + BACNET_XY_COLOR old_value; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return; + } + xy_color_copy(&old_value, &pObject->Tracking_Value); + if (milliseconds >= pObject->Color_Command.transit.fade_time) { + /* stop fading */ + xy_color_copy( + &pObject->Tracking_Value, &pObject->Color_Command.target.color); + pObject->In_Progress = BACNET_COLOR_OPERATION_IN_PROGRESS_IDLE; + pObject->Color_Command.operation = BACNET_COLOR_OPERATION_STOP; + pObject->Color_Command.transit.fade_time = 0; + } else { + if (xy_color_same(&old_value, &pObject->Color_Command.target.color)) { + /* stop fading */ + xy_color_copy( + &pObject->Tracking_Value, &pObject->Color_Command.target.color); + pObject->In_Progress = BACNET_COLOR_OPERATION_IN_PROGRESS_IDLE; + pObject->Color_Command.operation = BACNET_COLOR_OPERATION_STOP; + pObject->Color_Command.transit.fade_time = 0; + } else { + /* fading */ + pObject->Tracking_Value.x_coordinate = linear_interpolate(0, + milliseconds, pObject->Color_Command.transit.fade_time, + old_value.x_coordinate, + pObject->Color_Command.target.color.x_coordinate); + pObject->Tracking_Value.y_coordinate = linear_interpolate(0, + milliseconds, pObject->Color_Command.transit.fade_time, + old_value.y_coordinate, + pObject->Color_Command.target.color.y_coordinate); + pObject->Color_Command.transit.fade_time -= milliseconds; + pObject->In_Progress = + BACNET_COLOR_OPERATION_IN_PROGRESS_FADE_ACTIVE; + } + } + if (Color_Write_Present_Value_Callback) { + Color_Write_Present_Value_Callback( + object_instance, &old_value, &pObject->Tracking_Value); + } +} + +/** + * Updates the color object tracking value per ramp or fade + * + * @param object_instance - object-instance number of the object + * @param milliseconds - number of milliseconds elapsed + */ +void Color_Timer(uint32_t object_instance, uint16_t milliseconds) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + switch (pObject->Color_Command.operation) { + case BACNET_COLOR_OPERATION_NONE: + pObject->In_Progress = BACNET_COLOR_OPERATION_IN_PROGRESS_IDLE; + break; + case BACNET_COLOR_OPERATION_FADE_TO_COLOR: + Color_Fade_To_Color_Handler( + object_instance, milliseconds); + break; + case BACNET_COLOR_OPERATION_STOP: + pObject->In_Progress = BACNET_COLOR_OPERATION_IN_PROGRESS_IDLE; + break; + default: + break; + } + } +} + /** * ReadProperty handler for this object. For the given ReadProperty * data, the application_data is loaded or the error flags are set. @@ -725,10 +989,14 @@ bool Color_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) bool status = false; /* return value */ int len = 0; BACNET_APPLICATION_DATA_VALUE value; + int apdu_size = 0; + uint8_t *apdu = NULL; /* decode the some of the request */ - len = bacapp_decode_application_data( - wp_data->application_data, wp_data->application_data_len, &value); + apdu = wp_data->application_data; + apdu_size = wp_data->application_data_len; + len = bacapp_decode_known_property(apdu, apdu_size, &value, + wp_data->object_type, wp_data->object_property); /* FIXME: len < application_data_len: more data? */ if (len < 0) { /* error while decoding - a value larger than we can handle */ @@ -747,17 +1015,55 @@ bool Color_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) switch (wp_data->object_property) { case PROP_PRESENT_VALUE: status = write_property_type_valid( - wp_data, &value, BACNET_APPLICATION_TAG_REAL); + wp_data, &value, BACNET_APPLICATION_TAG_XY_COLOR); if (status) { status = Color_Present_Value_Write(wp_data->object_instance, &value.type.XY_Color, wp_data->priority, &wp_data->error_class, &wp_data->error_code); } break; + case PROP_COLOR_COMMAND: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_COLOR_COMMAND); + if (status) { + status = Color_Command_Write(wp_data->object_instance, + &value.type.Color_Command, wp_data->priority, + &wp_data->error_class, &wp_data->error_code); + } + break; + case PROP_DEFAULT_COLOR: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_XY_COLOR); + if (status) { + status = Color_Default_Color_Write(wp_data->object_instance, + &value.type.XY_Color, wp_data->priority, + &wp_data->error_class, &wp_data->error_code); + } + break; + case PROP_DEFAULT_FADE_TIME: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + status = Color_Default_Fade_Time_Write(wp_data->object_instance, + value.type.Unsigned_Int, wp_data->priority, + &wp_data->error_class, &wp_data->error_code); + } + break; + case PROP_TRANSITION: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + status = Color_Transition_Write(wp_data->object_instance, + value.type.Enumerated, wp_data->priority, + &wp_data->error_class, &wp_data->error_code); + } + break; case PROP_OBJECT_IDENTIFIER: case PROP_OBJECT_TYPE: case PROP_OBJECT_NAME: case PROP_DESCRIPTION: + case PROP_TRACKING_VALUE: + case PROP_IN_PROGRESS: wp_data->error_class = ERROR_CLASS_PROPERTY; wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; break; @@ -840,9 +1146,9 @@ uint32_t Color_Create(uint32_t object_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 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); } @@ -851,23 +1157,26 @@ uint32_t Color_Create(uint32_t object_instance) pObject = calloc(1, sizeof(struct object_data)); if (pObject) { pObject->Object_Name = NULL; - pObject->Present_Value.x_coordinate = 0.0; - pObject->Present_Value.y_coordinate = 0.0; - pObject->Tracking_Value.x_coordinate = 0.0; - pObject->Tracking_Value.y_coordinate = 0.0; - pObject->Color_Command.operation = BACNET_COLOR_OPERATION_NONE; + /* color defaults */ + xy_color_set(&pObject->Present_Value, 0.0, 0.0); + xy_color_set(&pObject->Tracking_Value, 0.0, 0.0); + xy_color_set(&pObject->Default_Color, 1.0, 1.0); + pObject->Default_Fade_Time = BACNET_COLOR_FADE_TIME_MIN; + /* at powerup - fade to default color */ + xy_color_copy( + &pObject->Color_Command.target.color, &pObject->Default_Color); + pObject->Color_Command.operation = + BACNET_COLOR_OPERATION_FADE_TO_COLOR; + pObject->Color_Command.transit.fade_time = + pObject->Default_Fade_Time; + /* initialize all the status */ pObject->In_Progress = BACNET_COLOR_OPERATION_IN_PROGRESS_IDLE; - pObject->Default_Color.x_coordinate = 1.0; - pObject->Default_Color.y_coordinate = 1.0; - pObject->Default_Fade_Time = 0; - pObject->Transition = BACNET_COLOR_TRANSITION_NONE; + pObject->Transition = BACNET_COLOR_TRANSITION_FADE; pObject->Changed = false; pObject->Write_Enabled = false; /* add to list */ index = Keylist_Data_Add(Object_List, object_instance, pObject); - if (index >= 0) { - Device_Inc_Database_Revision(); - } else { + if (index < 0) { free(pObject); return BACNET_MAX_INSTANCE; } @@ -893,7 +1202,6 @@ bool Color_Delete(uint32_t object_instance) if (pObject) { free(pObject); status = true; - Device_Inc_Database_Revision(); } return status; @@ -911,7 +1219,6 @@ void Color_Cleanup(void) pObject = Keylist_Data_Pop(Object_List); if (pObject) { free(pObject); - Device_Inc_Database_Revision(); } } while (pObject); Keylist_Delete(Object_List); @@ -925,7 +1232,4 @@ void Color_Cleanup(void) void Color_Init(void) { Object_List = Keylist_Create(); - if (Object_List) { - atexit(Color_Cleanup); - } } diff --git a/src/bacnet/basic/object/color_object.h b/src/bacnet/basic/object/color_object.h index 6c035a5d..47169865 100644 --- a/src/bacnet/basic/object/color_object.h +++ b/src/bacnet/basic/object/color_object.h @@ -29,7 +29,7 @@ #include "bacnet/wp.h" /** - * @brief Callback for gateway write present value request + * @brief Callback for tracking value * @param object_instance - object-instance number of the object * @param old_value - BACnetXYColor value prior to write * @param value - BACnetXYColor value of the write @@ -118,6 +118,9 @@ void Color_Write_Enable(uint32_t instance); BACNET_STACK_EXPORT void Color_Write_Disable(uint32_t instance); +BACNET_STACK_EXPORT +void Color_Timer(uint32_t object_instance, uint16_t milliseconds); + BACNET_STACK_EXPORT uint32_t Color_Create(uint32_t object_instance); BACNET_STACK_EXPORT diff --git a/src/bacnet/basic/object/color_temperature.c b/src/bacnet/basic/object/color_temperature.c index 6530e1d3..8e7b7fa3 100644 --- a/src/bacnet/basic/object/color_temperature.c +++ b/src/bacnet/basic/object/color_temperature.c @@ -39,6 +39,7 @@ #include "bacnet/basic/object/device.h" #include "bacnet/basic/services.h" #include "bacnet/basic/sys/keylist.h" +#include "bacnet/basic/sys/linear.h" /* me! */ #include "color_temperature.h" @@ -207,7 +208,7 @@ bool Color_Temperature_Present_Value_Set( * For a given object instance-number, sets the present-value * * @param object_instance - object-instance number of the object - * @param value - floating point Color + * @param value - property value to write * @param priority - priority-array index value 1..16 * @param error_class - the BACnet error class * @param error_code - BACnet Error code @@ -222,22 +223,34 @@ static bool Color_Temperature_Present_Value_Write(uint32_t object_instance, { bool status = false; struct object_data *pObject; - uint32_t old_value = 0; pObject = Keylist_Data(Object_List, object_instance); if (pObject) { (void)priority; - if (pObject->Write_Enabled) { - old_value = pObject->Present_Value; + if ((value >= BACNET_COLOR_TEMPERATURE_MIN) && + (value <= BACNET_COLOR_TEMPERATURE_MAX)) { pObject->Present_Value = value; - if (Color_Temperature_Write_Present_Value_Callback) { - Color_Temperature_Write_Present_Value_Callback( - object_instance, old_value, value); + /* configure the color-command to perform the transition */ + if (pObject->Transition == BACNET_COLOR_TRANSITION_FADE) { + pObject->Color_Command.transit.fade_time = + pObject->Default_Fade_Time; + pObject->Color_Command.operation = + BACNET_COLOR_OPERATION_FADE_TO_CCT; + } else if (pObject->Transition == BACNET_COLOR_TRANSITION_RAMP) { + pObject->Color_Command.transit.ramp_rate = + pObject->Default_Ramp_Rate; + pObject->Color_Command.operation = + BACNET_COLOR_OPERATION_RAMP_TO_CCT; + } else { + pObject->Color_Command.transit.fade_time = 0; + pObject->Color_Command.operation = + BACNET_COLOR_OPERATION_FADE_TO_CCT; } + pObject->Color_Command.target.color_temperature = value; status = true; } else { *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; } } else { *error_class = ERROR_CLASS_OBJECT; @@ -506,6 +519,50 @@ bool Color_Temperature_Default_Color_Temperature_Set( return status; } +/** + * Handle a WriteProperty to a specific property. + * + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Color_Temperature_Default_Write(uint32_t object_instance, + BACNET_UNSIGNED_INTEGER value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if (pObject->Write_Enabled) { + if ((value >= BACNET_COLOR_TEMPERATURE_MIN) && + (value <= BACNET_COLOR_TEMPERATURE_MAX)) { + pObject->Default_Color_Temperature = value; + 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; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + /** * For a given object instance-number, gets the property value * @@ -551,6 +608,51 @@ bool Color_Temperature_Default_Fade_Time_Set( return status; } +/** + * Handle a WriteProperty to a specific property. + * + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Color_Temperature_Default_Fade_Time_Write(uint32_t object_instance, + BACNET_UNSIGNED_INTEGER value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if (pObject->Write_Enabled) { + if ((value == 0) || + ((value >= BACNET_COLOR_FADE_TIME_MIN) && + (value <= BACNET_COLOR_FADE_TIME_MAX))) { + pObject->Default_Fade_Time = value; + 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; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + /** * For a given object instance-number, gets the property value * @@ -592,6 +694,51 @@ bool Color_Temperature_Default_Ramp_Rate_Set( return status; } +/** + * Handle a WriteProperty to a specific property. + * + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Color_Temperature_Default_Ramp_Rate_Write(uint32_t object_instance, + BACNET_UNSIGNED_INTEGER value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if (pObject->Write_Enabled) { + if ((value == 0) || + ((value >= BACNET_COLOR_RAMP_RATE_MIN) && + (value <= BACNET_COLOR_RAMP_RATE_MAX))) { + pObject->Default_Fade_Time = value; + 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; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + /** * For a given object instance-number, gets the property value * @@ -633,6 +780,52 @@ bool Color_Temperature_Default_Step_Increment_Set( return status; } +/** + * Handle a WriteProperty to a specific property. + * + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Color_Temperature_Default_Step_Increment_Write( + uint32_t object_instance, + BACNET_UNSIGNED_INTEGER value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if (pObject->Write_Enabled) { + if ((value == 0) || + ((value >= BACNET_COLOR_STEP_INCREMENT_MIN) && + (value <= BACNET_COLOR_STEP_INCREMENT_MAX))) { + pObject->Default_Fade_Time = value; + 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; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + /** * For a given object instance-number, gets the property value * @@ -676,6 +869,49 @@ bool Color_Temperature_Transition_Set( return status; } +/** + * Handle a WriteProperty to a specific property. + * + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Color_Transition_Write(uint32_t object_instance, + uint32_t value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if (pObject->Write_Enabled) { + if (value < BACNET_COLOR_TRANSITION_MAX) { + pObject->Transition = value; + 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; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + /** * For a given object instance-number, loads the object-name into * a characterstring. Note that the object name must be unique @@ -795,6 +1031,265 @@ bool Color_Temperature_Description_Set(uint32_t object_instance, char *new_name) return status; } +/** + * Updates the color object tracking value while fading + * + * The fade operation changes the output color temperature + * from its current value to target-color-temperature, over + * a period of time defined by fade-time. While the fade + * operation is executing, In_Progress shall be set to FADE_ACTIVE, + * and Tracking_Value shall be updated to reflect the current + * progress of the fade. shall be clamped + * to Min_Pres_Value and Max_Pres_Value + * + * @param object_instance - object-instance number of the object + * @param milliseconds - number of milliseconds elapsed + */ +static void Color_Temperature_Fade_To_CCT_Handler( + uint32_t object_instance, uint16_t milliseconds) +{ + uint32_t old_value, target_value, min_value, max_value; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return; + } + old_value = pObject->Tracking_Value; + min_value = pObject->Present_Value_Minimum; + max_value = pObject->Present_Value_Maximum; + target_value = pObject->Color_Command.target.color_temperature; + /* clamp target within min/max, if needed */ + if (target_value > max_value) { + target_value = max_value; + } + if (target_value < min_value) { + target_value = min_value; + } + if (milliseconds >= pObject->Color_Command.transit.fade_time) { + /* done fading */ + pObject->Tracking_Value = target_value; + pObject->In_Progress = BACNET_COLOR_OPERATION_IN_PROGRESS_IDLE; + pObject->Color_Command.operation = BACNET_COLOR_OPERATION_STOP; + pObject->Color_Command.transit.fade_time = 0; + } else { + if (old_value == target_value) { + /* stop fading */ + pObject->Tracking_Value = target_value; + pObject->In_Progress = BACNET_COLOR_OPERATION_IN_PROGRESS_IDLE; + pObject->Color_Command.operation = BACNET_COLOR_OPERATION_STOP; + pObject->Color_Command.transit.fade_time = 0; + } else { + /* fading */ + pObject->Tracking_Value = linear_interpolate_int(0, milliseconds, + pObject->Color_Command.transit.fade_time, old_value, + target_value); + pObject->Color_Command.transit.fade_time -= milliseconds; + pObject->In_Progress = + BACNET_COLOR_OPERATION_IN_PROGRESS_FADE_ACTIVE; + } + } + if (Color_Temperature_Write_Present_Value_Callback) { + Color_Temperature_Write_Present_Value_Callback( + object_instance, old_value, pObject->Tracking_Value); + } +} + +/** + * Updates the color object tracking value while ramping + * + * Commands Present_Value to ramp from the current Tracking_Value to the + * target-color-temperature specified in the command. The ramp operation + * changes the output color temperature from its current value to + * target-color-temperature, at a particular Kelvin per second defined by + * ramp-rate. While the ramp operation is executing, In_Progress shall be set + * to RAMP_ACTIVE, and Tracking_Value shall be updated to reflect the current + * progress of the fade. shall be clamped to + * Min_Pres_Value and Max_Pres_Value + * + * @param object_instance - object-instance number of the object + * @param milliseconds - number of milliseconds elapsed + */ +static void Color_Temperature_Ramp_To_CCT_Handler( + uint32_t object_instance, uint16_t milliseconds) +{ + uint16_t old_value, target_value, min_value, max_value, step_value, steps; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return; + } + old_value = pObject->Tracking_Value; + min_value = pObject->Present_Value_Minimum; + max_value = pObject->Present_Value_Maximum; + target_value = pObject->Color_Command.target.color_temperature; + /* clamp target within min/max, if needed */ + if (target_value > max_value) { + target_value = max_value; + } + if (target_value < min_value) { + target_value = min_value; + } + /* determine the number of K steps */ + if (milliseconds <= 1000) { + /* K per second */ + steps = linear_interpolate_int( + 0, milliseconds, 1000, 0, pObject->Color_Command.transit.ramp_rate); + } else { + steps = + (milliseconds * pObject->Color_Command.transit.ramp_rate) / 1000; + } + if (old_value == target_value) { + pObject->Tracking_Value = target_value; + pObject->In_Progress = BACNET_COLOR_OPERATION_IN_PROGRESS_IDLE; + pObject->Color_Command.operation = BACNET_COLOR_OPERATION_STOP; + } else { + if (old_value < target_value) { + step_value = old_value + steps; + } else if (old_value > target_value) { + if (steps > old_value) { + step_value = old_value - steps; + } else { + step_value = target_value; + } + } else { + step_value = target_value; + } + /* clamp target within min/max, if needed */ + if (step_value > max_value) { + step_value = max_value; + } + if (step_value < min_value) { + step_value = min_value; + } + pObject->Tracking_Value = step_value; + pObject->In_Progress = BACNET_COLOR_OPERATION_IN_PROGRESS_FADE_ACTIVE; + } + if (Color_Temperature_Write_Present_Value_Callback) { + Color_Temperature_Write_Present_Value_Callback( + object_instance, old_value, pObject->Tracking_Value); + } +} + +/** + * Updates the color object tracking value while stepping + * + * Commands Present_Value to a value equal to the Tracking_Value + * plus the step-increment. The resulting sum shall be clamped to + * Min_Pres_Value and Max_Pres_Value + * + * @param object_instance - object-instance number of the object + */ +static void Color_Temperature_Step_Up_CCT_Handler(uint32_t object_instance) +{ + uint16_t old_value, target_value, min_value, max_value, step_value; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return; + } + old_value = target_value = pObject->Tracking_Value; + min_value = pObject->Present_Value_Minimum; + max_value = pObject->Present_Value_Maximum; + step_value = pObject->Color_Command.transit.step_increment; + target_value += step_value; + /* clamp target within min/max, if needed */ + if (target_value > max_value) { + target_value = max_value; + } + if (target_value < min_value) { + target_value = min_value; + } + pObject->Present_Value = target_value; + pObject->Tracking_Value = target_value; + pObject->In_Progress = BACNET_COLOR_OPERATION_IN_PROGRESS_IDLE; + if (Color_Temperature_Write_Present_Value_Callback) { + Color_Temperature_Write_Present_Value_Callback( + object_instance, old_value, pObject->Tracking_Value); + } +} + +/** + * Updates the color object tracking value while stepping + * + * Commands Present_Value to a value equal to the Tracking_Value + * plus the step-increment. The resulting sum shall be clamped to + * Min_Pres_Value and Max_Pres_Value + * + * @param object_instance - object-instance number of the object + */ +static void Color_Temperature_Step_Down_CCT_Handler(uint32_t object_instance) +{ + uint16_t old_value, target_value, min_value, max_value, step_value; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return; + } + old_value = target_value = pObject->Tracking_Value; + min_value = pObject->Present_Value_Minimum; + max_value = pObject->Present_Value_Maximum; + step_value = pObject->Color_Command.transit.step_increment; + if (target_value >= step_value) { + target_value -= step_value; + } else { + target_value = 0; + } + /* clamp target within min/max, if needed */ + if (target_value > max_value) { + target_value = max_value; + } + if (target_value < min_value) { + target_value = min_value; + } + pObject->Present_Value = target_value; + pObject->Tracking_Value = target_value; + pObject->In_Progress = BACNET_COLOR_OPERATION_IN_PROGRESS_IDLE; + if (Color_Temperature_Write_Present_Value_Callback) { + Color_Temperature_Write_Present_Value_Callback( + object_instance, old_value, pObject->Tracking_Value); + } +} + +/** + * Updates the color temperature tracking value per ramp or fade + * + * @param object_instance - object-instance number of the object + * @param milliseconds - number of milliseconds elapsed + */ +void Color_Temperature_Timer(uint32_t object_instance, uint16_t milliseconds) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + switch (pObject->Color_Command.operation) { + case BACNET_COLOR_OPERATION_FADE_TO_CCT: + Color_Temperature_Fade_To_CCT_Handler( + object_instance, milliseconds); + break; + case BACNET_COLOR_OPERATION_RAMP_TO_CCT: + Color_Temperature_Ramp_To_CCT_Handler( + object_instance, milliseconds); + break; + case BACNET_COLOR_OPERATION_STEP_UP_CCT: + Color_Temperature_Step_Up_CCT_Handler(object_instance); + break; + case BACNET_COLOR_OPERATION_STEP_DOWN_CCT: + Color_Temperature_Step_Down_CCT_Handler(object_instance); + break; + case BACNET_COLOR_OPERATION_NONE: + case BACNET_COLOR_OPERATION_STOP: + default: + pObject->In_Progress = BACNET_COLOR_OPERATION_IN_PROGRESS_IDLE; + break; + } + } +} + /** * ReadProperty handler for this object. For the given ReadProperty * data, the application_data is loaded or the error flags are set. @@ -918,11 +1413,14 @@ bool Color_Temperature_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) bool status = false; /* return value */ int len = 0; BACNET_APPLICATION_DATA_VALUE value; + int apdu_size = 0; + uint8_t *apdu = NULL; /* 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? */ + apdu = wp_data->application_data; + apdu_size = wp_data->application_data_len; + len = bacapp_decode_known_property(apdu, apdu_size, &value, + wp_data->object_type, wp_data->object_property); if (len < 0) { /* error while decoding - a value larger than we can handle */ wp_data->error_class = ERROR_CLASS_PROPERTY; @@ -948,10 +1446,64 @@ bool Color_Temperature_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) &wp_data->error_code); } break; + case PROP_DEFAULT_COLOR_TEMPERATURE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + status = + Color_Temperature_Default_Write(wp_data->object_instance, + value.type.Unsigned_Int, wp_data->priority, + &wp_data->error_class, &wp_data->error_code); + } + break; + case PROP_DEFAULT_FADE_TIME: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + status = Color_Temperature_Default_Fade_Time_Write( + wp_data->object_instance, value.type.Unsigned_Int, + wp_data->priority, &wp_data->error_class, + &wp_data->error_code); + } + break; + case PROP_TRANSITION: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + status = Color_Transition_Write(wp_data->object_instance, + value.type.Enumerated, wp_data->priority, + &wp_data->error_class, &wp_data->error_code); + } + break; + case PROP_DEFAULT_RAMP_RATE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + status = Color_Temperature_Default_Ramp_Rate_Write( + wp_data->object_instance, value.type.Unsigned_Int, + wp_data->priority, &wp_data->error_class, + &wp_data->error_code); + } + break; + case PROP_DEFAULT_STEP_INCREMENT: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + status = Color_Temperature_Default_Step_Increment_Write( + wp_data->object_instance, value.type.Unsigned_Int, + wp_data->priority, &wp_data->error_class, + &wp_data->error_code); + } + break; case PROP_OBJECT_IDENTIFIER: case PROP_OBJECT_TYPE: case PROP_OBJECT_NAME: case PROP_DESCRIPTION: + case PROP_TRACKING_VALUE: + case PROP_COLOR_COMMAND: + case PROP_IN_PROGRESS: + case PROP_MAX_PRES_VALUE: + case PROP_MIN_PRES_VALUE: wp_data->error_class = ERROR_CLASS_PROPERTY; wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; break; @@ -1034,9 +1586,9 @@ uint32_t Color_Temperature_Create(uint32_t object_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 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); } @@ -1047,22 +1599,26 @@ uint32_t Color_Temperature_Create(uint32_t object_instance) pObject->Object_Name = NULL; pObject->Present_Value = 0; pObject->Tracking_Value = 0; - pObject->Color_Command.operation = BACNET_COLOR_OPERATION_NONE; pObject->In_Progress = BACNET_COLOR_OPERATION_IN_PROGRESS_IDLE; pObject->Default_Color_Temperature = 5000; - pObject->Default_Fade_Time = 0; - pObject->Default_Ramp_Rate = 0; - pObject->Default_Step_Increment = 0; - pObject->Transition = BACNET_COLOR_TRANSITION_NONE; - pObject->Present_Value_Minimum = 0; - pObject->Present_Value_Maximum = 0; + pObject->Default_Fade_Time = BACNET_COLOR_FADE_TIME_MIN; + pObject->Default_Ramp_Rate = BACNET_COLOR_RAMP_RATE_MIN; + pObject->Default_Step_Increment = BACNET_COLOR_STEP_INCREMENT_MIN; + pObject->Transition = BACNET_COLOR_TRANSITION_FADE; + pObject->Present_Value_Minimum = BACNET_COLOR_TEMPERATURE_MIN; + pObject->Present_Value_Maximum = BACNET_COLOR_TEMPERATURE_MAX; + /* configure to transition from power up values */ + pObject->Color_Command.operation = + BACNET_COLOR_OPERATION_FADE_TO_CCT; + pObject->Color_Command.transit.fade_time = + pObject->Default_Fade_Time; + pObject->Color_Command.target.color_temperature = + pObject->Default_Color_Temperature; pObject->Changed = false; pObject->Write_Enabled = false; /* add to list */ index = Keylist_Data_Add(Object_List, object_instance, pObject); - if (index >= 0) { - Device_Inc_Database_Revision(); - } else { + if (index < 0) { free(pObject); return BACNET_MAX_INSTANCE; } @@ -1088,7 +1644,6 @@ bool Color_Temperature_Delete(uint32_t object_instance) if (pObject) { free(pObject); status = true; - Device_Inc_Database_Revision(); } return status; @@ -1106,7 +1661,6 @@ void Color_Temperature_Cleanup(void) pObject = Keylist_Data_Pop(Object_List); if (pObject) { free(pObject); - Device_Inc_Database_Revision(); } } while (pObject); Keylist_Delete(Object_List); @@ -1120,7 +1674,4 @@ void Color_Temperature_Cleanup(void) void Color_Temperature_Init(void) { Object_List = Keylist_Create(); - if (Object_List) { - atexit(Color_Temperature_Cleanup); - } } diff --git a/src/bacnet/basic/object/color_temperature.h b/src/bacnet/basic/object/color_temperature.h index accc519e..0ceb6ad6 100644 --- a/src/bacnet/basic/object/color_temperature.h +++ b/src/bacnet/basic/object/color_temperature.h @@ -29,7 +29,7 @@ #include "bacnet/wp.h" /** - * @brief Callback for gateway write present value request + * @brief Callback for write present value request * @param object_instance - object-instance number of the object * @param old_value - 32-bit value prior to write * @param value - 32-bit value of the write @@ -150,6 +150,9 @@ void Color_Temperature_Write_Enable(uint32_t instance); BACNET_STACK_EXPORT void Color_Temperature_Write_Disable(uint32_t instance); +BACNET_STACK_EXPORT +void Color_Temperature_Timer(uint32_t object_instance, uint16_t milliseconds); + BACNET_STACK_EXPORT uint32_t Color_Temperature_Create(uint32_t object_instance); BACNET_STACK_EXPORT diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index 1e4143ad..1e125069 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -99,7 +99,7 @@ static object_functions_t My_Object_Table[] = { NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, #if (BACNET_PROTOCOL_REVISION >= 17) { OBJECT_NETWORK_PORT, Network_Port_Init, Network_Port_Count, Network_Port_Index_To_Instance, Network_Port_Valid_Instance, @@ -108,7 +108,7 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */ , NULL /* Timer */}, #endif { OBJECT_ANALOG_INPUT, Analog_Input_Init, Analog_Input_Count, Analog_Input_Index_To_Instance, Analog_Input_Valid_Instance, @@ -118,7 +118,7 @@ static object_functions_t My_Object_Table[] = { Analog_Input_Encode_Value_List, Analog_Input_Change_Of_Value, Analog_Input_Change_Of_Value_Clear, Analog_Input_Intrinsic_Reporting, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */ , NULL /* Timer */}, { OBJECT_ANALOG_OUTPUT, Analog_Output_Init, Analog_Output_Count, Analog_Output_Index_To_Instance, Analog_Output_Valid_Instance, Analog_Output_Object_Name, Analog_Output_Read_Property, @@ -126,7 +126,7 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - Analog_Output_Create, Analog_Output_Delete}, + Analog_Output_Create, Analog_Output_Delete, NULL /* Timer */}, { OBJECT_ANALOG_VALUE, Analog_Value_Init, Analog_Value_Count, Analog_Value_Index_To_Instance, Analog_Value_Valid_Instance, Analog_Value_Object_Name, Analog_Value_Read_Property, @@ -135,7 +135,7 @@ static object_functions_t My_Object_Table[] = { Analog_Value_Encode_Value_List, Analog_Value_Change_Of_Value, Analog_Value_Change_Of_Value_Clear, Analog_Value_Intrinsic_Reporting, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, { OBJECT_BINARY_INPUT, Binary_Input_Init, Binary_Input_Count, Binary_Input_Index_To_Instance, Binary_Input_Valid_Instance, Binary_Input_Object_Name, Binary_Input_Read_Property, @@ -144,7 +144,7 @@ static object_functions_t My_Object_Table[] = { Binary_Input_Encode_Value_List, Binary_Input_Change_Of_Value, Binary_Input_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, { 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, @@ -152,7 +152,7 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - Binary_Output_Create, Binary_Output_Delete}, + Binary_Output_Create, Binary_Output_Delete, NULL /* Timer */}, { OBJECT_BINARY_VALUE, Binary_Value_Init, Binary_Value_Count, Binary_Value_Index_To_Instance, Binary_Value_Valid_Instance, Binary_Value_Object_Name, Binary_Value_Read_Property, @@ -160,7 +160,7 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, { OBJECT_CHARACTERSTRING_VALUE, CharacterString_Value_Init, CharacterString_Value_Count, CharacterString_Value_Index_To_Instance, CharacterString_Value_Valid_Instance, CharacterString_Value_Object_Name, @@ -172,14 +172,14 @@ static object_functions_t My_Object_Table[] = { CharacterString_Value_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, { OBJECT_COMMAND, Command_Init, Command_Count, Command_Index_To_Instance, Command_Valid_Instance, Command_Object_Name, Command_Read_Property, Command_Write_Property, Command_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, { OBJECT_INTEGER_VALUE, Integer_Value_Init, Integer_Value_Count, Integer_Value_Index_To_Instance, Integer_Value_Valid_Instance, Integer_Value_Object_Name, Integer_Value_Read_Property, @@ -187,7 +187,7 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, #if defined(INTRINSIC_REPORTING) { OBJECT_NOTIFICATION_CLASS, Notification_Class_Init, Notification_Class_Count, Notification_Class_Index_To_Instance, @@ -198,7 +198,7 @@ static object_functions_t My_Object_Table[] = { NULL /* COV Clear */, NULL /* Intrinsic Reporting */, Notification_Class_Add_List_Element, Notification_Class_Remove_List_Element, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, #endif { OBJECT_LIFE_SAFETY_POINT, Life_Safety_Point_Init, Life_Safety_Point_Count, Life_Safety_Point_Index_To_Instance, Life_Safety_Point_Valid_Instance, @@ -207,7 +207,7 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */ , NULL /* Timer */}, { OBJECT_LOAD_CONTROL, Load_Control_Init, Load_Control_Count, Load_Control_Index_To_Instance, Load_Control_Valid_Instance, Load_Control_Object_Name, Load_Control_Read_Property, @@ -215,7 +215,7 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */ , NULL /* Timer */}, { OBJECT_MULTI_STATE_INPUT, Multistate_Input_Init, Multistate_Input_Count, Multistate_Input_Index_To_Instance, Multistate_Input_Valid_Instance, Multistate_Input_Object_Name, Multistate_Input_Read_Property, @@ -223,7 +223,7 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */ , NULL /* Timer */}, { OBJECT_MULTI_STATE_OUTPUT, Multistate_Output_Init, Multistate_Output_Count, Multistate_Output_Index_To_Instance, Multistate_Output_Valid_Instance, Multistate_Output_Object_Name, @@ -232,7 +232,7 @@ static object_functions_t My_Object_Table[] = { NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - Multistate_Output_Create, Multistate_Output_Delete}, + Multistate_Output_Create, Multistate_Output_Delete, NULL /* Timer */}, { OBJECT_MULTI_STATE_VALUE, Multistate_Value_Init, Multistate_Value_Count, Multistate_Value_Index_To_Instance, Multistate_Value_Valid_Instance, Multistate_Value_Object_Name, Multistate_Value_Read_Property, @@ -241,7 +241,7 @@ static object_functions_t My_Object_Table[] = { Multistate_Value_Encode_Value_List, Multistate_Value_Change_Of_Value, Multistate_Value_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */ , NULL /* Timer */}, { OBJECT_TRENDLOG, Trend_Log_Init, Trend_Log_Count, Trend_Log_Index_To_Instance, Trend_Log_Valid_Instance, Trend_Log_Object_Name, Trend_Log_Read_Property, @@ -249,7 +249,7 @@ static object_functions_t My_Object_Table[] = { NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */ , NULL /* Timer */}, #if (BACNET_PROTOCOL_REVISION >= 14) { OBJECT_LIGHTING_OUTPUT, Lighting_Output_Init, Lighting_Output_Count, Lighting_Output_Index_To_Instance, Lighting_Output_Valid_Instance, @@ -258,14 +258,14 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + Lighting_Output_Create, Lighting_Output_Delete, Lighting_Output_Timer}, { OBJECT_CHANNEL, Channel_Init, Channel_Count, Channel_Index_To_Instance, Channel_Valid_Instance, Channel_Object_Name, Channel_Read_Property, Channel_Write_Property, Channel_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + Channel_Create, Channel_Delete, NULL /* Timer */ }, #endif #if (BACNET_PROTOCOL_REVISION >= 24) { OBJECT_COLOR, Color_Init, Color_Count, Color_Index_To_Instance, @@ -274,7 +274,7 @@ static object_functions_t My_Object_Table[] = { NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - Color_Create, Color_Delete}, + Color_Create, Color_Delete, Color_Timer}, { OBJECT_COLOR_TEMPERATURE, Color_Temperature_Init, Color_Temperature_Count, Color_Temperature_Index_To_Instance, Color_Temperature_Valid_Instance, Color_Temperature_Object_Name, Color_Temperature_Read_Property, @@ -282,7 +282,8 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - Color_Temperature_Create, Color_Temperature_Delete}, + Color_Temperature_Create, Color_Temperature_Delete, + Color_Temperature_Timer}, #endif #if defined(BACFILE) { OBJECT_FILE, bacfile_init, bacfile_count, bacfile_index_to_instance, @@ -291,7 +292,7 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - bacfile_create, bacfile_delete}, + bacfile_create, bacfile_delete, NULL /* Timer */}, #endif { OBJECT_OCTETSTRING_VALUE, OctetString_Value_Init, OctetString_Value_Count, OctetString_Value_Index_To_Instance, OctetString_Value_Valid_Instance, @@ -300,7 +301,7 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, { OBJECT_POSITIVE_INTEGER_VALUE, PositiveInteger_Value_Init, PositiveInteger_Value_Count, PositiveInteger_Value_Index_To_Instance, PositiveInteger_Value_Valid_Instance, PositiveInteger_Value_Object_Name, @@ -310,7 +311,7 @@ static object_functions_t My_Object_Table[] = { NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, { OBJECT_SCHEDULE, Schedule_Init, Schedule_Count, Schedule_Index_To_Instance, Schedule_Valid_Instance, Schedule_Object_Name, Schedule_Read_Property, Schedule_Write_Property, @@ -318,7 +319,7 @@ static object_functions_t My_Object_Table[] = { NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, { OBJECT_ACCUMULATOR, Accumulator_Init, Accumulator_Count, Accumulator_Index_To_Instance, Accumulator_Valid_Instance, Accumulator_Object_Name, Accumulator_Read_Property, @@ -326,7 +327,7 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */ , NULL /* Timer */}, { MAX_BACNET_OBJECT_TYPE, NULL /* Init */, NULL /* Count */, NULL /* Index_To_Instance */, NULL /* Valid_Instance */, NULL /* Object_Name */, NULL /* Read_Property */, @@ -334,7 +335,7 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, - NULL /* Create */, NULL /* Delete */ }, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, }; /** Glue function to let the Device object, when called by a handler, @@ -545,7 +546,7 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) bool status = false; /* From 16.4.1.1.2 Password - This optional parameter shall be a CharacterString of up to + This optional parameter shall be a CharacterString of up to 20 characters. For those devices that require the password as a protection, the service request shall be denied if the parameter is absent or if the password is incorrect. For those devices that @@ -554,7 +555,7 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) rd_data->error_class = ERROR_CLASS_SERVICES; rd_data->error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; } else if (characterstring_ansi_same(&rd_data->password, Reinit_Password)) { - /* Note: you could use a mix of state and password to + /* Note: you could use a mix of state and password to accomplish multiple things before restarting */ switch (rd_data->state) { case BACNET_REINIT_COLDSTART: @@ -1976,6 +1977,7 @@ bool Device_Create_Object( } else { /* required by ACK */ data->object_instance = object_instance; + Device_Inc_Database_Revision(); status = true; } } @@ -2013,7 +2015,9 @@ bool Device_Delete_Object( pObject->Object_Valid_Instance(data->object_instance)) { /* The object being deleted must already exist */ status = pObject->Object_Delete(data->object_instance); - if (!status) { + if (status) { + Device_Inc_Database_Revision(); + } else { /* The object exists but cannot be deleted. */ data->error_class = ERROR_CLASS_OBJECT; data->error_code = ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED; @@ -2154,6 +2158,34 @@ bool DeviceGetRRInfo(BACNET_READ_RANGE_DATA *pRequest, /* Info on the request */ return status; } +/** + * @brief Updates all the object timers with elapsed milliseconds + * @param milliseconds - number of milliseconds elapsed + */ +void Device_Timer( + uint16_t milliseconds) +{ + struct object_functions *pObject; + unsigned count = 0; + uint32_t instance; + + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count) { + count = pObject->Object_Count(); + } + while (count) { + count--; + if ((pObject->Object_Timer) && + (pObject->Object_Index_To_Instance)) { + instance = pObject->Object_Index_To_Instance(count); + pObject->Object_Timer(instance, milliseconds); + } + } + pObject++; + } +} + #ifdef BAC_ROUTING /**************************************************************************** ************* BACnet Routing Functionality (Optional) ********************** diff --git a/src/bacnet/basic/object/device.h b/src/bacnet/basic/object/device.h index 477522f3..58ba15c7 100644 --- a/src/bacnet/basic/object/device.h +++ b/src/bacnet/basic/object/device.h @@ -142,6 +142,14 @@ typedef void ( *object_intrinsic_reporting_function) ( uint32_t object_instance); +/** + * @brief Updates the object with the elapsed milliseconds + * @param object_instance - object-instance number of the object + * @param milliseconds - number of milliseconds elapsed + */ +typedef void ( + *object_timer_function) ( + uint32_t object_instance, uint16_t milliseconds); /** Defines the group of object helper functions for any supported Object. * @ingroup ObjHelpers @@ -172,6 +180,7 @@ typedef struct object_functions { list_element_function Object_Remove_List_Element; create_object_function Object_Create; delete_object_function Object_Delete; + object_timer_function Object_Timer; } object_functions_t; /* String Lengths - excluding any nul terminator */ @@ -233,6 +242,10 @@ extern "C" { void Device_Init( object_functions_t * object_table); + BACNET_STACK_EXPORT + void Device_Timer( + uint16_t milliseconds); + BACNET_STACK_EXPORT bool Device_Reinitialize( BACNET_REINITIALIZE_DEVICE_DATA * rd_data); diff --git a/src/bacnet/basic/object/lo.c b/src/bacnet/basic/object/lo.c index 75183814..783338de 100644 --- a/src/bacnet/basic/object/lo.c +++ b/src/bacnet/basic/object/lo.c @@ -27,9 +27,12 @@ * */ +#include +#include +#include #include #include -#include +#include #include "bacnet/bacdef.h" #include "bacnet/bacdcode.h" #include "bacnet/bacenum.h" @@ -38,7 +41,10 @@ #include "bacnet/rp.h" #include "bacnet/wp.h" #include "bacnet/lighting.h" +#include "bacnet/basic/object/device.h" #include "bacnet/basic/services.h" +#include "bacnet/basic/sys/keylist.h" +#include "bacnet/basic/sys/linear.h" #include "bacnet/proplist.h" /* me! */ #include "bacnet/basic/object/lo.h" @@ -47,15 +53,12 @@ #define MAX_LIGHTING_OUTPUTS 8 #endif -struct lighting_output_object { +struct object_data { float Present_Value; float Tracking_Value; float Physical_Value; BACNET_LIGHTING_COMMAND Lighting_Command; BACNET_LIGHTING_IN_PROGRESS In_Progress; - bool Out_Of_Service : 1; - bool Blink_Warn_Enable : 1; - bool Egress_Active : 1; uint32_t Egress_Time; uint32_t Default_Fade_Time; float Default_Ramp_Rate; @@ -70,8 +73,21 @@ struct lighting_output_object { float Min_Actual_Value; float Max_Actual_Value; uint8_t Lighting_Command_Default_Priority; + BACNET_OBJECT_ID Color_Reference; + BACNET_OBJECT_ID Override_Color_Reference; + const char *Object_Name; + const char *Description; + /* bits */ + bool Out_Of_Service : 1; + bool Blink_Warn_Enable : 1; + bool Egress_Active : 1; + bool Color_Override : 1; }; -static struct lighting_output_object Lighting_Output[MAX_LIGHTING_OUTPUTS]; +/* Key List for storing the object data sorted by instance number */ +static OS_Keylist Object_List; +/* callback for present value writes */ +static lighting_output_write_present_value_callback + Lighting_Output_Write_Present_Value_Callback; /* These arrays are used by the ReadPropertyMultiple handler and property-list property (as of protocol-revision 14) */ @@ -88,7 +104,12 @@ static const int Lighting_Output_Properties_Required[] = { #endif -1 }; -static const int Lighting_Output_Properties_Optional[] = { -1 }; +static const int Lighting_Output_Properties_Optional[] = { PROP_DESCRIPTION, + PROP_TRANSITION, +#if (BACNET_PROTOCOL_REVISION >= 24) + PROP_COLOR_OVERRIDE, PROP_COLOR_REFERENCE, PROP_OVERRIDE_COLOR_REFERENCE, +#endif + -1 }; static const int Lighting_Output_Properties_Proprietary[] = { -1 }; @@ -128,10 +149,10 @@ void Lighting_Output_Property_Lists( */ bool Lighting_Output_Valid_Instance(uint32_t object_instance) { - unsigned int index; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { return true; } @@ -145,7 +166,7 @@ bool Lighting_Output_Valid_Instance(uint32_t object_instance) */ unsigned Lighting_Output_Count(void) { - return MAX_LIGHTING_OUTPUTS; + return Keylist_Count(Object_List); } /** @@ -158,11 +179,7 @@ unsigned Lighting_Output_Count(void) */ uint32_t Lighting_Output_Index_To_Instance(unsigned index) { - uint32_t instance = 1; - - instance += index; - - return instance; + return Keylist_Key(Object_List, index); } /** @@ -176,16 +193,7 @@ uint32_t Lighting_Output_Index_To_Instance(unsigned index) */ unsigned Lighting_Output_Instance_To_Index(uint32_t object_instance) { - unsigned index = MAX_LIGHTING_OUTPUTS; - - if (object_instance) { - index = object_instance - 1; - if (index > MAX_LIGHTING_OUTPUTS) { - index = MAX_LIGHTING_OUTPUTS; - } - } - - return index; + return Keylist_Index(Object_List, object_instance); } /** @@ -198,15 +206,15 @@ unsigned Lighting_Output_Instance_To_Index(uint32_t object_instance) float Lighting_Output_Present_Value(uint32_t object_instance) { float value = 0.0; - unsigned index = 0; unsigned p = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - value = Lighting_Output[index].Relinquish_Default; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Relinquish_Default; for (p = 0; p < BACNET_MAX_PRIORITY; p++) { - if (BIT_CHECK(Lighting_Output[index].Priority_Active_Bits, p)) { - value = Lighting_Output[index].Priority_Array[p]; + if (BIT_CHECK(pObject->Priority_Active_Bits, p)) { + value = pObject->Priority_Array[p]; break; } } @@ -215,6 +223,73 @@ float Lighting_Output_Present_Value(uint32_t object_instance) return value; } +/** + * @brief Get the priority-array active status for the specific priority + * @param object [in] BACnet object instance + * @param priority [in] array index requested: + * 0 to N for individual array members + * @return the priority-array active status for the specific priority + */ +static bool Priority_Array_Active( + struct object_data *pObject, BACNET_ARRAY_INDEX priority) +{ + bool active = false; + + if (priority < BACNET_MAX_PRIORITY) { + if (BIT_CHECK(pObject->Priority_Active_Bits, priority)) { + active = true; + } + } + + return active; +} + +/** + * @brief Get the value of the next highest non-NULL priority, including + * Relinquish_Default + * @param object [in] BACnet object instance + * @param priority [in] array index requested: + * 0 to N for individual array members + * @return The priority-array value for the specific priority + */ +static float Priority_Array_Next_Value( + struct object_data *pObject, BACNET_ARRAY_INDEX priority) +{ + float real_value = 0.0; + unsigned p = 0; + + real_value = pObject->Relinquish_Default; + for (p = priority; p < BACNET_MAX_PRIORITY; p++) { + if (Priority_Array_Active(pObject, p)) { + real_value = pObject->Priority_Array[p]; + break; + } + } + + return real_value; +} + +/** + * @brief Get the priority-array value for the specific priority + * @param object [in] BACnet object instance + * @param priority [in] array index requested: + * 0 to N for individual array members + * @return The priority-array value for the specific priority + */ +static float Priority_Array_Value( + struct object_data *pObject, BACNET_ARRAY_INDEX priority) +{ + float real_value = 0.0; + + if (priority < BACNET_MAX_PRIORITY) { + if (BIT_CHECK(pObject->Priority_Active_Bits, priority)) { + real_value = pObject->Priority_Array[priority]; + } + } + + return real_value; +} + /** * @brief Encode a BACnetARRAY property element * @param object_instance [in] BACnet network port object instance number @@ -230,15 +305,17 @@ static int Lighting_Output_Priority_Array_Encode( { int apdu_len = BACNET_STATUS_ERROR; float real_value = 0.0; - unsigned index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if ((index < MAX_LIGHTING_OUTPUTS) && (priority < BACNET_MAX_PRIORITY)) { - if (BIT_CHECK(Lighting_Output[index].Priority_Active_Bits, priority)) { - real_value = Lighting_Output[index].Priority_Array[priority]; - apdu_len = encode_application_real(apdu, real_value); - } else { - apdu_len = encode_application_null(apdu); + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (priority < BACNET_MAX_PRIORITY) { + if (Priority_Array_Active(pObject, priority)) { + real_value = pObject->Priority_Array[priority]; + apdu_len = encode_application_real(apdu, real_value); + } else { + apdu_len = encode_application_null(apdu); + } } } @@ -252,25 +329,92 @@ static int Lighting_Output_Priority_Array_Encode( * * @return active priority 1..16, or 0 if no priority is active */ -unsigned Lighting_Output_Present_Value_Priority(uint32_t object_instance) +static unsigned Present_Value_Priority(struct object_data *pObject) { - unsigned index = 0; /* instance to index conversion */ unsigned p = 0; /* loop counter */ unsigned priority = 0; /* return value */ - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - for (p = 0; p < BACNET_MAX_PRIORITY; p++) { - if (BIT_CHECK(Lighting_Output[index].Priority_Active_Bits, p)) { - priority = p + 1; - break; - } + for (p = 0; p < BACNET_MAX_PRIORITY; p++) { + if (BIT_CHECK(pObject->Priority_Active_Bits, p)) { + priority = p + 1; + break; } } return priority; } +/** + * For a given object instance, relinquishes the present-value + * at a given priority 1..16. + * + * @param object - object instance + * @param priority - priority 1..16 + * + * @return true if values are within range and present-value is set. + */ +static bool Present_Value_Relinquish( + struct object_data *pObject, unsigned priority) +{ + bool status = false; + + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */)) { + priority--; + BIT_CLEAR(pObject->Priority_Active_Bits, priority); + pObject->Priority_Array[priority] = 0.0; + status = true; + } + + return status; +} + +/** + * For a given object instance, sets the present-value at a given + * priority 1..16. + * + * @param object_instance - object-instance number of the object + * @param value - floating point analog value + * @param priority - priority 1..16 + * + * @return true if values are within range and present-value is set. + */ +static bool Present_Value_Set( + struct object_data *pObject, float value, unsigned priority) +{ + bool status = false; + + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */)) { + priority--; + BIT_SET(pObject->Priority_Active_Bits, priority); + pObject->Priority_Array[priority] = value; + status = true; + } + + return status; +} + +/** + * For a given object instance-number, determines the active priority + * + * @param object_instance - object-instance number of the object + * + * @return active priority 1..16, or 0 if no priority is active + */ +unsigned Lighting_Output_Present_Value_Priority(uint32_t object_instance) +{ + unsigned priority = 0; /* return value */ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + priority = Present_Value_Priority(pObject); + } + + return priority; +} + /** * For a given object instance-number, sets the present-value at a given * priority 1..16. @@ -284,16 +428,16 @@ unsigned Lighting_Output_Present_Value_Priority(uint32_t object_instance) bool Lighting_Output_Present_Value_Set( uint32_t object_instance, float value, unsigned priority) { - unsigned index = 0; bool status = false; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { if (priority && (priority <= BACNET_MAX_PRIORITY) && (priority != 6 /* reserved */)) { priority--; - BIT_SET(Lighting_Output[index].Priority_Active_Bits, priority); - Lighting_Output[index].Priority_Array[priority] = value; + BIT_SET(pObject->Priority_Active_Bits, priority); + pObject->Priority_Array[priority] = value; status = true; } } @@ -301,6 +445,151 @@ bool Lighting_Output_Present_Value_Set( return status; } +/** + * For a given object instance-number, writes the present-value + * + * @param object_instance - object-instance number of the object + * @param value - property value to write + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Lighting_Output_Present_Value_Write(uint32_t object_instance, + float value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + uint8_t current_priority; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + 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. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else if ((priority > 0) && (priority <= BACNET_MAX_PRIORITY)) { + /* Note: Writing a special value has the same effect as writing + the corresponding lighting command and is subject to the same + restrictions. The special value itself is not written to the + priority array. */ + if (!islessgreater(value, -1.0)) { + /* Provides the same functionality as the + WARN lighting command. */ + current_priority = Present_Value_Priority(pObject); + if ((priority <= current_priority) && + (Priority_Array_Active(pObject, priority - 1)) && + (isgreater( + Priority_Array_Value(pObject, priority - 1), 0.0))) { + /* The blink-warn notification shall not occur + if any of the following conditions occur: + (a) The specified priority is not the highest + active priority, or + (b) The value at the specified priority is 0.0%, or + (c) Blink_Warn_Enable is FALSE. */ + pObject->Lighting_Command.operation = BACNET_LIGHTS_WARN; + } + status = true; + } else if (!islessgreater(value, -2.0)) { + /* Provides the same functionality as the + WARN_RELINQUISH lighting command. */ + current_priority = Present_Value_Priority(pObject); + if ((priority <= current_priority) && + (Priority_Array_Active(pObject, priority - 1)) && + (isgreater( + Priority_Array_Value(pObject, priority - 1), 0.0)) && + (isgreater(Priority_Array_Next_Value(pObject, priority - 1), + 0.0))) { + /* The blink-warn notification shall not occur, + and the value at the specified priority shall be + relinquished immediately if any of the following + conditions occur: + (a) The specified priority is not the highest + active priority, or + (b) The value at the specified priority + is 0.0% or NULL, or + (c) The value of the next highest non-NULL + priority, including Relinquish_Default, + is greater than 0.0%, or + (d) Blink_Warn_Enable is FALSE. */ + pObject->Lighting_Command.operation = + BACNET_LIGHTS_WARN_RELINQUISH; + } else { + Present_Value_Relinquish(pObject, priority); + } + status = true; + } else if (!islessgreater(value, -3.0)) { + /* Provides the same functionality as the + WARN_OFF lighting command. */ + current_priority = Present_Value_Priority(pObject); + if ((priority <= current_priority) && + (Priority_Array_Active(pObject, priority - 1)) && + (isgreater( + Priority_Array_Value(pObject, priority - 1), 0.0)) && + (isgreater(Priority_Array_Next_Value(pObject, priority - 1), + 0.0))) { + /* The blink-warn notification shall not occur and + the value 0.0% written at the specified + priority immediately if any of the following + conditions occur: + (a) The specified priority is not the highest + active priority, or + (b) The Present_Value is 0.0%, or + (c) Blink_Warn_Enable is FALSE. */ + pObject->Lighting_Command.operation = + BACNET_LIGHTS_WARN_OFF; + } else { + Present_Value_Set(pObject, 0.0, priority); + } + status = true; + } else if (isgreaterequal(value, 0.0) && + islessequal(value, 100.0)) { + Present_Value_Set(pObject, value, priority); + current_priority = Present_Value_Priority(pObject); + if (priority <= current_priority) { + /* we have priority - configure the Lighting Command */ + if (pObject->Transition == + BACNET_LIGHTING_TRANSITION_FADE) { + pObject->Lighting_Command.fade_time = + pObject->Default_Fade_Time; + pObject->Lighting_Command.operation = + BACNET_LIGHTS_FADE_TO; + } else if (pObject->Transition == + BACNET_LIGHTING_TRANSITION_RAMP) { + pObject->Lighting_Command.ramp_rate = + pObject->Default_Ramp_Rate; + pObject->Lighting_Command.operation = + BACNET_LIGHTS_RAMP_TO; + } else { + pObject->Lighting_Command.fade_time = 0; + pObject->Lighting_Command.operation = + BACNET_LIGHTS_FADE_TO; + } + pObject->Lighting_Command.target_level = value; + } + 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_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + /** * For a given object instance-number, relinquishes the present-value * at a given priority 1..16. @@ -313,18 +602,84 @@ bool Lighting_Output_Present_Value_Set( bool Lighting_Output_Present_Value_Relinquish( uint32_t object_instance, unsigned priority) { - unsigned index = 0; bool status = false; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - if (priority && (priority <= BACNET_MAX_PRIORITY) && - (priority != 6 /* reserved */)) { - priority--; - BIT_CLEAR(Lighting_Output[index].Priority_Active_Bits, priority); - Lighting_Output[index].Priority_Array[priority] = 0.0; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + status = Present_Value_Relinquish(pObject, priority); + } + + return status; +} + +/** + * For a given object instance-number, relinquishes the present-value + * + * @param object_instance - object-instance number of the object + * @param value - property value to write + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Lighting_Output_Present_Value_Relinquish_Write( + uint32_t object_instance, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + uint8_t old_priority, new_priority; + float value; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + 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. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else if ((priority > 0) && (priority <= BACNET_MAX_PRIORITY)) { + old_priority = Present_Value_Priority(pObject); + Lighting_Output_Present_Value_Relinquish(object_instance, priority); + new_priority = + Lighting_Output_Present_Value_Priority(object_instance); + if (old_priority != new_priority) { + if (new_priority > BACNET_MAX_PRIORITY) { + /* BACNET_LIGHTS_WARN_RELINQUISH? */ + value = Lighting_Output_Relinquish_Default(object_instance); + } else { + value = + Lighting_Output_Present_Value_Priority(object_instance); + } + /* we have priority - configure the Lighting Command */ + if (pObject->Transition == BACNET_LIGHTING_TRANSITION_FADE) { + pObject->Lighting_Command.fade_time = + pObject->Default_Fade_Time; + pObject->Lighting_Command.operation = BACNET_LIGHTS_FADE_TO; + } else if (pObject->Transition == + BACNET_LIGHTING_TRANSITION_RAMP) { + pObject->Lighting_Command.ramp_rate = + pObject->Default_Ramp_Rate; + pObject->Lighting_Command.operation = BACNET_LIGHTS_RAMP_TO; + } else { + pObject->Lighting_Command.fade_time = 0; + pObject->Lighting_Command.operation = BACNET_LIGHTS_FADE_TO; + } + pObject->Lighting_Command.target_level = value; + } status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; } return status; @@ -343,15 +698,107 @@ bool Lighting_Output_Present_Value_Relinquish( bool Lighting_Output_Object_Name( uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) { - char text_string[32] = ""; bool status = false; - unsigned index = 0; + struct object_data *pObject; + char name_text[24] = "LIGHTING-OUTPUT-4194303"; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - sprintf( - text_string, "LIGHTING OUTPUT %lu", (unsigned long)object_instance); - status = characterstring_init_ansi(object_name, text_string); + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (pObject->Object_Name) { + status = + characterstring_init_ansi(object_name, pObject->Object_Name); + } else { + snprintf(name_text, sizeof(name_text), "LIGHTING-OUTPUT-%u", + object_instance); + status = characterstring_init_ansi(object_name, name_text); + } + } + + return status; +} + +/** + * For a given object instance-number, sets the object-name + * Note that the object name must be unique within this device. + * + * @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 Lighting_Output_Name_Set(uint32_t object_instance, char *new_name) +{ + bool status = false; /* return value */ + BACNET_CHARACTER_STRING object_name; + BACNET_OBJECT_TYPE found_type = 0; + uint32_t found_instance = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject && new_name) { + /* All the object names in a device must be unique */ + characterstring_init_ansi(&object_name, new_name); + if (Device_Valid_Object_Name( + &object_name, &found_type, &found_instance)) { + if ((found_type == OBJECT_LIGHTING_OUTPUT) && + (found_instance == object_instance)) { + /* writing same name to same object */ + status = true; + } else { + /* duplicate name! */ + status = false; + } + } else { + status = true; + pObject->Object_Name = new_name; + Device_Inc_Database_Revision(); + } + } + + return status; +} + +/** + * 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 + */ +char *Lighting_Output_Description(uint32_t object_instance) +{ + char *name = NULL; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (pObject->Description) { + name = (char *)pObject->Description; + } else { + name = ""; + } + } + + return name; +} + +/** + * 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 object-name was set + */ +bool Lighting_Output_Description_Set(uint32_t object_instance, char *new_name) +{ + bool status = false; /* return value */ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject && new_name) { + status = true; + pObject->Description = new_name; } return status; @@ -369,13 +816,12 @@ bool Lighting_Output_Lighting_Command_Set( uint32_t object_instance, BACNET_LIGHTING_COMMAND *value) { bool status = false; - unsigned index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { /* FIXME: check lighting command member values */ - status = lighting_command_copy( - &Lighting_Output[index].Lighting_Command, value); + status = lighting_command_copy(&pObject->Lighting_Command, value); /* FIXME: set all the other values, and get the light levels moving */ } @@ -394,12 +840,11 @@ bool Lighting_Output_Lighting_Command( uint32_t object_instance, BACNET_LIGHTING_COMMAND *value) { bool status = false; - unsigned index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - status = lighting_command_copy( - value, &Lighting_Output[index].Lighting_Command); + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + status = lighting_command_copy(value, &pObject->Lighting_Command); } return status; @@ -416,11 +861,11 @@ BACNET_LIGHTING_IN_PROGRESS Lighting_Output_In_Progress( uint32_t object_instance) { BACNET_LIGHTING_IN_PROGRESS value = BACNET_LIGHTING_IDLE; - unsigned index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - value = Lighting_Output[index].In_Progress; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->In_Progress; } return value; @@ -439,11 +884,11 @@ bool Lighting_Output_In_Progress_Set( uint32_t object_instance, BACNET_LIGHTING_IN_PROGRESS in_progress) { bool status = false; - unsigned index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - Lighting_Output[index].In_Progress = in_progress; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->In_Progress = in_progress; } return status; @@ -459,11 +904,11 @@ bool Lighting_Output_In_Progress_Set( float Lighting_Output_Tracking_Value(uint32_t object_instance) { float value = 0.0; - unsigned index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - value = Lighting_Output[index].Tracking_Value; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Tracking_Value; } return value; @@ -481,11 +926,11 @@ float Lighting_Output_Tracking_Value(uint32_t object_instance) bool Lighting_Output_Tracking_Value_Set(uint32_t object_instance, float value) { bool status = false; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - Lighting_Output[index].Tracking_Value = value; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Tracking_Value = value; status = true; } @@ -503,11 +948,11 @@ bool Lighting_Output_Tracking_Value_Set(uint32_t object_instance, float value) bool Lighting_Output_Blink_Warn_Enable(uint32_t object_instance) { bool value = false; - unsigned index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - value = Lighting_Output[index].Blink_Warn_Enable; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Blink_Warn_Enable; } return value; @@ -526,11 +971,11 @@ bool Lighting_Output_Blink_Warn_Enable_Set( uint32_t object_instance, bool enable) { bool status = false; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - Lighting_Output[index].Blink_Warn_Enable = enable; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Blink_Warn_Enable = enable; status = true; } @@ -548,11 +993,11 @@ bool Lighting_Output_Blink_Warn_Enable_Set( uint32_t Lighting_Output_Egress_Time(uint32_t object_instance) { uint32_t value = 0; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - value = Lighting_Output[index].Egress_Time; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Egress_Time; } return value; @@ -570,11 +1015,11 @@ uint32_t Lighting_Output_Egress_Time(uint32_t object_instance) bool Lighting_Output_Egress_Time_Set(uint32_t object_instance, uint32_t seconds) { bool status = false; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - Lighting_Output[index].Egress_Time = seconds; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Egress_Time = seconds; status = true; } @@ -592,11 +1037,11 @@ bool Lighting_Output_Egress_Time_Set(uint32_t object_instance, uint32_t seconds) bool Lighting_Output_Egress_Active(uint32_t object_instance) { bool value = false; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - value = Lighting_Output[index].Egress_Active; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Egress_Active; } return value; @@ -613,11 +1058,11 @@ bool Lighting_Output_Egress_Active(uint32_t object_instance) uint32_t Lighting_Output_Default_Fade_Time(uint32_t object_instance) { uint32_t value = 0; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - value = Lighting_Output[index].Default_Fade_Time; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Default_Fade_Time; } return value; @@ -636,13 +1081,52 @@ bool Lighting_Output_Default_Fade_Time_Set( uint32_t object_instance, uint32_t milliseconds) { bool status = false; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if ((index < MAX_LIGHTING_OUTPUTS) && (milliseconds >= 100) && - (milliseconds <= 86400000)) { - Lighting_Output[index].Default_Fade_Time = milliseconds; - status = true; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if ((milliseconds >= 100) && (milliseconds <= 86400000)) { + pObject->Default_Fade_Time = milliseconds; + status = true; + } + } + + return status; +} + +/** + * Handle a WriteProperty to a specific property. + * + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Lighting_Output_Default_Fade_Time_Write(uint32_t object_instance, + uint32_t value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if ((value >= 100) && (value <= 86400000)) { + pObject->Default_Fade_Time = value; + 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; @@ -659,11 +1143,11 @@ bool Lighting_Output_Default_Fade_Time_Set( float Lighting_Output_Default_Ramp_Rate(uint32_t object_instance) { float value = 0.0; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - value = Lighting_Output[index].Default_Ramp_Rate; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Default_Ramp_Rate; } return value; @@ -682,13 +1166,52 @@ bool Lighting_Output_Default_Ramp_Rate_Set( uint32_t object_instance, float percent_per_second) { bool status = false; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if ((index < MAX_LIGHTING_OUTPUTS) && (percent_per_second >= 0.1) && - (percent_per_second <= 100.0)) { - Lighting_Output[index].Default_Ramp_Rate = percent_per_second; - status = true; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if ((percent_per_second >= 0.1) && (percent_per_second <= 100.0)) { + pObject->Default_Ramp_Rate = percent_per_second; + status = true; + } + } + + return status; +} + +/** + * Handle a WriteProperty to a specific property. + * + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Lighting_Output_Default_Ramp_Rate_Write(uint32_t object_instance, + float value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if ((value >= 0.1) && (value <= 100.0)) { + pObject->Default_Fade_Time = value; + 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; @@ -705,11 +1228,11 @@ bool Lighting_Output_Default_Ramp_Rate_Set( float Lighting_Output_Default_Step_Increment(uint32_t object_instance) { float value = 0.0; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - value = Lighting_Output[index].Default_Step_Increment; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Default_Step_Increment; } return value; @@ -728,13 +1251,53 @@ bool Lighting_Output_Default_Step_Increment_Set( uint32_t object_instance, float step_increment) { bool status = false; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if ((index < MAX_LIGHTING_OUTPUTS) && (step_increment >= 0.1) && - (step_increment <= 100.0)) { - Lighting_Output[index].Default_Step_Increment = step_increment; - status = true; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if ((step_increment >= 0.1) && (step_increment <= 100.0)) { + pObject->Default_Step_Increment = step_increment; + status = true; + } + } + + return status; +} + +/** + * Handle a WriteProperty to a specific property. + * + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Lighting_Output_Default_Step_Increment_Write( + uint32_t object_instance, + float value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if ((value >= 0.1) && (value <= 100.0)) { + pObject->Default_Step_Increment = value; + 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; @@ -753,11 +1316,11 @@ bool Lighting_Output_Default_Step_Increment_Set( unsigned Lighting_Output_Default_Priority(uint32_t object_instance) { unsigned value = 0; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - value = Lighting_Output[index].Lighting_Command_Default_Priority; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Lighting_Command_Default_Priority; } return value; @@ -776,13 +1339,15 @@ bool Lighting_Output_Default_Priority_Set( uint32_t object_instance, unsigned priority) { bool status = false; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if ((index < MAX_LIGHTING_OUTPUTS) && (priority >= BACNET_MIN_PRIORITY) && - (priority <= BACNET_MAX_PRIORITY)) { - Lighting_Output[index].Lighting_Command_Default_Priority = priority; - status = true; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if ((priority >= BACNET_MIN_PRIORITY) && + (priority <= BACNET_MAX_PRIORITY)) { + pObject->Lighting_Command_Default_Priority = priority; + status = true; + } } return status; @@ -799,11 +1364,11 @@ bool Lighting_Output_Default_Priority_Set( bool Lighting_Output_Out_Of_Service(uint32_t object_instance) { bool value = false; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - value = Lighting_Output[index].Out_Of_Service; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Out_Of_Service; } return value; @@ -819,11 +1384,11 @@ bool Lighting_Output_Out_Of_Service(uint32_t object_instance) */ void Lighting_Output_Out_Of_Service_Set(uint32_t object_instance, bool value) { - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - Lighting_Output[index].Out_Of_Service = value; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Out_Of_Service = value; } } @@ -838,11 +1403,11 @@ void Lighting_Output_Out_Of_Service_Set(uint32_t object_instance, bool value) float Lighting_Output_Relinquish_Default(uint32_t object_instance) { float value = 0.0; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - value = Lighting_Output[index].Relinquish_Default; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Relinquish_Default; } return value; @@ -861,11 +1426,257 @@ bool Lighting_Output_Relinquish_Default_Set( uint32_t object_instance, float value) { bool status = false; - unsigned int index = 0; + struct object_data *pObject; - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - Lighting_Output[index].Relinquish_Default = value; + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Relinquish_Default = value; + } + + return status; +} + +/** + * For a given object instance-number, gets the property value + * + * @param object_instance - object-instance number of the object + * @return property value + */ +BACNET_LIGHTING_TRANSITION Lighting_Output_Transition(uint32_t object_instance) +{ + BACNET_LIGHTING_TRANSITION value = BACNET_LIGHTING_TRANSITION_NONE; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Transition; + } + + return value; +} + +/** + * For a given object instance-number, sets the property value + * + * @param object_instance - object-instance number of the object + * @param value - BACNET_COLOR_TRANSITION + * @return true if values are within range and value is set. + */ +bool Lighting_Output_Transition_Set( + uint32_t object_instance, BACNET_LIGHTING_TRANSITION value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (value <= BACNET_LIGHTING_TRANSITION_PROPRIETARY_LAST) { + pObject->Transition = value; + status = true; + } + } + + return status; +} + +/** + * Handle a WriteProperty to a specific property. + * + * @param object_instance - object-instance number of the object + * @param value - property value to be written + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Lighting_Output_Transition_Write(uint32_t object_instance, + uint32_t value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if (value < BACNET_LIGHTING_TRANSITION_PROPRIETARY_LAST) { + pObject->Transition = value; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + +/** + * For a given object instance-number, returns the color-override + * property value + * + * @param object_instance - object-instance number of the object + * + * @return color-override property value + */ +bool Lighting_Output_Color_Override(uint32_t object_instance) +{ + bool value = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Color_Override; + } + + return value; +} + +/** + * For a given object instance-number, sets the color-override + * property value + * + * @param object_instance - object-instance number of the object + * @param value - color-override boolean value + * + * @return true if the color-override property value was set + */ +bool Lighting_Output_Color_Override_Set(uint32_t object_instance, bool value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Color_Override = value; + } + + return status; +} + +/** + * For a given object instance-number, gets the property value + * + * This property, of type BACnetObjectIdentifier, when present, + * shall specify the object identifier of a Color or Color Temperature + * object within the same device that controls the color aspects + * of this Lighting Output. If the object instance portion of + * the object identifier has the value 4194303, then there is no color + * companion object associated with this output. In that case the + * applicable color or color temperature shall be a local matter. + * + * @param object_instance - object-instance number of the object + * @param value - holds the property value + * + * @return true if property was retrieved + */ +bool Lighting_Output_Color_Reference( + uint32_t object_instance, BACNET_OBJECT_ID *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (value) { + value->type = pObject->Color_Reference.type; + value->instance = pObject->Color_Reference.instance; + } + status = true; + } + + return status; +} + +/** + * For a given object instance-number, sets the property value + * + * @param object_instance - object-instance number of the object + * @param value - property value to set + * + * @return true if property value was set + */ +bool Lighting_Output_Color_Reference_Set( + uint32_t object_instance, BACNET_OBJECT_ID *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Color_Reference.type = value->type; + pObject->Color_Reference.instance = value->instance; + status = true; + } + + return status; +} + +/** + * For a given object instance-number, gets the property value + * + * This property, of type BACnetObjectIdentifier, when present, shall + * specify the object identifier of a Color or Color Temperature + * object within the same device that controls the color override + * aspects of this Lighting Output. Color override occurs + * when the Color_Override property of the Lighting Output is + * written with TRUE. In this case the Override_Color_Reference points + * to an object whose color shall be used to control the actual color + * of the lighting output. While color-overridden, any fade that may + * be in progress for the Color_Reference object, shall continue without + * interruption, except that the actual color output shall use the + * override color instead. See Clause 12.55 for a description of + * color override. Color override shall cease when Color_Override + * is written with FALSE. + * + * @param object_instance - object-instance number of the object + * @param value - holds the property value + * + * @return true if property was retrieved + */ +bool Lighting_Output_Override_Color_Reference( + uint32_t object_instance, BACNET_OBJECT_ID *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (value) { + value->type = pObject->Override_Color_Reference.type; + value->instance = pObject->Override_Color_Reference.instance; + } + status = true; + } + + return status; +} + +/** + * For a given object instance-number, sets the property value + * + * @param object_instance - object-instance number of the object + * @param value - property value to set + * + * @return true if property value was set + */ +bool Lighting_Output_Override_Color_Reference_Set( + uint32_t object_instance, BACNET_OBJECT_ID *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Override_Color_Reference.type = value->type; + pObject->Override_Color_Reference.instance = value->instance; + status = true; } return status; @@ -888,6 +1699,9 @@ int Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) BACNET_BIT_STRING bit_string; BACNET_CHARACTER_STRING char_string; BACNET_LIGHTING_COMMAND lighting_command; +#if (BACNET_PROTOCOL_REVISION >= 24) + BACNET_OBJECT_ID object_id = { 0 }; +#endif float real_value = (float)1.414; uint32_t unsigned_value = 0; unsigned i = 0; @@ -974,6 +1788,10 @@ int Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) Lighting_Output_Default_Step_Increment(rpdata->object_instance); apdu_len = encode_application_real(&apdu[0], real_value); break; + case PROP_TRANSITION: + apdu_len = encode_application_enumerated( + apdu, Lighting_Output_Transition(rpdata->object_instance)); + break; case PROP_PRIORITY_ARRAY: apdu_len = bacnet_array_encode(rpdata->object_instance, rpdata->array_index, Lighting_Output_Priority_Array_Encode, @@ -1006,6 +1824,30 @@ int Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) } break; #endif +#if (BACNET_PROTOCOL_REVISION >= 24) + case PROP_COLOR_OVERRIDE: + apdu_len = encode_application_boolean(&apdu[0], + Lighting_Output_Color_Override(rpdata->object_instance)); + break; + case PROP_COLOR_REFERENCE: + (void)Lighting_Output_Color_Reference( + rpdata->object_instance, &object_id); + apdu_len = encode_application_object_id( + &apdu[0], object_id.type, object_id.instance); + break; + case PROP_OVERRIDE_COLOR_REFERENCE: + (void)Lighting_Output_Override_Color_Reference( + rpdata->object_instance, &object_id); + apdu_len = encode_application_object_id( + &apdu[0], object_id.type, object_id.instance); + break; +#endif + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, + Lighting_Output_Description(rpdata->object_instance)); + 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; @@ -1060,42 +1902,17 @@ bool Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) status = write_property_type_valid( wp_data, &value, BACNET_APPLICATION_TAG_REAL); if (status) { - /* Command priority 6 is reserved for use by Minimum On/Off - algorithm and may not be used for other purposes in any - object. */ - status = - Lighting_Output_Present_Value_Set(wp_data->object_instance, - value.type.Real, wp_data->priority); - if (wp_data->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 if (!status) { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } + status = Lighting_Output_Present_Value_Write( + wp_data->object_instance, value.type.Real, + wp_data->priority, &wp_data->error_class, + &wp_data->error_code); } else { status = write_property_type_valid( wp_data, &value, BACNET_APPLICATION_TAG_NULL); if (status) { - if (wp_data->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. - Note - Lighting_Output_Present_Value_Relinquish() - will have returned false because of this */ - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - } else { - status = Lighting_Output_Present_Value_Relinquish( - wp_data->object_instance, wp_data->priority); - if (!status) { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } - } + status = Lighting_Output_Present_Value_Relinquish_Write( + wp_data->object_instance, wp_data->priority, + &wp_data->error_class, &wp_data->error_code); } } break; @@ -1119,6 +1936,46 @@ bool Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) wp_data->object_instance, value.type.Boolean); } break; + case PROP_DEFAULT_FADE_TIME: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + status = Lighting_Output_Default_Fade_Time_Write( + wp_data->object_instance, value.type.Unsigned_Int, + wp_data->priority, &wp_data->error_class, + &wp_data->error_code); + } + break; + case PROP_DEFAULT_RAMP_RATE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_REAL); + if (status) { + status = Lighting_Output_Default_Ramp_Rate_Write( + wp_data->object_instance, value.type.Real, + wp_data->priority, &wp_data->error_class, + &wp_data->error_code); + } + break; + case PROP_DEFAULT_STEP_INCREMENT: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_REAL); + if (status) { + status = Lighting_Output_Default_Step_Increment_Write( + wp_data->object_instance, value.type.Real, + wp_data->priority, &wp_data->error_class, + &wp_data->error_code); + } + break; + case PROP_TRANSITION: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + status = + Lighting_Output_Transition_Write(wp_data->object_instance, + value.type.Enumerated, wp_data->priority, + &wp_data->error_class, &wp_data->error_code); + } + break; case PROP_OBJECT_IDENTIFIER: case PROP_OBJECT_NAME: case PROP_OBJECT_TYPE: @@ -1128,14 +1985,18 @@ bool Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) case PROP_BLINK_WARN_ENABLE: case PROP_EGRESS_TIME: case PROP_EGRESS_ACTIVE: - case PROP_DEFAULT_FADE_TIME: - case PROP_DEFAULT_RAMP_RATE: - case PROP_DEFAULT_STEP_INCREMENT: case PROP_PRIORITY_ARRAY: case PROP_RELINQUISH_DEFAULT: + case PROP_LIGHTING_COMMAND_DEFAULT_PRIORITY: #if (BACNET_PROTOCOL_REVISION >= 17) case PROP_CURRENT_COMMAND_PRIORITY: #endif +#if (BACNET_PROTOCOL_REVISION >= 24) + case PROP_COLOR_OVERRIDE: + case PROP_COLOR_REFERENCE: + case PROP_OVERRIDE_COLOR_REFERENCE: +#endif + case PROP_DESCRIPTION: wp_data->error_class = ERROR_CLASS_PROPERTY; wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; break; @@ -1148,26 +2009,6 @@ bool Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) return status; } -/** - * Handles the timing for a single Lighting Output object Ramp - * - * @param pLight - Lighting Output object - * @param pCommand - BACNET_LIGHTING_COMMAND of the Lighting Output object - * @param milliseconds - number of milliseconds elapsed since previously - * called. Works best when called about every 10 milliseconds. - */ -static void Lighting_Output_Ramp_Handler(struct lighting_output_object *pLight, - BACNET_LIGHTING_COMMAND *pCommand, - uint16_t milliseconds) -{ - if (pLight && pCommand) { - /* FIXME: add ramp functionality */ - (void)pLight; - (void)pCommand; - (void)milliseconds; - } -} - /** * Handles the timing for a single Lighting Output object Fade * @@ -1176,49 +2017,328 @@ static void Lighting_Output_Ramp_Handler(struct lighting_output_object *pLight, * @param milliseconds - number of milliseconds elapsed since previously * called. Works best when called about every 10 milliseconds. */ -static void Lighting_Output_Fade_Handler(struct lighting_output_object *pLight, - BACNET_LIGHTING_COMMAND *pCommand, - uint16_t milliseconds) +static void Lighting_Output_Fade_Handler( + uint32_t object_instance, uint16_t milliseconds) { - if (pLight && pCommand) { - /* FIXME: add fade functionality */ - (void)pLight; - (void)pCommand; - (void)milliseconds; + struct object_data *pObject; + float old_value; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return; + } + old_value = pObject->Tracking_Value; + if (milliseconds >= pObject->Lighting_Command.fade_time) { + /* stop fading */ + pObject->Tracking_Value = pObject->Lighting_Command.target_level; + pObject->In_Progress = BACNET_LIGHTING_IDLE; + pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; + pObject->Lighting_Command.fade_time = 0; + } else { + if (!islessgreater(pObject->Tracking_Value, + pObject->Lighting_Command.target_level)) { + /* stop fading */ + pObject->Tracking_Value = pObject->Lighting_Command.target_level; + pObject->In_Progress = BACNET_LIGHTING_IDLE; + pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; + pObject->Lighting_Command.fade_time = 0; + } else { + /* fading */ + pObject->Tracking_Value = linear_interpolate(0, milliseconds, + pObject->Lighting_Command.fade_time, old_value, + pObject->Lighting_Command.target_level); + pObject->Lighting_Command.fade_time -= milliseconds; + pObject->In_Progress = BACNET_LIGHTING_FADE_ACTIVE; + } + } + if (Lighting_Output_Write_Present_Value_Callback) { + Lighting_Output_Write_Present_Value_Callback( + object_instance, old_value, pObject->Tracking_Value); } } /** - * Handles the timing for a single Lighting Output object + * Updates the object tracking value while ramping * - * @param index - 0..MAX_LIGHTING_OUTPUTS value - * @param milliseconds - number of milliseconds elapsed since previously - * called. Works best when called about every 10 milliseconds. + * Commands Present_Value to ramp from the current Tracking_Value to the + * target-level specified in the command. The ramp operation + * changes the output from its current value to target-level, + * at a particular percent per second defined by ramp-rate. + * While the ramp operation is executing, In_Progress shall be set + * to RAMP_ACTIVE, and Tracking_Value shall be updated to reflect the current + * progress of the ramp. shall be clamped to + * Min_Actual_Value and Max_Actual_Value. + * + * @param object_instance - object-instance number of the object + * @param milliseconds - number of milliseconds elapsed */ -static void Lighting_Output_Timer_Handler(unsigned index, uint16_t milliseconds) +static void Lighting_Output_Ramp_Handler( + uint32_t object_instance, uint16_t milliseconds) { - struct lighting_output_object *pLight = NULL; - BACNET_LIGHTING_COMMAND *pCommand = NULL; + float old_value, target_value, min_value, max_value, step_value, steps; + struct object_data *pObject; - if (index < MAX_LIGHTING_OUTPUTS) { - pLight = &Lighting_Output[index]; - pCommand = &pLight->Lighting_Command; - switch (pCommand->operation) { + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return; + } + old_value = pObject->Tracking_Value; + min_value = pObject->Min_Actual_Value; + max_value = pObject->Max_Actual_Value; + target_value = pObject->Lighting_Command.target_level; + /* clamp target within min/max, if needed */ + if (isgreater(target_value, max_value)) { + target_value = max_value; + } + if (isless(target_value, min_value)) { + target_value = min_value; + } + /* determine the number of steps */ + if (milliseconds <= 1000) { + /* percent per second */ + steps = linear_interpolate( + 0, milliseconds, 1000, 0, pObject->Lighting_Command.ramp_rate); + } else { + steps = (milliseconds * pObject->Lighting_Command.ramp_rate) / 1000; + } + if (!islessgreater(pObject->Tracking_Value, target_value)) { + /* stop ramping */ + pObject->Tracking_Value = target_value; + pObject->In_Progress = BACNET_LIGHTING_IDLE; + pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; + } else { + if (isless(old_value, target_value)) { + step_value = old_value + steps; + } else if (isgreater(old_value, target_value)) { + if (isgreater(steps, old_value)) { + step_value = old_value - steps; + } else { + step_value = target_value; + } + } else { + step_value = target_value; + } + /* clamp target within min/max, if needed */ + if (isgreater(step_value, max_value)) { + step_value = max_value; + } + if (isless(step_value, min_value)) { + step_value = min_value; + } + pObject->Tracking_Value = step_value; + pObject->In_Progress = BACNET_LIGHTING_RAMP_ACTIVE; + } + if (Lighting_Output_Write_Present_Value_Callback) { + Lighting_Output_Write_Present_Value_Callback( + object_instance, old_value, pObject->Tracking_Value); + } +} + +/** + * Updates the object tracking value while stepping + * + * Commands Present_Value to a value equal to the Tracking_Value + * plus the step-increment. The resulting sum shall be clamped to + * Min_Actual_Value and Max_Actual_Value + * + * @param object_instance - object-instance number of the object + */ +static void Lighting_Output_Step_Up_Handler(uint32_t object_instance) +{ + float old_value, target_value, min_value, max_value, step_value; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return; + } + old_value = pObject->Tracking_Value; + min_value = pObject->Min_Actual_Value; + max_value = pObject->Max_Actual_Value; + step_value = pObject->Lighting_Command.step_increment; + /* inhibit ON if the value is already OFF */ + if (isgreaterequal(old_value, min_value)) { + target_value = old_value + step_value; + /* clamp target within min/max, if needed */ + if (isgreater(target_value, max_value)) { + target_value = max_value; + } + if (isless(target_value, min_value)) { + target_value = min_value; + } + pObject->Present_Value = target_value; + pObject->Tracking_Value = target_value; + pObject->In_Progress = BACNET_LIGHTING_IDLE; + pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; + if (Lighting_Output_Write_Present_Value_Callback) { + Lighting_Output_Write_Present_Value_Callback( + object_instance, old_value, pObject->Tracking_Value); + } + } +} + +/** + * Updates the object tracking value while stepping + * + * Commands Present_Value to a value equal to the Tracking_Value + * plus the step-increment. The resulting sum shall be clamped to + * Min_Actual_Value and Max_Actual_Value + * + * @param object_instance - object-instance number of the object + */ +static void Lighting_Output_Step_Down_Handler(uint32_t object_instance) +{ + float old_value, target_value, min_value, max_value, step_value; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return; + } + old_value = target_value = pObject->Tracking_Value; + min_value = pObject->Min_Actual_Value; + max_value = pObject->Max_Actual_Value; + step_value = pObject->Lighting_Command.step_increment; + if (isgreaterequal(target_value, step_value)) { + target_value -= step_value; + } else { + target_value = 0.0; + } + /* clamp target within min/max, if needed */ + if (isgreater(target_value, max_value)) { + target_value = max_value; + } + if (isless(target_value, min_value)) { + target_value = min_value; + } + pObject->Present_Value = target_value; + pObject->Tracking_Value = target_value; + pObject->In_Progress = BACNET_LIGHTING_IDLE; + pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; + if (Lighting_Output_Write_Present_Value_Callback) { + Lighting_Output_Write_Present_Value_Callback( + object_instance, old_value, pObject->Tracking_Value); + } +} + +/** + * Updates the object tracking value while stepping + * + * Commands Present_Value to a value equal to the Tracking_Value + * plus the step-increment. The resulting sum shall be clamped to + * Min_Actual_Value and Max_Actual_Value + * + * @param object_instance - object-instance number of the object + */ +static void Lighting_Output_Step_On_Handler(uint32_t object_instance) +{ + float old_value, target_value, min_value, max_value, step_value; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return; + } + old_value = target_value = pObject->Tracking_Value; + min_value = pObject->Min_Actual_Value; + max_value = pObject->Max_Actual_Value; + step_value = pObject->Lighting_Command.step_increment; + target_value += step_value; + /* clamp target within min/max, if needed */ + if (isgreater(target_value, max_value)) { + target_value = max_value; + } + if (isless(target_value, min_value)) { + target_value = min_value; + } + pObject->Present_Value = target_value; + pObject->Tracking_Value = target_value; + pObject->In_Progress = BACNET_LIGHTING_IDLE; + pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; + if (Lighting_Output_Write_Present_Value_Callback) { + Lighting_Output_Write_Present_Value_Callback( + object_instance, old_value, pObject->Tracking_Value); + } +} + +/** + * Updates the object tracking value while stepping + * + * Commands Present_Value to a value equal to the Tracking_Value + * plus the step-increment. The resulting sum shall be clamped to + * Min_Actual_Value and Max_Actual_Value + * + * @param object_instance - object-instance number of the object + */ +static void Lighting_Output_Step_Off_Handler(uint32_t object_instance) +{ + float old_value, target_value, min_value, max_value, step_value; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return; + } + old_value = target_value = pObject->Tracking_Value; + min_value = pObject->Min_Actual_Value; + max_value = pObject->Max_Actual_Value; + step_value = pObject->Lighting_Command.step_increment; + if (isgreaterequal(target_value, step_value)) { + target_value -= step_value; + } else { + target_value = 0; + } + /* clamp target within max */ + if (isgreater(target_value, max_value)) { + target_value = max_value; + } + /* jump target to OFF if below min */ + if (isless(target_value, min_value)) { + target_value = 0.0; + } + pObject->Present_Value = target_value; + pObject->Tracking_Value = target_value; + pObject->In_Progress = BACNET_LIGHTING_IDLE; + pObject->Lighting_Command.operation = BACNET_LIGHTS_STOP; + if (Lighting_Output_Write_Present_Value_Callback) { + Lighting_Output_Write_Present_Value_Callback( + object_instance, old_value, pObject->Tracking_Value); + } +} + +/** + * @brief Updates the lighting object tracking value per ramp or fade or step + * @param object_instance - object-instance number of the object + * @param milliseconds - number of milliseconds elapsed since previously + * called. Suggest that this is called every 10 milliseconds. + */ +void Lighting_Output_Timer(uint32_t object_instance, uint16_t milliseconds) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + switch (pObject->Lighting_Command.operation) { case BACNET_LIGHTS_NONE: + pObject->In_Progress = BACNET_LIGHTING_IDLE; break; case BACNET_LIGHTS_FADE_TO: - Lighting_Output_Fade_Handler(pLight, pCommand, milliseconds); + Lighting_Output_Fade_Handler(object_instance, milliseconds); break; case BACNET_LIGHTS_RAMP_TO: - Lighting_Output_Ramp_Handler(pLight, pCommand, milliseconds); + Lighting_Output_Ramp_Handler(object_instance, milliseconds); break; case BACNET_LIGHTS_STEP_UP: + Lighting_Output_Step_Up_Handler(object_instance); break; case BACNET_LIGHTS_STEP_DOWN: + Lighting_Output_Step_Down_Handler(object_instance); break; case BACNET_LIGHTS_STEP_ON: + Lighting_Output_Step_On_Handler(object_instance); break; case BACNET_LIGHTS_STEP_OFF: + Lighting_Output_Step_Off_Handler(object_instance); break; case BACNET_LIGHTS_WARN: break; @@ -1227,6 +2347,7 @@ static void Lighting_Output_Timer_Handler(unsigned index, uint16_t milliseconds) case BACNET_LIGHTS_WARN_RELINQUISH: break; case BACNET_LIGHTS_STOP: + pObject->In_Progress = BACNET_LIGHTING_IDLE; break; default: break; @@ -1235,58 +2356,131 @@ static void Lighting_Output_Timer_Handler(unsigned index, uint16_t milliseconds) } /** - * Initializes the Lighting Output object data - * - * @param milliseconds - number of milliseconds elapsed since previously - * called. Works best when called about every 10 milliseconds. + * @brief Sets a callback used when present-value is written from BACnet + * @param cb - callback used to provide indications */ -void Lighting_Output_Timer(uint16_t milliseconds) +void Lighting_Output_Write_Present_Value_Callback_Set( + lighting_output_write_present_value_callback cb) { - unsigned i = 0; + Lighting_Output_Write_Present_Value_Callback = cb; +} - for (i = 0; i < MAX_LIGHTING_OUTPUTS; i++) { - Lighting_Output_Timer_Handler(i, milliseconds); +/** + * @brief Creates a Color object + * @param object_instance - object-instance number of the object + * @return the object-instance that was created, or BACNET_MAX_INSTANCE + */ +uint32_t Lighting_Output_Create(uint32_t object_instance) +{ + struct object_data *pObject = NULL; + int index = 0; + unsigned p = 0; + + 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) { + pObject = calloc(1, sizeof(struct object_data)); + if (!pObject) { + return BACNET_MAX_INSTANCE; + } + pObject->Object_Name = NULL; + pObject->Description = NULL; + pObject->Present_Value = 0.0; + pObject->Tracking_Value = 0.0; + pObject->Physical_Value = 0.0; + pObject->Lighting_Command.operation = BACNET_LIGHTS_NONE; + pObject->Lighting_Command.use_target_level = false; + pObject->Lighting_Command.use_ramp_rate = false; + pObject->Lighting_Command.use_step_increment = false; + pObject->Lighting_Command.use_fade_time = false; + pObject->Lighting_Command.use_priority = false; + pObject->In_Progress = BACNET_LIGHTING_IDLE; + pObject->Out_Of_Service = false; + pObject->Blink_Warn_Enable = false; + pObject->Egress_Active = false; + pObject->Egress_Time = 0; + pObject->Default_Fade_Time = 100; + pObject->Default_Ramp_Rate = 100.0; + pObject->Default_Step_Increment = 1.0; + pObject->Transition = BACNET_LIGHTING_TRANSITION_FADE; + pObject->Feedback_Value = 0.0; + for (p = 0; p < BACNET_MAX_PRIORITY; p++) { + pObject->Priority_Array[p] = 0.0; + BIT_CLEAR(pObject->Priority_Active_Bits, p); + } + pObject->Relinquish_Default = 0.0; + pObject->Power = 0.0; + pObject->Instantaneous_Power = 0.0; + pObject->Min_Actual_Value = 0.0; + pObject->Max_Actual_Value = 100.0; + pObject->Lighting_Command_Default_Priority = 16; + pObject->Color_Override = false; + pObject->Color_Reference.type = OBJECT_COLOR; + pObject->Color_Reference.instance = BACNET_MAX_INSTANCE; + pObject->Override_Color_Reference.type = OBJECT_COLOR; + pObject->Override_Color_Reference.instance = BACNET_MAX_INSTANCE; + /* add to list */ + index = Keylist_Data_Add(Object_List, object_instance, pObject); + if (index < 0) { + free(pObject); + return BACNET_MAX_INSTANCE; + } + } + + return object_instance; +} + +/** + * Deletes an object instance + * @param object_instance - object-instance number of the object + * @return true if the object is deleted + */ +bool Lighting_Output_Delete(uint32_t object_instance) +{ + bool status = false; + struct object_data *pObject = NULL; + + pObject = Keylist_Data_Delete(Object_List, object_instance); + if (pObject) { + free(pObject); + status = true; + } + + return status; +} + +/** + * Deletes all the objects and their data + */ +void Lighting_Output_Cleanup(void) +{ + struct object_data *pObject; + + if (Object_List) { + do { + pObject = Keylist_Data_Pop(Object_List); + if (pObject) { + free(pObject); + } + } while (pObject); + Keylist_Delete(Object_List); + Object_List = NULL; } } /** - * Initializes the Lighting Output object data + * Initializes the object list */ void Lighting_Output_Init(void) { - unsigned i, p; - - for (i = 0; i < MAX_LIGHTING_OUTPUTS; i++) { - Lighting_Output[i].Present_Value = 0.0; - Lighting_Output[i].Tracking_Value = 0.0; - Lighting_Output[i].Physical_Value = 0.0; - Lighting_Output[i].Lighting_Command.operation = BACNET_LIGHTS_NONE; - Lighting_Output[i].Lighting_Command.use_target_level = false; - Lighting_Output[i].Lighting_Command.use_ramp_rate = false; - Lighting_Output[i].Lighting_Command.use_step_increment = false; - Lighting_Output[i].Lighting_Command.use_fade_time = false; - Lighting_Output[i].Lighting_Command.use_priority = false; - Lighting_Output[i].In_Progress = BACNET_LIGHTING_IDLE; - Lighting_Output[i].Out_Of_Service = false; - Lighting_Output[i].Blink_Warn_Enable = false; - Lighting_Output[i].Egress_Active = false; - Lighting_Output[i].Egress_Time = 0; - Lighting_Output[i].Default_Fade_Time = 100; - Lighting_Output[i].Default_Ramp_Rate = 100.0; - Lighting_Output[i].Default_Step_Increment = 1.0; - Lighting_Output[i].Transition = BACNET_LIGHTING_TRANSITION_IDLE; - Lighting_Output[i].Feedback_Value = 0.0; - for (p = 0; p < BACNET_MAX_PRIORITY; p++) { - Lighting_Output[i].Priority_Array[p] = 0.0; - BIT_CLEAR(Lighting_Output[i].Priority_Active_Bits, p); - } - Lighting_Output[i].Relinquish_Default = 0.0; - Lighting_Output[i].Power = 0.0; - Lighting_Output[i].Instantaneous_Power = 0.0; - Lighting_Output[i].Min_Actual_Value = 0.0; - Lighting_Output[i].Max_Actual_Value = 100.0; - Lighting_Output[i].Lighting_Command_Default_Priority = 16; - } - - return; + Object_List = Keylist_Create(); } diff --git a/src/bacnet/basic/object/lo.h b/src/bacnet/basic/object/lo.h index af8751e5..0a5f7bbf 100644 --- a/src/bacnet/basic/object/lo.h +++ b/src/bacnet/basic/object/lo.h @@ -33,6 +33,15 @@ #include "bacnet/rp.h" #include "bacnet/wp.h" +/** + * @brief Callback for write present value request + * @param object_instance - object-instance number of the object + * @param old_value - value prior to write + * @param value - value of the write + */ +typedef void (*lighting_output_write_present_value_callback)( + uint32_t object_instance, float old_value, float value); + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -193,6 +202,14 @@ extern "C" { uint32_t object_instance, float step_increment); + BACNET_STACK_EXPORT + BACNET_LIGHTING_TRANSITION Lighting_Output_Transition( + uint32_t object_instance); + BACNET_STACK_EXPORT + bool Lighting_Output_Transition_Set( + uint32_t object_instance, + BACNET_LIGHTING_TRANSITION value); + BACNET_STACK_EXPORT unsigned Lighting_Output_Default_Priority( uint32_t object_instance); @@ -201,10 +218,47 @@ extern "C" { uint32_t object_instance, unsigned priority); + BACNET_STACK_EXPORT + bool Lighting_Output_Color_Override( + uint32_t object_instance); + BACNET_STACK_EXPORT + bool Lighting_Output_Color_Override_Set( + uint32_t object_instance, + bool value); + + BACNET_STACK_EXPORT + bool Lighting_Output_Color_Reference( + uint32_t object_instance, + BACNET_OBJECT_ID *value); + BACNET_STACK_EXPORT + bool Lighting_Output_Color_Reference_Set( + uint32_t object_instance, + BACNET_OBJECT_ID *value); + + BACNET_STACK_EXPORT + bool Lighting_Output_Override_Color_Reference( + uint32_t object_instance, + BACNET_OBJECT_ID *value); + BACNET_STACK_EXPORT + bool Lighting_Output_Override_Color_Reference_Set( + uint32_t object_instance, + BACNET_OBJECT_ID *value); + BACNET_STACK_EXPORT void Lighting_Output_Timer( + uint32_t object_instance, uint16_t milliseconds); + BACNET_STACK_EXPORT + void Lighting_Output_Write_Present_Value_Callback_Set( + lighting_output_write_present_value_callback cb); + + BACNET_STACK_EXPORT + uint32_t Lighting_Output_Create(uint32_t object_instance); + BACNET_STACK_EXPORT + bool Lighting_Output_Delete(uint32_t object_instance); + BACNET_STACK_EXPORT + void Lighting_Output_Cleanup(void); BACNET_STACK_EXPORT void Lighting_Output_Init( void); diff --git a/src/bacnet/basic/object/mso.c b/src/bacnet/basic/object/mso.c index 032828af..089e24d8 100644 --- a/src/bacnet/basic/object/mso.c +++ b/src/bacnet/basic/object/mso.c @@ -1164,9 +1164,9 @@ uint32_t Multistate_Output_Create(uint32_t object_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 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); } @@ -1186,9 +1186,7 @@ uint32_t Multistate_Output_Create(uint32_t object_instance) pObject->Relinquish_Default = 1; /* add to list */ index = Keylist_Data_Add(Object_List, object_instance, pObject); - if (index >= 0) { - Device_Inc_Database_Revision(); - } else { + if (index < 0) { free(pObject); return BACNET_MAX_INSTANCE; } @@ -1214,7 +1212,6 @@ bool Multistate_Output_Delete(uint32_t object_instance) if (pObject) { free(pObject); status = true; - Device_Inc_Database_Revision(); } return status; @@ -1232,7 +1229,6 @@ void Multistate_Output_Cleanup(void) pObject = Keylist_Data_Pop(Object_List); if (pObject) { free(pObject); - Device_Inc_Database_Revision(); } } while (pObject); Keylist_Delete(Object_List); diff --git a/src/bacnet/basic/sys/color_rgb.c b/src/bacnet/basic/sys/color_rgb.c index 3d08a06d..d593ba3d 100644 --- a/src/bacnet/basic/sys/color_rgb.c +++ b/src/bacnet/basic/sys/color_rgb.c @@ -41,14 +41,16 @@ static double clamp(double d, double min, double max) * @param x_coordinate - return x of CIE xy 0.0..1.0 * @param y_coordinate - return y of CIE xy 0.0..1.0 * @param brightness - return brightness of the CIE xy color 0..255 + * @param gamma_correction - true if gamma correction is applied * @note http://en.wikipedia.org/wiki/Srgb */ -void color_rgb_to_xy(uint8_t r, +static void color_rgb_to_xy_gamma_correction(uint8_t r, uint8_t g, uint8_t b, float *x_coordinate, float *y_coordinate, - uint8_t *brightness) + uint8_t *brightness, + bool gamma_correction) { float X, Y, Z; float x, y; @@ -63,18 +65,20 @@ void color_rgb_to_xy(uint8_t r, green /= 255.0f; blue /= 255.0f; - /* Apply a gamma correction to the RGB values, - which makes the color more vivid and more the - like the color displayed on the screen of your device. - This gamma correction is also applied to the screen - of your computer or phone, thus we need this to create - the same color on the light as on screen. */ - red = (red > 0.04045f) ? pow((red + 0.055f) / (1.0f + 0.055f), 2.4f) - : (red / 12.92f); - green = (green > 0.04045f) ? pow((green + 0.055f) / (1.0f + 0.055f), 2.4f) - : (green / 12.92f); - blue = (blue > 0.04045f) ? pow((blue + 0.055f) / (1.0f + 0.055f), 2.4f) - : (blue / 12.92f); + if (gamma_correction) { + /* Apply a gamma correction to the RGB values, + which makes the color more vivid and more the + like the color displayed on the screen of your device. + This gamma correction is also applied to the screen + of your computer or phone, thus we need this to create + the same color on the light as on screen. */ + red = (red > 0.04045f) ? pow((red + 0.055f) / (1.0f + 0.055f), 2.4f) + : (red / 12.92f); + green = (green > 0.04045f) ? pow((green + 0.055f) / (1.0f + 0.055f), 2.4f) + : (green / 12.92f); + blue = (blue > 0.04045f) ? pow((blue + 0.055f) / (1.0f + 0.055f), 2.4f) + : (blue / 12.92f); + } /* Convert the RGB values to XYZ using the Wide RGB D65 conversion formula */ @@ -107,6 +111,48 @@ void color_rgb_to_xy(uint8_t r, } } +/** + * @brief Convert sRGB to CIE xy + * @param r - R value of sRGB 0..255 + * @param g - G value of sRGB 0..255 + * @param b - B value of sRGB 0..255 + * @param x_coordinate - return x of CIE xy 0.0..1.0 + * @param y_coordinate - return y of CIE xy 0.0..1.0 + * @param brightness - return brightness of the CIE xy color 0..255 + * @note http://en.wikipedia.org/wiki/Srgb + */ +void color_rgb_to_xy(uint8_t r, + uint8_t g, + uint8_t b, + float *x_coordinate, + float *y_coordinate, + uint8_t *brightness) +{ + color_rgb_to_xy_gamma_correction(r, g, b, + x_coordinate, y_coordinate, brightness, false); +} + +/** + * @brief Convert sRGB to CIE xy, with gamma correction + * @param r - R value of sRGB 0..255 + * @param g - G value of sRGB 0..255 + * @param b - B value of sRGB 0..255 + * @param x_coordinate - return x of CIE xy 0.0..1.0 + * @param y_coordinate - return y of CIE xy 0.0..1.0 + * @param brightness - return brightness of the CIE xy color 0..255 + * @note http://en.wikipedia.org/wiki/Srgb + */ +void color_rgb_to_xy_gamma(uint8_t r, + uint8_t g, + uint8_t b, + float *x_coordinate, + float *y_coordinate, + uint8_t *brightness) +{ + color_rgb_to_xy_gamma_correction(r, g, b, + x_coordinate, y_coordinate, brightness, true); +} + /** * @brief Convert sRGB from CIE xy and brightness * @param red - return R value of sRGB @@ -115,14 +161,16 @@ void color_rgb_to_xy(uint8_t r, * @param x_coordinate - x of CIE xy * @param y_coordinate - y of CIE xy * @param brightness - brightness of the CIE xy color + * @param gamma_correction - true if gamma correction is needed * @note http://en.wikipedia.org/wiki/Srgb */ -void color_rgb_from_xy(uint8_t *red, +static void color_rgb_from_xy_gamma_correction(uint8_t *red, uint8_t *green, uint8_t *blue, float x_coordinate, float y_coordinate, - uint8_t brightness) + uint8_t brightness, + bool gamma_correction) { float r, g, b; float x, y, z, X, Y, Z; @@ -142,13 +190,15 @@ void color_rgb_from_xy(uint8_t *red, g = -X * 0.5217933f + Y * 1.4472381f + Z * 0.0677227f; b = X * 0.0349342f - Y * 0.0968930f + Z * 1.2884099f; - /* Apply reverse gamma correction */ - r = r <= 0.0031308f ? 12.92f * r - : (1.0f + 0.055f) * pow(r, (1.0f / 2.4f)) - 0.055f; - g = g <= 0.0031308f ? 12.92f * g - : (1.0f + 0.055f) * pow(g, (1.0f / 2.4f)) - 0.055f; - b = b <= 0.0031308f ? 12.92f * b - : (1.0f + 0.055f) * pow(b, (1.0f / 2.4f)) - 0.055f; + if (gamma_correction) { + /* Apply reverse gamma correction */ + r = r <= 0.0031308f ? 12.92f * r + : (1.0f + 0.055f) * pow(r, (1.0f / 2.4f)) - 0.055f; + g = g <= 0.0031308f ? 12.92f * g + : (1.0f + 0.055f) * pow(g, (1.0f / 2.4f)) - 0.055f; + b = b <= 0.0031308f ? 12.92f * b + : (1.0f + 0.055f) * pow(b, (1.0f / 2.4f)) - 0.055f; + } /* Convert the RGB values to your color object The rgb values from the above formulas are @@ -171,6 +221,48 @@ void color_rgb_from_xy(uint8_t *red, } } +/** + * @brief Convert sRGB from CIE xy and brightness + * @param red - return R value of sRGB + * @param green - return G value of sRGB + * @param blue - return B value of sRGB + * @param x_coordinate - x of CIE xy + * @param y_coordinate - y of CIE xy + * @param brightness - brightness of the CIE xy color + * @note http://en.wikipedia.org/wiki/Srgb + */ +void color_rgb_from_xy(uint8_t *red, + uint8_t *green, + uint8_t *blue, + float x_coordinate, + float y_coordinate, + uint8_t brightness) +{ + color_rgb_from_xy_gamma_correction(red, green, blue, + x_coordinate, y_coordinate, brightness, false); +} + +/** + * @brief Convert sRGB from CIE xy and brightness, with gamma correction + * @param red - return R value of sRGB + * @param green - return G value of sRGB + * @param blue - return B value of sRGB + * @param x_coordinate - x of CIE xy + * @param y_coordinate - y of CIE xy + * @param brightness - brightness of the CIE xy color + * @note http://en.wikipedia.org/wiki/Srgb + */ +void color_rgb_from_xy_gamma(uint8_t *red, + uint8_t *green, + uint8_t *blue, + float x_coordinate, + float y_coordinate, + uint8_t brightness) +{ + color_rgb_from_xy_gamma_correction(red, green, blue, + x_coordinate, y_coordinate, brightness, true); +} + /* table for converting RGB to and from ASCII color names */ struct css_color_rgb { const char *name; diff --git a/src/bacnet/basic/sys/color_rgb.h b/src/bacnet/basic/sys/color_rgb.h index 2e754f82..2052d76b 100644 --- a/src/bacnet/basic/sys/color_rgb.h +++ b/src/bacnet/basic/sys/color_rgb.h @@ -22,6 +22,13 @@ BACNET_STACK_EXPORT void color_rgb_from_xy(uint8_t *red, uint8_t *green, uint8_t *blue, float x_coordinate, float y_coordinate, uint8_t brightness); +BACNET_STACK_EXPORT +void color_rgb_to_xy_gamma(uint8_t r, uint8_t g, uint8_t b, + float *x_coordinate, float *y_coordinate, uint8_t *brightness); +BACNET_STACK_EXPORT +void color_rgb_from_xy_gamma(uint8_t *red, uint8_t *green, uint8_t *blue, + float x_coordinate, float y_coordinate, uint8_t brightness); + BACNET_STACK_EXPORT const char * color_rgb_to_ascii(uint8_t red, uint8_t green, uint8_t blue); BACNET_STACK_EXPORT diff --git a/src/bacnet/basic/sys/linear.c b/src/bacnet/basic/sys/linear.c new file mode 100644 index 00000000..62d6d1a6 --- /dev/null +++ b/src/bacnet/basic/sys/linear.c @@ -0,0 +1,128 @@ +/** +* @file +* @author Steve Karg +* @date 2011 +* @brief Performs linear interpolation using single precision floating +* point math or integer math, or a mixture of both. +* +* @section LICENSE +* +* Copyright (C) 2011 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. +* +* @section DESCRIPTION +* +* Performs linear interpolation using single precision floating +* point math or integer math, or a mixture of both. +* Linear interpolation is a method of constructing new data +* points within the range of a discrete set of known data points. +*/ +#include +#include +#include "linear.h" + +/** +* Linearly Interpolate the values between y1 and y3 based on x. +* +* @param x1 - first x value, where x1 <= x2 <= x3 or x1 >= x2 >= x3 +* @param x2 - intermediate x value, where x1 <= x2 <= x3 or x1 >= x2 >= x3 +* @param x3 - last x value, where x1 <= x2 <= x3 or x1 >= x2 >= x3 +* @param y1 - first y value, where y1 <= y2 <= y3 or y1 >= y2 >= y3 +* @param y3 - last y value, where y1 <= y2 <= y3 or y1 >= y2 >= y3 +* @return y2 - an intermediate y value y1 <= y2 <= y3 or y1 >= y2 >= y3 +* and the y value is linearly proportional to x1, x2, and x2. +*/ +float linear_interpolate(float x1, + float x2, + float x3, + float y1, + float y3) +{ + float y2; + + if (y3 > y1) { + y2 = y1 + (((x2 - x1) * (y3 - y1)) / (x3 - x1)); + } else { + y2 = y1 - (((x2 - x1) * (y1 - y3)) / (x3 - x1)); + } + + return y2; +} + +/** +* Linearly Interpolate the values between y1 and y3 based on x +* and round up the result. Useful for integer interpolation. +* +* @param x1 - first x value, where x1 <= x2 <= x3 or x1 >= x2 >= x3 +* @param x2 - intermediate x value, where x1 <= x2 <= x3 or x1 >= x2 >= x3 +* @param x3 - last x value, where x1 <= x2 <= x3 or x1 >= x2 >= x3 +* @param y1 - first y value, where y1 <= y2 <= y3 or y1 >= y2 >= y3 +* @param y3 - last y value, where y1 <= y2 <= y3 or y1 >= y2 >= y3 +* @return y2 - an intermediate y value y1 <= y2 <= y3 or y1 >= y2 >= y3 +* and the y value is linearly proportional to x1, x2, and x2. +*/ +float linear_interpolate_round(float x1, + float x2, + float x3, + float y1, + float y3) +{ + float y2; + + y2 = linear_interpolate(x1, x2, x3, y1, y3); + /* round away from zero */ + if (y2 > 0.0) { + y2 += 0.5; + } else if (y2 < 0.0) { + y2 -= 0.5; + } + + return y2; +} + +/** +* Linearly Interpolate the values between y1 and y3 based on x +* using integer math. +* +* @param x1 - first x value, where x1 <= x2 <= x3 or x1 >= x2 >= x3 +* @param x2 - intermediate x value, where x1 <= x2 <= x3 or x1 >= x2 >= x3 +* @param x3 - last x value, where x1 <= x2 <= x3 or x1 >= x2 >= x3 +* @param y1 - first y value, where y1 <= y2 <= y3 or y1 >= y2 >= y3 +* @param y3 - last y value, where y1 <= y2 <= y3 or y1 >= y2 >= y3 +* @return y2 - an intermediate y value y1 <= y2 <= y3 or y1 >= y2 >= y3 +* and the y value is linearly proportional to x1, x2, and x2. +*/ +long linear_interpolate_int(long x1, + long x2, + long x3, + long y1, + long y3) +{ + long y2; + + if (y3 > y1) { + y2 = y1 + (((x2 - x1) * (y3 - y1)) / (x3 - x1)); + } else { + y2 = y1 - (((x2 - x1) * (y1 - y3)) / (x3 - x1)); + } + + return y2; +} diff --git a/src/bacnet/basic/sys/linear.h b/src/bacnet/basic/sys/linear.h new file mode 100644 index 00000000..1bf4c0e1 --- /dev/null +++ b/src/bacnet/basic/sys/linear.h @@ -0,0 +1,34 @@ +/** +* @file +* @author Steve Karg +* @date 2011 +*/ +#ifndef LINEAR_H +#define LINEAR_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + float linear_interpolate(float x1, + float x2, + float x3, + float y1, + float y3); + + float linear_interpolate_round(float x1, + float x2, + float x3, + float y1, + float y3); + + long linear_interpolate_int(long x1, + long x2, + long x3, + long y1, + long y3); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/lighting.c b/src/bacnet/lighting.c index 0bf09f86..ed1d23bf 100644 --- a/src/bacnet/lighting.c +++ b/src/bacnet/lighting.c @@ -25,6 +25,18 @@ /** * Encodes into bytes from the lighting-command structure * + * BACnetLightingCommand ::= SEQUENCE { + * operation [0] BACnetLightingOperation, + * target-level [1] REAL (0.0..100.0) OPTIONAL, + * ramp-rate [2] REAL (0.1..100.0) OPTIONAL, + * step-increment [3] REAL (0.1..100.0) OPTIONAL, + * fade-time [4] Unsigned (100..86400000) OPTIONAL, + * priority [5] Unsigned (1..16) OPTIONAL + * } + * -- Note that the combination of level, ramp-rate, + * -- step-increment, and fade-time fields is + * -- dependent on the specific lighting operation. + * * @param apdu - buffer to hold the bytes, or null for length * @param value - lighting command value to encode * @@ -86,6 +98,18 @@ int lighting_command_encode(uint8_t *apdu, BACNET_LIGHTING_COMMAND *data) * Encodes into bytes from the lighting-command structure * a context tagged chunk (opening and closing tag) * + * BACnetLightingCommand ::= SEQUENCE { + * operation [0] BACnetLightingOperation, + * target-level [1] REAL (0.0..100.0) OPTIONAL, + * ramp-rate [2] REAL (0.1..100.0) OPTIONAL, + * step-increment [3] REAL (0.1..100.0) OPTIONAL, + * fade-time [4] Unsigned (100..86400000) OPTIONAL, + * priority [5] Unsigned (1..16) OPTIONAL + * } + * -- Note that the combination of level, ramp-rate, + * -- step-increment, and fade-time fields is + * -- dependent on the specific lighting operation. + * * @param apdu - buffer to hold the bytes * @param tag_number - tag number to encode this chunk * @param value - lighting command value to encode @@ -114,58 +138,64 @@ int lighting_command_encode_context( /** * Decodes from bytes into the lighting-command structure * + * BACnetLightingCommand ::= SEQUENCE { + * operation [0] BACnetLightingOperation, + * target-level [1] REAL (0.0..100.0) OPTIONAL, + * ramp-rate [2] REAL (0.1..100.0) OPTIONAL, + * step-increment [3] REAL (0.1..100.0) OPTIONAL, + * fade-time [4] Unsigned (100..86400000) OPTIONAL, + * priority [5] Unsigned (1..16) OPTIONAL + * } + * -- Note that the combination of level, ramp-rate, + * -- step-increment, and fade-time fields is + * -- dependent on the specific lighting operation. + * * @param apdu - buffer to hold the bytes - * @param apdu_max_len - number of bytes in the buffer to decode + * @param apdu_size - number of bytes in the buffer to decode * @param value - lighting command value to place the decoded values * - * @return number of bytes decoded + * @return number of bytes decoded, or BACNET_STATUS_ERROR */ int lighting_command_decode( - uint8_t *apdu, unsigned apdu_max_len, BACNET_LIGHTING_COMMAND *data) + uint8_t *apdu, unsigned apdu_size, BACNET_LIGHTING_COMMAND *data) { int len = 0; int apdu_len = 0; - uint8_t tag_number = 0; - uint32_t len_value_type = 0; uint32_t enum_value = 0; BACNET_UNSIGNED_INTEGER unsigned_value = 0; BACNET_LIGHTING_OPERATION operation = BACNET_LIGHTS_NONE; float real_value = 0.0; /* check for value pointers */ - if (apdu_max_len) { - /* Tag 0: operation */ - if (!decode_is_context_tag(&apdu[apdu_len], 0)) { + if (!apdu) { + return BACNET_STATUS_ERROR; + } + /* operation [0] BACnetLightingOperation */ + len = bacnet_enumerated_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 0, &enum_value); + if (len > 0) { + apdu_len += len; + if (unsigned_value <= BACNET_LIGHTS_PROPRIETARY_LAST) { + if (data) { + data->operation = (BACNET_LIGHTING_OPERATION)enum_value; + } + } else { return BACNET_STATUS_ERROR; } - len = decode_tag_number_and_value( - &apdu[apdu_len], &tag_number, &len_value_type); - apdu_len += len; - len = decode_enumerated(&apdu[apdu_len], len_value_type, &enum_value); - if (len > 0) { - if (unsigned_value <= BACNET_LIGHTS_PROPRIETARY_LAST) { - if (data) { - data->operation = (BACNET_LIGHTING_OPERATION)enum_value; - } - } else { - return BACNET_STATUS_ERROR; - } - } - apdu_len += len; + } else { + return BACNET_STATUS_ERROR; } switch (operation) { case BACNET_LIGHTS_NONE: break; case BACNET_LIGHTS_FADE_TO: - if ((apdu_max_len - apdu_len) == 0) { + if ((apdu_size - apdu_len) == 0) { return BACNET_STATUS_REJECT; } - /* Tag 1: target-level */ - if (decode_is_context_tag(&apdu[apdu_len], 1)) { - len = decode_tag_number_and_value( - &apdu[apdu_len], &tag_number, &len_value_type); - apdu_len += len; - len = decode_real(&apdu[apdu_len], &real_value); + /* target-level [1] REAL (0.0..100.0) OPTIONAL */ + len = bacnet_real_context_decode( + &apdu[apdu_len], apdu_size, 1, &real_value); + if (len > 0) { apdu_len += len; if (data) { data->target_level = real_value; @@ -176,33 +206,29 @@ int lighting_command_decode( data->use_target_level = false; } } - if ((apdu_max_len - apdu_len) != 0) { + if ((apdu_size - apdu_len) > 0) { /* Tag 4: fade-time - OPTIONAL */ - if (decode_is_context_tag(&apdu[apdu_len], 4)) { - len = decode_tag_number_and_value( - &apdu[apdu_len], &tag_number, &len_value_type); - apdu_len += len; - len = decode_unsigned( - &apdu[apdu_len], len_value_type, &unsigned_value); + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size, 4, &unsigned_value); + if (len > 0) { apdu_len += len; if (data) { data->fade_time = (uint32_t)unsigned_value; data->use_fade_time = true; } } else { - if (data) { - data->use_fade_time = false; - } + return BACNET_STATUS_ERROR; + } + } else { + if (data) { + data->use_fade_time = false; } } - if ((apdu_max_len - apdu_len) != 0) { - /* Tag 5: priority - OPTIONAL */ - if (decode_is_context_tag(&apdu[apdu_len], 4)) { - len = decode_tag_number_and_value( - &apdu[apdu_len], &tag_number, &len_value_type); - apdu_len += len; - len = decode_unsigned( - &apdu[apdu_len], len_value_type, &unsigned_value); + if ((apdu_size - apdu_len) > 0) { + /* priority [5] Unsigned (1..16) OPTIONAL */ + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size, 5, &unsigned_value); + if (len > 0) { apdu_len += len; if (data) { data->priority = (uint8_t)unsigned_value; @@ -213,18 +239,20 @@ int lighting_command_decode( data->use_priority = false; } } + } else { + if (data) { + data->use_priority = false; + } } break; case BACNET_LIGHTS_RAMP_TO: - if ((apdu_max_len - apdu_len) == 0) { + if ((apdu_size - apdu_len) == 0) { return BACNET_STATUS_REJECT; } - /* Tag 1: target-level */ - if (decode_is_context_tag(&apdu[apdu_len], 1)) { - len = decode_tag_number_and_value( - &apdu[apdu_len], &tag_number, &len_value_type); - apdu_len += len; - len = decode_real(&apdu[apdu_len], &real_value); + /* target-level [1] REAL (0.0..100.0) OPTIONAL */ + len = bacnet_real_context_decode( + &apdu[apdu_len], apdu_size, 1, &real_value); + if (len > 0) { apdu_len += len; if (data) { data->target_level = real_value; @@ -235,13 +263,11 @@ int lighting_command_decode( data->use_target_level = false; } } - if ((apdu_max_len - apdu_len) != 0) { - /* Tag 2: ramp-rate - OPTIONAL */ - if (decode_is_context_tag(&apdu[apdu_len], 2)) { - len = decode_tag_number_and_value( - &apdu[apdu_len], &tag_number, &len_value_type); - apdu_len += len; - len = decode_real(&apdu[apdu_len], &real_value); + if ((apdu_size - apdu_len) > 0) { + /* ramp-rate [2] REAL (0.1..100.0) OPTIONAL */ + len = bacnet_real_context_decode( + &apdu[apdu_len], apdu_size, 2, &real_value); + if (len > 0) { apdu_len += len; if (data) { data->ramp_rate = real_value; @@ -252,15 +278,16 @@ int lighting_command_decode( data->use_ramp_rate = false; } } + } else { + if (data) { + data->use_ramp_rate = false; + } } - if ((apdu_max_len - apdu_len) != 0) { - /* Tag 5: priority - OPTIONAL */ - if (decode_is_context_tag(&apdu[apdu_len], 4)) { - len = decode_tag_number_and_value( - &apdu[apdu_len], &tag_number, &len_value_type); - apdu_len += len; - len = decode_unsigned( - &apdu[apdu_len], len_value_type, &unsigned_value); + if ((apdu_size - apdu_len) > 0) { + /* priority [5] Unsigned (1..16) OPTIONAL */ + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size, 5, &unsigned_value); + if (len > 0) { apdu_len += len; if (data) { data->priority = (uint8_t)unsigned_value; @@ -271,19 +298,21 @@ int lighting_command_decode( data->use_priority = false; } } + } else { + if (data) { + data->use_priority = false; + } } break; case BACNET_LIGHTS_STEP_UP: case BACNET_LIGHTS_STEP_DOWN: case BACNET_LIGHTS_STEP_ON: case BACNET_LIGHTS_STEP_OFF: - if ((apdu_max_len - apdu_len) != 0) { - /* Tag 3: step-increment - OPTIONAL */ - if (decode_is_context_tag(&apdu[apdu_len], 3)) { - len = decode_tag_number_and_value( - &apdu[apdu_len], &tag_number, &len_value_type); - apdu_len += len; - len = decode_real(&apdu[apdu_len], &real_value); + if ((apdu_size - apdu_len) > 0) { + /* step-increment [3] REAL (0.1..100.0) OPTIONAL */ + len = bacnet_real_context_decode( + &apdu[apdu_len], apdu_size, 3, &real_value); + if (len > 0) { apdu_len += len; if (data) { data->step_increment = real_value; @@ -294,15 +323,16 @@ int lighting_command_decode( data->use_step_increment = false; } } + } else { + if (data) { + data->use_step_increment = false; + } } - if ((apdu_max_len - apdu_len) != 0) { - /* Tag 5: priority - OPTIONAL */ - if (decode_is_context_tag(&apdu[apdu_len], 4)) { - len = decode_tag_number_and_value( - &apdu[apdu_len], &tag_number, &len_value_type); - apdu_len += len; - len = decode_unsigned( - &apdu[apdu_len], len_value_type, &unsigned_value); + if ((apdu_size - apdu_len) > 0) { + /* priority [5] Unsigned (1..16) OPTIONAL */ + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size, 5, &unsigned_value); + if (len > 0) { apdu_len += len; if (data) { data->priority = (uint8_t)unsigned_value; @@ -313,20 +343,21 @@ int lighting_command_decode( data->use_priority = false; } } + } else { + if (data) { + data->use_priority = false; + } } break; case BACNET_LIGHTS_WARN: case BACNET_LIGHTS_WARN_OFF: case BACNET_LIGHTS_WARN_RELINQUISH: case BACNET_LIGHTS_STOP: - if ((apdu_max_len - apdu_len) != 0) { - /* Tag 5: priority - OPTIONAL */ - if (decode_is_context_tag(&apdu[apdu_len], 4)) { - len = decode_tag_number_and_value( - &apdu[apdu_len], &tag_number, &len_value_type); - apdu_len += len; - len = decode_unsigned( - &apdu[apdu_len], len_value_type, &unsigned_value); + if ((apdu_size - apdu_len) > 0) { + /* priority [5] Unsigned (1..16) OPTIONAL */ + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size, 5, &unsigned_value); + if (len > 0) { apdu_len += len; if (data) { data->priority = (uint8_t)unsigned_value; @@ -337,6 +368,10 @@ int lighting_command_decode( data->use_priority = false; } } + } else { + if (data) { + data->use_priority = false; + } } break; default: @@ -437,20 +472,16 @@ int xy_color_encode(uint8_t *apdu, BACNET_XY_COLOR *value) { int len = 0; int apdu_len = 0; - uint8_t *apdu_offset = NULL; if (value) { /* x-coordinate REAL */ - if (apdu) { - apdu_offset = &apdu[apdu_len]; - } - len = encode_bacnet_real(value->x_coordinate, apdu_offset); + len = encode_application_real(apdu, value->x_coordinate); apdu_len += len; - /* y-coordinate REAL */ if (apdu) { - apdu_offset = &apdu[apdu_len]; + apdu += len; } - len = encode_bacnet_real(value->y_coordinate, apdu_offset); + /* y-coordinate REAL */ + len = encode_application_real(apdu, value->y_coordinate); apdu_len += len; } @@ -497,7 +528,7 @@ int xy_color_context_encode( * @param apdu_size - the size of the data buffer * @param value - decoded BACnetxyColor, if decoded * - * @return the number of apdu bytes consumed + * @return the number of apdu bytes consumed, or BACNET_STATUS_ERROR */ int xy_color_decode(uint8_t *apdu, uint32_t apdu_size, BACNET_XY_COLOR *value) { @@ -505,21 +536,26 @@ int xy_color_decode(uint8_t *apdu, uint32_t apdu_size, BACNET_XY_COLOR *value) int len = 0; int apdu_len = 0; - if (apdu && value && (apdu_size >= 8)) { - /* each REAL is encoded in 4 octets */ - len = decode_real(&apdu[0], &real_value); - if (len == 4) { + if (apdu) { + len = bacnet_real_application_decode( + &apdu[apdu_len], apdu_size, &real_value); + if (len > 0) { if (value) { value->x_coordinate = real_value; } apdu_len += len; + } else { + return BACNET_STATUS_ERROR; } - len = decode_real(&apdu[4], &real_value); - if (len == 4) { + len = bacnet_real_application_decode( + &apdu[apdu_len], apdu_size, &real_value); + if (len > 0) { if (value) { value->y_coordinate = real_value; } apdu_len += len; + } else { + return BACNET_STATUS_ERROR; } } @@ -533,7 +569,7 @@ int xy_color_decode(uint8_t *apdu, uint32_t apdu_size, BACNET_XY_COLOR *value) * @param apdu_size - the size of the data buffer * @param value - decoded BACnetxyColor, if decoded * - * @return the number of apdu bytes consumed + * @return the number of apdu bytes consumed, or BACNET_STATUS_ERROR */ int xy_color_context_decode(uint8_t *apdu, uint32_t apdu_size, @@ -541,33 +577,45 @@ int xy_color_context_decode(uint8_t *apdu, BACNET_XY_COLOR *value) { int len = 0; - int rlen = 0; int apdu_len = 0; BACNET_XY_COLOR color = { 0.0, 0.0 }; - if (apdu_size > 0) { - if (decode_is_opening_tag_number(&apdu[apdu_len], tag_number)) { - apdu_len += 1; - len = - xy_color_decode(&apdu[apdu_len], apdu_size - apdu_len, &color); - if (len > 0) { - apdu_len += len; - if (value) { - value->x_coordinate = color.x_coordinate; - value->y_coordinate = color.y_coordinate; - } - if ((apdu_size - apdu_len) > 0) { - if (decode_is_closing_tag_number( - &apdu[apdu_len], tag_number)) { - apdu_len += 1; - rlen = apdu_len; - } - } - } - } + if (!bacnet_is_opening_tag_number( + &apdu[apdu_len], apdu_size - apdu_len, tag_number, &len)) { + return BACNET_STATUS_ERROR; } + apdu_len += len; + len = xy_color_decode(&apdu[apdu_len], apdu_size - apdu_len, &color); + if (len > 0) { + apdu_len += len; + if (value) { + value->x_coordinate = color.x_coordinate; + value->y_coordinate = color.y_coordinate; + } + } else { + return BACNET_STATUS_ERROR; + } + if (!bacnet_is_closing_tag_number( + &apdu[apdu_len], apdu_size - apdu_len, tag_number, &len)) { + return BACNET_STATUS_ERROR; + } + apdu_len += len; - return rlen; + return apdu_len; +} + +/** + * @brief Set the BACnetxyColor complex data for x and y coordinates + * @param dst - destination BACNET_XY_COLOR structure + * @param x - x coordinate + * @param x - y coordinate + */ +void xy_color_set(BACNET_XY_COLOR *dst, float x, float y) +{ + if (dst) { + dst->x_coordinate = x; + dst->y_coordinate = y; + } } /** @@ -609,6 +657,64 @@ bool xy_color_same(BACNET_XY_COLOR *value1, BACNET_XY_COLOR *value2) return status; } +/** + * Convert BACnetXYcolor to ASCII for printing + * + * @param value - struct to convert to ASCII + * @param buf - ASCII output buffer (or NULL for size) + * @param buf_size - ASCII output buffer capacity (or 0 for size) + * + * @return the number of characters which would be generated for the given + * input, excluding the trailing null. negative is returned if the + * capacity was not sufficient. + * + * @note buf and buf_size may be null and zero to return only the size + */ +int xy_color_to_ascii( + const BACNET_XY_COLOR *value, + char *buf, + size_t buf_size) +{ + return snprintf(buf, buf_size, "(%f,%f)", value->x_coordinate, + value->x_coordinate); +} + +/** + * @brief Parse an ASCII string for a BACnetXYcolor + * @param mac [out] BACNET_XY_COLOR structure to store the results + * @param arg [in] nul terminated ASCII string to parse + * @return true if the address was parsed + */ +bool xy_color_from_ascii(BACNET_XY_COLOR *value, const char *argv) +{ + bool status = false; + int count; + float x,y; + + count = sscanf(argv, "%f,%f", &x, &y); + if (count == 2) { + value->x_coordinate = x; + value->y_coordinate = y; + status = true; + } else { +#if defined(BACAPP_COLOR_RGB_CONVERSION_ENABLED) + uint8_t red, green, blue; + unsigned rgb_max; + + rgb_max = color_rgb_count(); + count = color_rgb_from_ascii(&red, &green, &blue, argv); + if (count < rgb_max) { + color_rgb_to_xy(red, green, blue, &x, &y, NULL); + value->x_coordinate = x; + value->y_coordinate = y; + status = true; + } +#endif + } + + return status; +} + /** * @brief Encode a BACnetColorCommand complex data type * @@ -629,82 +735,62 @@ int color_command_encode(uint8_t *apdu, BACNET_COLOR_COMMAND *value) { int len = 0; int apdu_len = 0; - uint8_t *apdu_offset = NULL; BACNET_UNSIGNED_INTEGER unsigned_value; if (value) { /* operation [0] BACnetColorOperation */ - if (apdu) { - apdu_offset = &apdu[apdu_len]; - } - len = encode_context_enumerated(apdu_offset, 0, value->operation); + len = encode_context_enumerated(apdu, 0, value->operation); apdu_len += len; + if (apdu) { + apdu += len; + } switch (value->operation) { case BACNET_COLOR_OPERATION_NONE: break; case BACNET_COLOR_OPERATION_FADE_TO_COLOR: - if (apdu) { - apdu_offset = &apdu[apdu_len]; - } /* target-color [1] BACnetxyColor */ - len = xy_color_context_encode( - apdu_offset, 1, &value->target.color); + len = xy_color_context_encode(apdu, 1, &value->target.color); apdu_len += len; + if (apdu) { + apdu += len; + } if ((value->transit.fade_time >= BACNET_COLOR_FADE_TIME_MIN) && (value->transit.fade_time <= BACNET_COLOR_FADE_TIME_MAX)) { - /* fade-time [3] Unsigned (100.. 86400000) */ + /* fade-time [3] Unsigned (100.. 86400000) OPTIONAL */ unsigned_value = value->transit.fade_time; - if (apdu) { - apdu_offset = &apdu[apdu_len]; - } - len = - encode_context_unsigned(apdu_offset, 3, unsigned_value); + len = encode_context_unsigned(apdu, 3, unsigned_value); apdu_len += len; } break; case BACNET_COLOR_OPERATION_FADE_TO_CCT: - if (apdu) { - apdu_offset = &apdu[apdu_len]; - } /* target-color-temperature [2] Unsigned */ unsigned_value = value->target.color_temperature; - if (apdu) { - apdu_offset = &apdu[apdu_len]; - } - len = encode_context_unsigned(apdu_offset, 2, unsigned_value); + len = encode_context_unsigned(apdu, 2, unsigned_value); apdu_len += len; + if (apdu) { + apdu += len; + } if ((value->transit.fade_time >= BACNET_COLOR_FADE_TIME_MIN) && (value->transit.fade_time <= BACNET_COLOR_FADE_TIME_MAX)) { - /* fade-time [3] Unsigned (100.. 86400000) */ + /* fade-time [3] Unsigned (100.. 86400000) OPTIONAL */ unsigned_value = value->transit.fade_time; - if (apdu) { - apdu_offset = &apdu[apdu_len]; - } - len = - encode_context_unsigned(apdu_offset, 3, unsigned_value); + len = encode_context_unsigned(apdu, 3, unsigned_value); apdu_len += len; } break; case BACNET_COLOR_OPERATION_RAMP_TO_CCT: - if (apdu) { - apdu_offset = &apdu[apdu_len]; - } /* target-color-temperature [2] Unsigned */ unsigned_value = value->target.color_temperature; - if (apdu) { - apdu_offset = &apdu[apdu_len]; - } - len = encode_context_unsigned(apdu_offset, 2, unsigned_value); + len = encode_context_unsigned(apdu, 2, unsigned_value); apdu_len += len; + if (apdu) { + apdu += len; + } if ((value->transit.ramp_rate >= BACNET_COLOR_RAMP_RATE_MIN) && (value->transit.ramp_rate <= BACNET_COLOR_RAMP_RATE_MAX)) { - /* ramp-rate [4] Unsigned (1..30000) */ + /* ramp-rate [4] Unsigned (1..30000) OPTIONAL */ unsigned_value = value->transit.ramp_rate; - if (apdu) { - apdu_offset = &apdu[apdu_len]; - } - len = - encode_context_unsigned(apdu_offset, 4, unsigned_value); + len = encode_context_unsigned(apdu, 4, unsigned_value); apdu_len += len; } break; @@ -714,16 +800,9 @@ int color_command_encode(uint8_t *apdu, BACNET_COLOR_COMMAND *value) BACNET_COLOR_STEP_INCREMENT_MIN) && (value->transit.step_increment <= BACNET_COLOR_STEP_INCREMENT_MAX)) { - if (apdu) { - apdu_offset = &apdu[apdu_len]; - } - /* step-increment [5] Unsigned (1..30000) */ + /* step-increment [5] Unsigned (1..30000) OPTIONAL */ unsigned_value = value->transit.step_increment; - if (apdu) { - apdu_offset = &apdu[apdu_len]; - } - len = - encode_context_unsigned(apdu_offset, 5, unsigned_value); + len = encode_context_unsigned(apdu, 5, unsigned_value); apdu_len += len; } break; @@ -749,21 +828,19 @@ int color_command_context_encode( { int len = 0; int apdu_len = 0; - uint8_t *apdu_offset = NULL; if (value) { - apdu_offset = apdu; - len = encode_opening_tag(apdu_offset, tag_number); + len = encode_opening_tag(apdu, tag_number); apdu_len += len; if (apdu) { - apdu_offset = &apdu[apdu_len]; + apdu += len; } - len = color_command_encode(apdu_offset, value); + len = color_command_encode(apdu, value); apdu_len += len; if (apdu) { - apdu_offset = &apdu[apdu_len]; + apdu += len; } - len = encode_closing_tag(apdu_offset, tag_number); + len = encode_closing_tag(apdu, tag_number); apdu_len += len; } @@ -808,8 +885,10 @@ int color_command_decode(uint8_t *apdu, } /* operation [0] BACnetColorOperation */ len = bacnet_unsigned_context_decode( - apdu, apdu_size - apdu_len, 0, &unsigned_value); - if (len <= 0) { + &apdu[apdu_len], apdu_size - apdu_len, 0, &unsigned_value); + if (len > 0) { + apdu_len += len; + } else { if (len == 0) { if (error_code) { *error_code = ERROR_CODE_REJECT_INVALID_TAG; @@ -821,7 +900,6 @@ int color_command_decode(uint8_t *apdu, } return BACNET_STATUS_REJECT; } - apdu_len += len; if (unsigned_value >= BACNET_COLOR_OPERATION_MAX) { if (error_code) { *error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; @@ -837,30 +915,27 @@ int color_command_decode(uint8_t *apdu, break; case BACNET_COLOR_OPERATION_FADE_TO_COLOR: /* target-color [1] BACnetxyColor */ - if ((apdu_size - apdu_len) == 0) { - if (error_code) { - *error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER; - } - return BACNET_STATUS_REJECT; - } len = xy_color_context_decode( &apdu[apdu_len], apdu_size - apdu_len, 1, &color); - if (len == 0) { + if (len > 0) { + apdu_len += len; + } else { if (error_code) { *error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER; } return BACNET_STATUS_REJECT; } - apdu_len += len; if (value) { value->target.color.x_coordinate = color.x_coordinate; value->target.color.y_coordinate = color.y_coordinate; } - if ((apdu_size - apdu_len) != 0) { + if ((apdu_size - apdu_len) > 0) { /* fade-time [3] Unsigned (100.. 86400000) OPTIONAL */ len = bacnet_unsigned_context_decode( &apdu[apdu_len], apdu_size - apdu_len, 3, &unsigned_value); - if (len <= 0) { + if (len > 0) { + apdu_len += len; + } else { if (len == 0) { if (error_code) { *error_code = ERROR_CODE_REJECT_INVALID_TAG; @@ -873,7 +948,6 @@ int color_command_decode(uint8_t *apdu, } return BACNET_STATUS_REJECT; } - apdu_len += len; if ((unsigned_value < BACNET_COLOR_FADE_TIME_MIN) || (unsigned_value > BACNET_COLOR_FADE_TIME_MAX)) { if (error_code) { @@ -881,22 +955,20 @@ int color_command_decode(uint8_t *apdu, } return BACNET_STATUS_REJECT; } - if (value) { - value->transit.fade_time = unsigned_value; - } + } else { + unsigned_value = 0; + } + if (value) { + value->transit.fade_time = unsigned_value; } break; case BACNET_COLOR_OPERATION_FADE_TO_CCT: /* target-color-temperature [2] Unsigned */ - if ((apdu_size - apdu_len) == 0) { - if (error_code) { - *error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER; - } - return BACNET_STATUS_REJECT; - } len = bacnet_unsigned_context_decode( - apdu, apdu_size - apdu_len, 2, &unsigned_value); - if (len <= 0) { + &apdu[apdu_len], apdu_size - apdu_len, 2, &unsigned_value); + if (len > 0) { + apdu_len += len; + } else { if (len == 0) { if (error_code) { *error_code = ERROR_CODE_REJECT_INVALID_TAG; @@ -909,7 +981,6 @@ int color_command_decode(uint8_t *apdu, } return BACNET_STATUS_REJECT; } - apdu_len += len; if (unsigned_value > UINT16_MAX) { if (error_code) { *error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; @@ -919,11 +990,13 @@ int color_command_decode(uint8_t *apdu, if (value) { value->target.color_temperature = unsigned_value; } - if ((apdu_size - apdu_len) != 0) { + if ((apdu_size - apdu_len) > 0) { /* fade-time [3] Unsigned (100.. 86400000) OPTIONAL */ len = bacnet_unsigned_context_decode( &apdu[apdu_len], apdu_size - apdu_len, 3, &unsigned_value); - if (len <= 0) { + if (len > 0) { + apdu_len += len; + } else { if (len == 0) { if (error_code) { *error_code = ERROR_CODE_REJECT_INVALID_TAG; @@ -936,7 +1009,6 @@ int color_command_decode(uint8_t *apdu, } return BACNET_STATUS_REJECT; } - apdu_len += len; if ((unsigned_value < BACNET_COLOR_FADE_TIME_MIN) || (unsigned_value > BACNET_COLOR_FADE_TIME_MAX)) { if (error_code) { @@ -944,22 +1016,20 @@ int color_command_decode(uint8_t *apdu, } return BACNET_STATUS_REJECT; } - if (value) { - value->transit.fade_time = unsigned_value; - } + } else { + unsigned_value = 0; + } + if (value) { + value->transit.fade_time = unsigned_value; } break; case BACNET_COLOR_OPERATION_RAMP_TO_CCT: /* target-color-temperature [2] Unsigned */ - if ((apdu_size - apdu_len) == 0) { - if (error_code) { - *error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER; - } - return BACNET_STATUS_REJECT; - } len = bacnet_unsigned_context_decode( - apdu, apdu_size - apdu_len, 2, &unsigned_value); - if (len <= 0) { + &apdu[apdu_len], apdu_size - apdu_len, 2, &unsigned_value); + if (len > 0) { + apdu_len += len; + } else { if (len == 0) { if (error_code) { *error_code = ERROR_CODE_REJECT_INVALID_TAG; @@ -972,7 +1042,6 @@ int color_command_decode(uint8_t *apdu, } return BACNET_STATUS_REJECT; } - apdu_len += len; if (unsigned_value > UINT16_MAX) { if (error_code) { *error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; @@ -982,11 +1051,13 @@ int color_command_decode(uint8_t *apdu, if (value) { value->target.color_temperature = unsigned_value; } - if ((apdu_size - apdu_len) != 0) { - /* ramp-rate [4] Unsigned (1..30000) */ + if ((apdu_size - apdu_len) > 0) { + /* ramp-rate [4] Unsigned (1..30000) OPTIONAL */ len = bacnet_unsigned_context_decode( &apdu[apdu_len], apdu_size - apdu_len, 4, &unsigned_value); - if (len <= 0) { + if (len > 0) { + apdu_len += len; + } else { if (len == 0) { if (error_code) { *error_code = ERROR_CODE_REJECT_INVALID_TAG; @@ -999,7 +1070,6 @@ int color_command_decode(uint8_t *apdu, } return BACNET_STATUS_REJECT; } - apdu_len += len; if ((unsigned_value < BACNET_COLOR_RAMP_RATE_MIN) || (unsigned_value > BACNET_COLOR_RAMP_RATE_MAX)) { if (error_code) { @@ -1007,42 +1077,43 @@ int color_command_decode(uint8_t *apdu, } return BACNET_STATUS_REJECT; } - if (value) { - value->transit.ramp_rate = unsigned_value; - } + } else { + unsigned_value = 0; + } + if (value) { + value->transit.ramp_rate = unsigned_value; } break; case BACNET_COLOR_OPERATION_STEP_UP_CCT: case BACNET_COLOR_OPERATION_STEP_DOWN_CCT: - /* step-increment [5] Unsigned (1..30000) */ - if ((apdu_size - apdu_len) == 0) { - if (error_code) { - *error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER; - } - return BACNET_STATUS_REJECT; - } - len = bacnet_unsigned_context_decode( - apdu, apdu_size - apdu_len, 3, &unsigned_value); - if (len <= 0) { - if (len == 0) { - if (error_code) { - *error_code = ERROR_CODE_REJECT_INVALID_TAG; - } + if ((apdu_size - apdu_len) > 0) { + /* step-increment [5] Unsigned (1..30000) OPTIONAL */ + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 5, &unsigned_value); + if (len > 0) { + apdu_len += len; } else { - if (error_code) { - *error_code = - ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER; + if (len == 0) { + if (error_code) { + *error_code = ERROR_CODE_REJECT_INVALID_TAG; + } + } else { + if (error_code) { + *error_code = + ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER; + } } + return BACNET_STATUS_REJECT; } - return BACNET_STATUS_REJECT; - } - apdu_len += len; - if ((unsigned_value < BACNET_COLOR_STEP_INCREMENT_MIN) || - (unsigned_value > BACNET_COLOR_STEP_INCREMENT_MAX)) { - if (error_code) { - *error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + if ((unsigned_value < BACNET_COLOR_STEP_INCREMENT_MIN) || + (unsigned_value > BACNET_COLOR_STEP_INCREMENT_MAX)) { + if (error_code) { + *error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + } + return BACNET_STATUS_REJECT; } - return BACNET_STATUS_REJECT; + } else { + unsigned_value = 0; } if (value) { value->transit.step_increment = unsigned_value; diff --git a/src/bacnet/lighting.h b/src/bacnet/lighting.h index 2b9f83a8..caf3f528 100644 --- a/src/bacnet/lighting.h +++ b/src/bacnet/lighting.h @@ -86,6 +86,9 @@ typedef struct BACnetColorCommand { #define BACNET_COLOR_STEP_INCREMENT_MIN 1ul #define BACNET_COLOR_STEP_INCREMENT_MAX 30000ul +#define BACNET_COLOR_TEMPERATURE_MIN 1000ul +#define BACNET_COLOR_TEMPERATURE_MAX 30000ul + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -140,6 +143,20 @@ extern "C" { bool xy_color_same( BACNET_XY_COLOR *value1, BACNET_XY_COLOR *value2); + BACNET_STACK_EXPORT + void xy_color_set( + BACNET_XY_COLOR *dst, + float x, + float y); + BACNET_STACK_EXPORT + int xy_color_to_ascii( + const BACNET_XY_COLOR *value, + char *buf, + size_t buf_size); + BACNET_STACK_EXPORT + bool xy_color_from_ascii( + BACNET_XY_COLOR *value, + const char *arg); BACNET_STACK_EXPORT int color_command_encode( diff --git a/src/bacnet/wp.c b/src/bacnet/wp.c index 2195539e..6cfc742c 100644 --- a/src/bacnet/wp.c +++ b/src/bacnet/wp.c @@ -66,7 +66,6 @@ int wp_encode_apdu( { int apdu_len = 0; /* total length of the apdu, return value */ int len = 0; /* total length of the apdu, return value */ - int imax = 0; /* maximum application data length */ if (!wpdata) { return BACNET_STATUS_ERROR; @@ -155,8 +154,6 @@ int wp_decode_service_request( { int len = 0; int apdu_len = 0; - int tag_len = 0; - uint8_t tag_number = 0; uint32_t instance = 0; BACNET_OBJECT_TYPE type = OBJECT_NONE; /* for decoding */ uint32_t property = 0; /* for decoding */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 56417dfc..b3da4f14 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -136,6 +136,7 @@ list(APPEND testdirs bacnet/basic/sys/fifo bacnet/basic/sys/filename bacnet/basic/sys/keylist + bacnet/basic/sys/linear bacnet/basic/sys/ringbuf bacnet/basic/sys/sbuf ) diff --git a/test/bacnet/basic/object/channel/CMakeLists.txt b/test/bacnet/basic/object/channel/CMakeLists.txt index a740efbf..2cc35733 100644 --- a/test/bacnet/basic/object/channel/CMakeLists.txt +++ b/test/bacnet/basic/object/channel/CMakeLists.txt @@ -45,8 +45,9 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bactext.c ${SRC_DIR}/bacnet/bactimevalue.c ${SRC_DIR}/bacnet/basic/sys/bigend.c - ${SRC_DIR}/bacnet/datetime.c ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/datetime.c ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c diff --git a/test/bacnet/basic/object/channel/src/main.c b/test/bacnet/basic/object/channel/src/main.c index d0487e71..87304816 100644 --- a/test/bacnet/basic/object/channel/src/main.c +++ b/test/bacnet/basic/object/channel/src/main.c @@ -24,22 +24,30 @@ static void test_Channel_ReadProperty(void) uint8_t apdu[MAX_APDU] = { 0 }; int len = 0; int test_len = 0; - BACNET_READ_PROPERTY_DATA rpdata; - /* for decode value data */ - BACNET_APPLICATION_DATA_VALUE value; + BACNET_READ_PROPERTY_DATA rpdata = { 0 }; + BACNET_APPLICATION_DATA_VALUE value = { 0 }; + BACNET_WRITE_PROPERTY_DATA wpdata = { 0 }; + const uint32_t instance = 123; const int *pRequired = NULL; const int *pOptional = NULL; const int *pProprietary = NULL; unsigned count = 0; bool status = false; + unsigned index; Channel_Init(); + Channel_Create(instance); + status = Channel_Valid_Instance(instance); + zassert_true(status, NULL); + index = Channel_Instance_To_Index(instance); + zassert_equal(index, 0, NULL); + count = Channel_Count(); zassert_true(count > 0, NULL); rpdata.application_data = &apdu[0]; rpdata.application_data_len = sizeof(apdu); rpdata.object_type = OBJECT_CHANNEL; - rpdata.object_instance = Channel_Index_To_Instance(0);; + rpdata.object_instance = Channel_Index_To_Instance(0); status = Channel_Valid_Instance(rpdata.object_instance); zassert_true(status, NULL); Channel_Property_Lists(&pRequired, &pOptional, &pProprietary); @@ -47,22 +55,37 @@ static void test_Channel_ReadProperty(void) rpdata.object_property = *pRequired; rpdata.array_index = BACNET_ARRAY_ALL; len = Channel_Read_Property(&rpdata); - zassert_not_equal(len, BACNET_STATUS_ERROR, NULL); + zassert_not_equal(len, BACNET_STATUS_ERROR, + "property '%s': failed to ReadProperty!\n", + bactext_property_name(rpdata.object_property)); if (len > 0) { test_len = bacapp_decode_application_data(rpdata.application_data, (uint8_t)rpdata.application_data_len, &value); - if (len != test_len) { - printf("property '%s': failed to decode!\n", - bactext_property_name(rpdata.object_property)); - } - if (rpdata.object_property == PROP_PRIORITY_ARRAY) { + if ((rpdata.object_property == PROP_PRIORITY_ARRAY) || + (rpdata.object_property == PROP_CONTROL_GROUPS) || + (rpdata.object_property == + PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES)) { /* FIXME: known fail to decode */ len = test_len; } - zassert_true(test_len >= 0, NULL); - } else { - printf("property '%s': failed to read!\n", + zassert_equal(len, test_len, "property '%s': failed to decode!\n", bactext_property_name(rpdata.object_property)); + /* check WriteProperty properties */ + wpdata.object_type = rpdata.object_type; + wpdata.object_instance = rpdata.object_instance; + wpdata.object_property = rpdata.object_property; + wpdata.array_index = rpdata.array_index; + memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); + wpdata.application_data_len = len; + wpdata.error_code = ERROR_CODE_SUCCESS; + status = Channel_Write_Property(&wpdata); + if (!status) { + /* verify WriteProperty property is known */ + zassert_not_equal(wpdata.error_code, + ERROR_CODE_UNKNOWN_PROPERTY, + "property '%s': WriteProperty Unknown!\n", + bactext_property_name(rpdata.object_property)); + } } pRequired++; } @@ -70,21 +93,41 @@ static void test_Channel_ReadProperty(void) rpdata.object_property = *pOptional; rpdata.array_index = BACNET_ARRAY_ALL; len = Channel_Read_Property(&rpdata); - zassert_not_equal(len, BACNET_STATUS_ERROR, NULL); + zassert_not_equal(len, BACNET_STATUS_ERROR, + "property '%s': failed to ReadProperty!\n", + bactext_property_name(rpdata.object_property)); if (len > 0) { test_len = bacapp_decode_application_data(rpdata.application_data, (uint8_t)rpdata.application_data_len, &value); - if (len != test_len) { - printf("property '%s': failed to decode!\n", + zassert_equal(len, test_len, "property '%s': failed to decode!\n", + bactext_property_name(rpdata.object_property)); + /* check WriteProperty properties */ + wpdata.object_type = rpdata.object_type; + wpdata.object_instance = rpdata.object_instance; + wpdata.object_property = rpdata.object_property; + wpdata.array_index = rpdata.array_index; + memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); + wpdata.application_data_len = len; + wpdata.error_code = ERROR_CODE_SUCCESS; + status = Channel_Write_Property(&wpdata); + if (!status) { + /* verify WriteProperty property is known */ + zassert_not_equal(wpdata.error_code, + ERROR_CODE_UNKNOWN_PROPERTY, + "property '%s': WriteProperty Unknown!\n", bactext_property_name(rpdata.object_property)); } - zassert_true(test_len >= 0, NULL); - } else { - printf("property '%s': failed to read!\n", - bactext_property_name(rpdata.object_property)); } pOptional++; } + rpdata.object_property = PROP_ALL; + len = Channel_Read_Property(&rpdata); + zassert_equal(len, BACNET_STATUS_ERROR, NULL); + wpdata.object_property = PROP_ALL; + status = Channel_Write_Property(&wpdata); + zassert_false(status, NULL); + status = Channel_Delete(instance); + zassert_true(status, NULL); } /** * @} @@ -92,8 +135,7 @@ static void test_Channel_ReadProperty(void) void test_main(void) { - ztest_test_suite(channel_tests, - ztest_unit_test(test_Channel_ReadProperty)); + ztest_test_suite(channel_tests, ztest_unit_test(test_Channel_ReadProperty)); ztest_run_test_suite(channel_tests); } diff --git a/test/bacnet/basic/object/color_object/CMakeLists.txt b/test/bacnet/basic/object/color_object/CMakeLists.txt index ac4815f9..59fd44b2 100644 --- a/test/bacnet/basic/object/color_object/CMakeLists.txt +++ b/test/bacnet/basic/object/color_object/CMakeLists.txt @@ -45,6 +45,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bactext.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/basic/sys/linear.c ${SRC_DIR}/bacnet/cov.c ${SRC_DIR}/bacnet/datetime.c ${SRC_DIR}/bacnet/basic/sys/days.c diff --git a/test/bacnet/basic/object/color_object/src/main.c b/test/bacnet/basic/object/color_object/src/main.c index d0767b8a..71d9e918 100644 --- a/test/bacnet/basic/object/color_object/src/main.c +++ b/test/bacnet/basic/object/color_object/src/main.c @@ -27,13 +27,22 @@ static void testColorObject(void) uint8_t apdu[MAX_APDU] = { 0 }; int len = 0; int test_len = 0; - BACNET_READ_PROPERTY_DATA rpdata = {0}; - BACNET_APPLICATION_DATA_VALUE value = {0}; - const int *required_property = NULL; + BACNET_READ_PROPERTY_DATA rpdata = { 0 }; + BACNET_APPLICATION_DATA_VALUE value = { 0 }; + const int *pRequired = NULL; + const int *pOptional = NULL; + const int *pProprietary = NULL; const uint32_t instance = 123; + BACNET_WRITE_PROPERTY_DATA wpdata = { 0 }; + bool status = false; + unsigned index; Color_Init(); Color_Create(instance); + status = Color_Valid_Instance(instance); + zassert_true(status, NULL); + index = Color_Instance_To_Index(instance); + zassert_equal(index, 0, NULL); rpdata.application_data = &apdu[0]; rpdata.application_data_len = sizeof(apdu); @@ -41,23 +50,77 @@ static void testColorObject(void) rpdata.object_instance = instance; rpdata.object_property = PROP_OBJECT_IDENTIFIER; - Color_Property_Lists(&required_property, NULL, NULL); - while ((*required_property) >= 0) { - rpdata.object_property = *required_property; + Color_Property_Lists(&pRequired, &pOptional, &pProprietary); + while ((*pRequired) >= 0) { + rpdata.object_property = *pRequired; rpdata.array_index = BACNET_ARRAY_ALL; len = Color_Read_Property(&rpdata); - zassert_true(len >= 0, NULL); + zassert_not_equal(len, BACNET_STATUS_ERROR, + "property '%s': failed to ReadProperty!\n", + bactext_property_name(rpdata.object_property)); if (len >= 0) { test_len = bacapp_decode_known_property(rpdata.application_data, len, &value, rpdata.object_type, rpdata.object_property); - if (len != test_len) { - printf("property '%s': failed to decode!\n", + zassert_equal(len, test_len, "property '%s': failed to decode!\n", + bactext_property_name(rpdata.object_property)); + /* check WriteProperty properties */ + wpdata.object_type = rpdata.object_type; + wpdata.object_instance = rpdata.object_instance; + wpdata.object_property = rpdata.object_property; + wpdata.array_index = rpdata.array_index; + memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); + wpdata.application_data_len = len; + wpdata.error_code = ERROR_CODE_SUCCESS; + status = Color_Write_Property(&wpdata); + if (!status) { + /* verify WriteProperty property is known */ + zassert_not_equal(wpdata.error_code, + ERROR_CODE_UNKNOWN_PROPERTY, + "property '%s': WriteProperty Unknown!\n", bactext_property_name(rpdata.object_property)); } - zassert_equal(len, test_len, NULL); } - required_property++; + pRequired++; } + while ((*pOptional) != -1) { + rpdata.object_property = *pOptional; + rpdata.array_index = BACNET_ARRAY_ALL; + len = Color_Read_Property(&rpdata); + zassert_not_equal(len, BACNET_STATUS_ERROR, + "property '%s': failed to ReadProperty!\n", + bactext_property_name(rpdata.object_property)); + if (len > 0) { + test_len = bacapp_decode_application_data(rpdata.application_data, + (uint8_t)rpdata.application_data_len, &value); + zassert_equal(len, test_len, "property '%s': failed to decode!\n", + bactext_property_name(rpdata.object_property)); + /* check WriteProperty properties */ + wpdata.object_type = rpdata.object_type; + wpdata.object_instance = rpdata.object_instance; + wpdata.object_property = rpdata.object_property; + wpdata.array_index = rpdata.array_index; + memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); + wpdata.application_data_len = len; + wpdata.error_code = ERROR_CODE_SUCCESS; + status = Color_Write_Property(&wpdata); + if (!status) { + /* verify WriteProperty property is known */ + zassert_not_equal(wpdata.error_code, + ERROR_CODE_UNKNOWN_PROPERTY, + "property '%s': WriteProperty Unknown!\n", + bactext_property_name(rpdata.object_property)); + } + } + pOptional++; + } + rpdata.object_property = PROP_ALL; + len = Color_Read_Property(&rpdata); + zassert_equal(len, BACNET_STATUS_ERROR, NULL); + wpdata.object_property = PROP_ALL; + status = Color_Write_Property(&wpdata); + zassert_false(status, NULL); + status = Color_Delete(instance); + zassert_true(status, NULL); return; } @@ -65,15 +128,12 @@ static void testColorObject(void) * @} */ - #if defined(CONFIG_ZTEST_NEW_API) ZTEST_SUITE(color_object_tests, NULL, NULL, NULL, NULL, NULL); #else void test_main(void) { - ztest_test_suite(color_object_tests, - ztest_unit_test(testColorObject) - ); + ztest_test_suite(color_object_tests, ztest_unit_test(testColorObject)); ztest_run_test_suite(color_object_tests); } diff --git a/test/bacnet/basic/object/color_temperature/CMakeLists.txt b/test/bacnet/basic/object/color_temperature/CMakeLists.txt index 951b951d..f398eea9 100644 --- a/test/bacnet/basic/object/color_temperature/CMakeLists.txt +++ b/test/bacnet/basic/object/color_temperature/CMakeLists.txt @@ -45,6 +45,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bactext.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/basic/sys/linear.c ${SRC_DIR}/bacnet/cov.c ${SRC_DIR}/bacnet/datetime.c ${SRC_DIR}/bacnet/basic/sys/days.c diff --git a/test/bacnet/basic/object/color_temperature/src/main.c b/test/bacnet/basic/object/color_temperature/src/main.c index 4a6b7cc9..a74e0467 100644 --- a/test/bacnet/basic/object/color_temperature/src/main.c +++ b/test/bacnet/basic/object/color_temperature/src/main.c @@ -30,8 +30,12 @@ static void testColorTemperature(void) int test_len = 0; BACNET_READ_PROPERTY_DATA rpdata = {0}; BACNET_APPLICATION_DATA_VALUE value = {0}; - const int *required_property = NULL; + const int *pRequired = NULL; + const int *pOptional = NULL; + const int *pProprietary = NULL; const uint32_t instance = 123; + BACNET_WRITE_PROPERTY_DATA wpdata = { 0 }; + bool status = false; Color_Temperature_Init(); Color_Temperature_Create(instance); @@ -42,23 +46,78 @@ static void testColorTemperature(void) rpdata.object_instance = instance; rpdata.object_property = PROP_OBJECT_IDENTIFIER; - Color_Temperature_Property_Lists(&required_property, NULL, NULL); - while ((*required_property) >= 0) { - rpdata.object_property = *required_property; + Color_Temperature_Property_Lists(&pRequired, &pOptional, &pProprietary); + while ((*pRequired) >= 0) { + rpdata.object_property = *pRequired; rpdata.array_index = BACNET_ARRAY_ALL; len = Color_Temperature_Read_Property(&rpdata); - zassert_true(len >= 0, NULL); + zassert_not_equal(len, BACNET_STATUS_ERROR, + "property '%s': failed to ReadProperty!\n", + bactext_property_name(rpdata.object_property)); if (len >= 0) { test_len = bacapp_decode_known_property(rpdata.application_data, len, &value, rpdata.object_type, rpdata.object_property); - if (len != test_len) { - printf("property '%s': failed to decode!\n", + zassert_equal(len, test_len, "property '%s': failed to decode!\n", + bactext_property_name(rpdata.object_property)); + /* check WriteProperty properties */ + wpdata.object_type = rpdata.object_type; + wpdata.object_instance = rpdata.object_instance; + wpdata.object_property = rpdata.object_property; + wpdata.array_index = rpdata.array_index; + memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); + wpdata.application_data_len = len; + wpdata.error_code = ERROR_CODE_SUCCESS; + status = Color_Temperature_Write_Property(&wpdata); + if (!status) { + /* verify WriteProperty property is known */ + zassert_not_equal(wpdata.error_code, + ERROR_CODE_UNKNOWN_PROPERTY, + "property '%s': WriteProperty Unknown!\n", bactext_property_name(rpdata.object_property)); } - zassert_equal(len, test_len, NULL); } - required_property++; + pRequired++; } + while ((*pOptional) != -1) { + rpdata.object_property = *pOptional; + rpdata.array_index = BACNET_ARRAY_ALL; + len = Color_Temperature_Read_Property(&rpdata); + zassert_not_equal(len, BACNET_STATUS_ERROR, + "property '%s': failed to ReadProperty!\n", + bactext_property_name(rpdata.object_property)); + if (len > 0) { + test_len = bacapp_decode_application_data( + rpdata.application_data, + (uint8_t)rpdata.application_data_len, &value); + zassert_equal(len, test_len, "property '%s': failed to decode!\n", + bactext_property_name(rpdata.object_property)); + /* check WriteProperty properties */ + wpdata.object_type = rpdata.object_type; + wpdata.object_instance = rpdata.object_instance; + wpdata.object_property = rpdata.object_property; + wpdata.array_index = rpdata.array_index; + memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); + wpdata.application_data_len = len; + wpdata.error_code = ERROR_CODE_SUCCESS; + status = Color_Temperature_Write_Property(&wpdata); + if (!status) { + /* verify WriteProperty property is known */ + zassert_not_equal(wpdata.error_code, + ERROR_CODE_UNKNOWN_PROPERTY, + "property '%s': WriteProperty Unknown!\n", + bactext_property_name(rpdata.object_property)); + } + } + pOptional++; + } + rpdata.object_property = PROP_ALL; + len = Color_Temperature_Read_Property(&rpdata); + zassert_equal(len, BACNET_STATUS_ERROR, NULL); + wpdata.object_property = PROP_ALL; + status = Color_Temperature_Write_Property(&wpdata); + zassert_false(status, NULL); + status = Color_Temperature_Delete(instance); + zassert_true(status, NULL); return; } diff --git a/test/bacnet/basic/object/device/CMakeLists.txt b/test/bacnet/basic/object/device/CMakeLists.txt index c6bb0b64..c287b70d 100644 --- a/test/bacnet/basic/object/device/CMakeLists.txt +++ b/test/bacnet/basic/object/device/CMakeLists.txt @@ -77,6 +77,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/debug.c ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/basic/sys/linear.c ${SRC_DIR}/bacnet/basic/tsm/tsm.c ${SRC_DIR}/bacnet/datalink/bvlc.c ${SRC_DIR}/bacnet/cov.c diff --git a/test/bacnet/basic/object/lo/CMakeLists.txt b/test/bacnet/basic/object/lo/CMakeLists.txt index cbdd938a..e8de6ac2 100644 --- a/test/bacnet/basic/object/lo/CMakeLists.txt +++ b/test/bacnet/basic/object/lo/CMakeLists.txt @@ -45,8 +45,11 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bacstr.c ${SRC_DIR}/bacnet/bactext.c ${SRC_DIR}/bacnet/basic/sys/bigend.c - ${SRC_DIR}/bacnet/datetime.c ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/basic/sys/color_rgb.c + ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/basic/sys/linear.c + ${SRC_DIR}/bacnet/datetime.c ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c @@ -57,6 +60,10 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c # Test and test library files ./src/main.c + ../mock/device_mock.c ${ZTST_DIR}/ztest_mock.c ${ZTST_DIR}/ztest.c ) + +target_link_libraries(${PROJECT_NAME} PRIVATE + m) diff --git a/test/bacnet/basic/object/lo/src/main.c b/test/bacnet/basic/object/lo/src/main.c index e75104fb..367ed8fe 100644 --- a/test/bacnet/basic/object/lo/src/main.c +++ b/test/bacnet/basic/object/lo/src/main.c @@ -30,36 +30,104 @@ static void testLightingOutput(void) int len = 0, test_len = 0; BACNET_READ_PROPERTY_DATA rpdata; BACNET_APPLICATION_DATA_VALUE value = {0}; - const int *required_property = NULL; - const uint32_t instance = 1; + const int *pRequired = NULL; + const int *pOptional = NULL; + const int *pProprietary = NULL; + const uint32_t instance = 123; + BACNET_WRITE_PROPERTY_DATA wpdata = { 0 }; + bool status = false; + unsigned index; + uint16_t milliseconds = 10; Lighting_Output_Init(); + Lighting_Output_Create(instance); + status = Lighting_Output_Valid_Instance(instance); + zassert_true(status, NULL); + index = Lighting_Output_Instance_To_Index(instance); + zassert_equal(index, 0, NULL); + rpdata.application_data = &apdu[0]; rpdata.application_data_len = sizeof(apdu); rpdata.object_type = OBJECT_LIGHTING_OUTPUT; rpdata.object_instance = instance; rpdata.array_index = BACNET_ARRAY_ALL; - Lighting_Output_Property_Lists(&required_property, NULL, NULL); - while ((*required_property) >= 0) { - rpdata.object_property = *required_property; + Lighting_Output_Property_Lists(&pRequired, &pOptional, &pProprietary); + while ((*pRequired) >= 0) { + rpdata.object_property = *pRequired; + rpdata.array_index = BACNET_ARRAY_ALL; len = Lighting_Output_Read_Property(&rpdata); - zassert_true(len >= 0, NULL); + zassert_not_equal(len, BACNET_STATUS_ERROR, + "property '%s': failed to ReadProperty!\n", + bactext_property_name(rpdata.object_property)); if (len >= 0) { test_len = bacapp_decode_known_property(rpdata.application_data, len, &value, rpdata.object_type, rpdata.object_property); - if (len != test_len) { - printf("property '%s': failed to decode!\n", + if (rpdata.object_property != PROP_PRIORITY_ARRAY) { + zassert_equal(len, test_len, "property '%s': failed to decode!\n", + bactext_property_name(rpdata.object_property)); + } + /* check WriteProperty properties */ + wpdata.object_type = rpdata.object_type; + wpdata.object_instance = rpdata.object_instance; + wpdata.object_property = rpdata.object_property; + wpdata.array_index = rpdata.array_index; + memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); + wpdata.application_data_len = len; + wpdata.error_code = ERROR_CODE_SUCCESS; + status = Lighting_Output_Write_Property(&wpdata); + if (!status) { + /* verify WriteProperty property is known */ + zassert_not_equal(wpdata.error_code, + ERROR_CODE_UNKNOWN_PROPERTY, + "property '%s': WriteProperty Unknown!\n", bactext_property_name(rpdata.object_property)); } - if (rpdata.object_property == PROP_PRIORITY_ARRAY) { - /* FIXME: known fail to decode */ - len = test_len; - } - zassert_equal(len, test_len, NULL); } - required_property++; + pRequired++; } + while ((*pOptional) != -1) { + rpdata.object_property = *pOptional; + rpdata.array_index = BACNET_ARRAY_ALL; + len = Lighting_Output_Read_Property(&rpdata); + zassert_not_equal(len, BACNET_STATUS_ERROR, + "property '%s': failed to ReadProperty!\n", + bactext_property_name(rpdata.object_property)); + if (len > 0) { + test_len = bacapp_decode_application_data(rpdata.application_data, + (uint8_t)rpdata.application_data_len, &value); + zassert_equal(len, test_len, "property '%s': failed to decode!\n", + bactext_property_name(rpdata.object_property)); + /* check WriteProperty properties */ + wpdata.object_type = rpdata.object_type; + wpdata.object_instance = rpdata.object_instance; + wpdata.object_property = rpdata.object_property; + wpdata.array_index = rpdata.array_index; + memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); + wpdata.application_data_len = len; + wpdata.error_code = ERROR_CODE_SUCCESS; + status = Lighting_Output_Write_Property(&wpdata); + if (!status) { + /* verify WriteProperty property is known */ + zassert_not_equal(wpdata.error_code, + ERROR_CODE_UNKNOWN_PROPERTY, + "property '%s': WriteProperty Unknown!\n", + bactext_property_name(rpdata.object_property)); + } + } + pOptional++; + } + /* check for unsupported property - use ALL */ + rpdata.object_property = PROP_ALL; + len = Lighting_Output_Read_Property(&rpdata); + zassert_equal(len, BACNET_STATUS_ERROR, NULL); + status = Lighting_Output_Write_Property(&wpdata); + zassert_false(status, NULL); + /* check the dimming/ramping/stepping engine*/ + Lighting_Output_Timer(instance, milliseconds); + /* check the delete function */ + status = Lighting_Output_Delete(instance); + zassert_true(status, NULL); return; } diff --git a/test/bacnet/basic/sys/color_rgb/src/main.c b/test/bacnet/basic/sys/color_rgb/src/main.c index f6b81037..16c995a3 100644 --- a/test/bacnet/basic/sys/color_rgb/src/main.c +++ b/test/bacnet/basic/sys/color_rgb/src/main.c @@ -8,6 +8,9 @@ * * SPDX-License-Identifier: MIT */ +#include +#include +#include #include #include @@ -16,34 +19,69 @@ * @{ */ +/** + * @brief compare two floating point values to 3 decimal places + * + * @param x1 - first comparison value + * @param x2 - second comparison value + * @return true if the value is the same to 3 decimal points + */ +static bool is_float_equal(float x1, float x2) +{ + return fabs(x1 - x2) < 0.001; +} + /** * Unit Test for sRGB to CIE xy */ -static void test_color_rgb_xy_unit( - uint8_t red, uint8_t green, uint8_t blue, - float x_coordinate, float y_coordinate, +static void test_color_rgb_xy_gamma_unit(uint8_t red, + uint8_t green, + uint8_t blue, + float x_coordinate, + float y_coordinate, + uint8_t brightness) +{ + float test_x_coordinate = 0.0, test_y_coordinate = 0.0; + uint8_t test_brightness = 0; + uint8_t test_red = 0, test_green = 0, test_blue = 0; + + /* functions with gamma correction */ + color_rgb_to_xy_gamma(red, green, blue, &test_x_coordinate, + &test_y_coordinate, &test_brightness); + color_rgb_from_xy_gamma(&test_red, &test_green, &test_blue, x_coordinate, + y_coordinate, brightness); + zassert_true(is_float_equal(x_coordinate, test_x_coordinate), + "(x=%.3f,test_x=%.3f)", x_coordinate, test_x_coordinate); + zassert_true(is_float_equal(y_coordinate, test_y_coordinate), + "(y=%.3f,test_y=%.3f)", y_coordinate, test_y_coordinate); + zassert_equal(brightness, test_brightness, "b=%u, test_b=%u", brightness, + test_brightness); +} + +/** + * Unit Test for sRGB to CIE xy + */ +static void test_color_rgb_xy_unit(uint8_t red, + uint8_t green, + uint8_t blue, + float x_coordinate, + float y_coordinate, uint8_t brightness) { float test_x_coordinate = 0.0, test_y_coordinate = 0.0; uint8_t test_brightness = 0; uint8_t test_red = 0, test_green = 0, test_blue = 0; - printf("test value:(%u,%u,%u)=(%.3f,%.3f,%u)\n", - (unsigned)red, (unsigned)green, (unsigned)blue, - x_coordinate, y_coordinate, (unsigned)brightness); color_rgb_to_xy(red, green, blue, &test_x_coordinate, &test_y_coordinate, &test_brightness); - color_rgb_from_xy(&test_red, &test_green, &test_blue, - x_coordinate, y_coordinate, brightness); - printf("calculated:(%u,%u,%u)=(%.3f,%.3f,%u)\n", - (unsigned)test_red, (unsigned)test_green, (unsigned)test_blue, - test_x_coordinate, test_y_coordinate, (unsigned)test_brightness); - //zassert_equal(x_coordinate, test_x_coordinate, NULL); - //zassert_equal(y_coordinate, test_y_coordinate, NULL); - //zassert_equal(brightness, test_brightness, NULL); - //zassert_equal(red, test_red, NULL); - //zassert_equal(green, test_green, NULL); - //zassert_equal(blue, test_blue, NULL); + color_rgb_from_xy(&test_red, &test_green, &test_blue, x_coordinate, + y_coordinate, brightness); + zassert_true(is_float_equal(x_coordinate, test_x_coordinate), + "(x=%.3f,test_x=%.3f)", x_coordinate, test_x_coordinate); + zassert_true(is_float_equal(y_coordinate, test_y_coordinate), + "(y=%.3f,test_y=%.3f)", y_coordinate, test_y_coordinate); + zassert_equal(brightness, test_brightness, "b=%u, test_b=%u", brightness, + test_brightness); } /** @@ -55,17 +93,40 @@ ZTEST(color_rgb_tests, test_color_rgb_xy) static void test_color_rgb_xy(void) #endif { - test_color_rgb_xy_unit(0, 0, 0, 0.0, 0.0, 0); - test_color_rgb_xy_unit(255, 255, 255, 0.323, 0.329, 255); - test_color_rgb_xy_unit(0, 0, 255, 0.136, 0.04, 12); - test_color_rgb_xy_unit(0, 255, 0, 0.172, 0.747, 170); - test_color_rgb_xy_unit(255, 0, 0, 0.701, 0.299, 72); - test_color_rgb_xy_unit(128, 0, 0, 0.701, 0.299, 16); + uint8_t red, green, blue; + + /* functions without gamma correction */ + color_rgb_from_ascii(&red, &green, &blue, "black"); + test_color_rgb_xy_unit(red, green, blue, 0.0, 0.0, 0); + color_rgb_from_ascii(&red, &green, &blue, "white"); + test_color_rgb_xy_unit(red, green, blue, 0.313, 0.329, 255); + color_rgb_from_ascii(&red, &green, &blue, "blue"); + test_color_rgb_xy_unit(red, green, blue, 0.157, 0.017, 5); + color_rgb_from_ascii(&red, &green, &blue, "green"); + test_color_rgb_xy_unit(red, green, blue, 0.115, 0.826, 95); + color_rgb_from_ascii(&red, &green, &blue, "red"); + test_color_rgb_xy_unit(red, green, blue, 0.735, 0.265, 59); + color_rgb_from_ascii(&red, &green, &blue, "maroon"); + test_color_rgb_xy_unit(red, green, blue, 0.735, 0.265, 29); + + /* functions with gamma correction */ + color_rgb_from_ascii(&red, &green, &blue, "black"); + test_color_rgb_xy_gamma_unit(red, green, blue, 0.0, 0.0, 0); + color_rgb_from_ascii(&red, &green, &blue, "white"); + test_color_rgb_xy_gamma_unit(red, green, blue, 0.313, 0.329, 255); + color_rgb_from_ascii(&red, &green, &blue, "blue"); + test_color_rgb_xy_gamma_unit(red, green, blue, 0.157, 0.017, 5); + color_rgb_from_ascii(&red, &green, &blue, "green"); + test_color_rgb_xy_gamma_unit(red, green, blue, 0.115, 0.826, 40); + color_rgb_from_ascii(&red, &green, &blue, "red"); + test_color_rgb_xy_gamma_unit(red, green, blue, 0.735, 0.265, 59); + color_rgb_from_ascii(&red, &green, &blue, "maroon"); + test_color_rgb_xy_gamma_unit(red, green, blue, 0.735, 0.265, 12); } /** -* Unit Test for sRGB to CIE xy -*/ + * Unit Test for sRGB to CIE xy + */ #if defined(CONFIG_ZTEST_NEW_API) ZTEST(color_rgb_tests, test_color_rgb_ascii) #else @@ -85,8 +146,8 @@ static void test_color_rgb_ascii(void) for (unsigned i = 0; i < count; i++) { name = color_rgb_from_index(i, &red, &green, &blue); zassert_not_null(name, NULL); - test_index = color_rgb_from_ascii(&test_red, &test_green, &test_blue, - name); + test_index = + color_rgb_from_ascii(&test_red, &test_green, &test_blue, name); zassert_equal(i, test_index, NULL); zassert_equal(red, test_red, NULL); zassert_equal(green, test_green, NULL); @@ -100,16 +161,13 @@ static void test_color_rgb_ascii(void) * @} */ - #if defined(CONFIG_ZTEST_NEW_API) ZTEST_SUITE(color_rgb_tests, NULL, NULL, NULL, NULL, NULL); #else void test_main(void) { - ztest_test_suite(color_rgb_tests, - ztest_unit_test(test_color_rgb_ascii), - ztest_unit_test(test_color_rgb_xy) - ); + ztest_test_suite(color_rgb_tests, ztest_unit_test(test_color_rgb_ascii), + ztest_unit_test(test_color_rgb_xy)); ztest_run_test_suite(color_rgb_tests); } diff --git a/test/bacnet/basic/sys/linear/CMakeLists.txt b/test/bacnet/basic/sys/linear/CMakeLists.txt new file mode 100644 index 00000000..45f3797a --- /dev/null +++ b/test/bacnet/basic/sys/linear/CMakeLists.txt @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(test_${basename} + VERSION 1.0.0 + LANGUAGES C) + + +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/src" + SRC_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/test" + TST_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +set(ZTST_DIR "${TST_DIR}/ztest/src") + +add_compile_definitions( + BIG_ENDIAN=0 + CONFIG_ZTEST=1 + ) + +include_directories( + ${SRC_DIR} + ${TST_DIR}/ztest/include + ) + +add_executable(${PROJECT_NAME} + # File(s) under test + ${SRC_DIR}/bacnet/basic/sys/linear.c + # Support files and stubs (pathname alphabetical) + # Test and test library files + ./src/main.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) + +target_link_libraries(${PROJECT_NAME} PRIVATE + m) diff --git a/test/bacnet/basic/sys/linear/src/main.c b/test/bacnet/basic/sys/linear/src/main.c new file mode 100644 index 00000000..3c2e574a --- /dev/null +++ b/test/bacnet/basic/sys/linear/src/main.c @@ -0,0 +1,136 @@ +/** + * @file + * @brief test linear interpolation APIs + * @date 2010 + * + * @section LICENSE + * Copyright (c) 2010 Steve Karg + * + * SPDX-License-Identifier: MIT + */ +#include +#include + +/** + * @addtogroup bacnet_tests + * @{ + */ + +/** +* Unit Test for linear interpolation of floating point values, rounded +*/ +void testLinearInterpolateRound(void) +{ + uint16_t x2 = 0; + uint16_t y1 = 0; + uint16_t y2 = 0; + uint16_t y3 = 0; + uint16_t x2_test = 0; + + y2 = linear_interpolate_round(1, 1, 65535, 1, 100); + zassert_equal(y2, 1, NULL); + y2 = linear_interpolate_round(1, 1, 65535, 100, 1); + zassert_equal(y2, 100, NULL); + + y2 = linear_interpolate_round(1, 65535, 65535, 1, 100); + zassert_equal(y2, 100, NULL); + y2 = linear_interpolate_round(1, 65535, 65535, 100, 1); + zassert_equal(y2, 1, NULL); + + y2 = linear_interpolate_round(1, (65535 / 2), 65535, 1, 100); + zassert_equal(y2, 50, NULL); + + y2 = linear_interpolate_round(1, (65535 / 4), 65535, 1, 100); + zassert_equal(y2, 26, NULL); + + y2 = linear_interpolate_round(1, ((65535 * 3) / 4), 65535, 1, 100); + zassert_equal(y2, 75, NULL); + + y2 = linear_interpolate_round(1, 1, 100, 1, 65535); + zassert_equal(y2, 1, NULL); + + y2 = linear_interpolate_round(1, 100, 100, 1, 65535); + zassert_equal(y2, 65535, NULL); + + y2 = linear_interpolate_round(1, 100 / 2, 100, 1, 65535); + zassert_equal(y2, 32437, NULL); + + /* scaling from percent to steps and back */ + for (x2 = 1; x2 <= 100; x2++) { + y2 = linear_interpolate_round(1, x2, 100, 1, 65535); + x2_test = linear_interpolate_round(1, y2, 65535, 1, 100); + zassert_equal(x2, x2_test, NULL); + } + + /* test for low-trim, high-trim and scaling from percent to steps */ + for (x2 = 1; x2 <= 100; x2++) { + y1 = linear_interpolate_round(1, 20, 100, 1, 65535); + y3 = linear_interpolate_round(1, 80, 100, 1, 65535); + y2 = linear_interpolate_round(1, x2, 100, y1, y3); + x2_test = linear_interpolate_round(y1, y2, y3, 1, 100); + zassert_equal(x2, x2_test, "x2=%hu x2_test=%hu\n", x2, x2_test); + } + + y2 = linear_interpolate_round(1, 1, 65535, 20, 80); + zassert_equal(y2, 20, NULL); + y2 = linear_interpolate_round(1, 1, 65535, 80, 20); + zassert_equal(y2, 80, NULL); + y2 = linear_interpolate_round(1, 65535, 65535, 20, 80); + zassert_equal(y2, 80, NULL); + y2 = linear_interpolate_round(1, 65535, 65535, 80, 20); + zassert_equal(y2, 20, NULL); +} + +/** +* Unit Test for linear interpolation of integers +*/ +void testLinearInterpolateInt(void) +{ + uint16_t y2 = 0; + + y2 = linear_interpolate_int(1, 1, 65535, 1, 100); + zassert_equal(y2, 1, NULL); + y2 = linear_interpolate_int(1, 1, 65535, 100, 1); + zassert_equal(y2, 100, NULL); + + y2 = linear_interpolate_int(1, 65535, 65535, 1, 100); + zassert_equal(y2, 100, NULL); + y2 = linear_interpolate_int(1, 65535, 65535, 100, 1); + zassert_equal(y2, 1, NULL); + + y2 = linear_interpolate_int(1, (65535 / 4), 65535, 1, 100); + zassert_equal(y2, 25, NULL); + + y2 = linear_interpolate_int(1, (65535 / 2), 65535, 1, 100); + zassert_equal(y2, 50, NULL); + + y2 = linear_interpolate_int(1, ((65535 * 3) / 4), 65535, 1, 100); + zassert_equal(y2, 75, NULL); + + y2 = linear_interpolate_int(1, 1, 100, 1, 65535); + zassert_equal(y2, 1, NULL); + + y2 = linear_interpolate_int(1, 100, 100, 1, 65535); + zassert_equal(y2, 65535, NULL); + + y2 = linear_interpolate_int(1, 100 / 2, 100, 1, 65535); + zassert_equal(y2, 32437, NULL); +} + +/** + * @} + */ + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST_SUITE(Linear_Interpolate, NULL, NULL, NULL, NULL, NULL); +#else +void test_main(void) +{ + ztest_test_suite(Linear_Interpolate, + ztest_unit_test(testLinearInterpolateRound), + ztest_unit_test(testLinearInterpolateInt) + ); + + ztest_run_test_suite(Linear_Interpolate); +} +#endif diff --git a/test/bacnet/lighting/CMakeLists.txt b/test/bacnet/lighting/CMakeLists.txt index b2645e44..2c650ed4 100644 --- a/test/bacnet/lighting/CMakeLists.txt +++ b/test/bacnet/lighting/CMakeLists.txt @@ -39,6 +39,8 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bacreal.c ${SRC_DIR}/bacnet/bacstr.c ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/bactext.c + ${SRC_DIR}/bacnet/indtext.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/lighting/src/main.c b/test/bacnet/lighting/src/main.c index 0e72b240..34db9af5 100644 --- a/test/bacnet/lighting/src/main.c +++ b/test/bacnet/lighting/src/main.c @@ -10,6 +10,8 @@ #include #include +#include +#include #include /** @@ -36,10 +38,16 @@ static void testBACnetLightingCommand(BACNET_LIGHTING_COMMAND *data) status = lighting_command_same(&test_data, data); zassert_true(status, NULL); len = lighting_command_encode(apdu, data); - apdu_len = lighting_command_decode(apdu, sizeof(apdu), &test_data); - zassert_true(len > 0, NULL); - zassert_true(apdu_len > 0, NULL); + apdu_len = lighting_command_decode(apdu, len, &test_data); + zassert_true(len > 0, "lighting-command[%s] failed to encode!", + bactext_lighting_operation_name(data->operation)); + zassert_true(apdu_len > 0, "lighting-command[%s] failed to decode!", + bactext_lighting_operation_name(data->operation)); status = lighting_command_same(&test_data, data); + while (len) { + len--; + apdu_len = lighting_command_decode(apdu, len, NULL); + } } #if defined(CONFIG_ZTEST_NEW_API) @@ -48,27 +56,58 @@ ZTEST(lighting_tests, testBACnetLightingCommandAll) static void testBACnetLightingCommandAll(void) #endif { - BACNET_LIGHTING_COMMAND data; + /* + BACNET_LIGHTING_OPERATION operation; + bool use_target_level:1; + bool use_ramp_rate:1; + bool use_step_increment:1; + bool use_fade_time:1; + bool use_priority:1; + float target_level; + float ramp_rate; + float step_increment; + uint32_t fade_time; + uint8_t priority; + */ + BACNET_LIGHTING_COMMAND test_data[] = { + { BACNET_LIGHTS_NONE, false, false, false, false, false, 0.0, 100.0, + 1.0, 100, 1 }, + { BACNET_LIGHTS_FADE_TO, true, false, false, true, true, 100.0, 100.0, + 1.0, 100, 1 }, + { BACNET_LIGHTS_FADE_TO, true, false, false, false, false, 0.0, 100.0, + 1.0, 100, 1 }, + { BACNET_LIGHTS_RAMP_TO, true, true, false, false, true, 0.0, 100.0, + 1.0, 100, 1 }, + { BACNET_LIGHTS_RAMP_TO, true, false, false, false, false, 100.0, 100.0, + 1.0, 100, 1 }, + { BACNET_LIGHTS_STEP_UP, false, false, true, false, true, 100.0, 100.0, + 1.0, 100, 1 }, + { BACNET_LIGHTS_STEP_UP, false, false, true, false, false, 100.0, 100.0, + 2.0, 100, 1 }, + { BACNET_LIGHTS_STEP_DOWN, false, false, true, false, true, 100.0, + 100.0, 1.0, 100, 1 }, + { BACNET_LIGHTS_STEP_DOWN, false, false, true, false, false, 100.0, + 100.0, 2.0, 100, 1 }, + { BACNET_LIGHTS_STEP_ON, false, false, true, false, true, 100.0, 100.0, + 1.0, 100, 1 }, + { BACNET_LIGHTS_STEP_ON, false, false, true, false, false, 100.0, 100.0, + 2.0, 100, 1 }, + { BACNET_LIGHTS_STEP_OFF, false, false, true, false, true, 100.0, 100.0, + 1.0, 100, 1 }, + { BACNET_LIGHTS_STEP_OFF, false, false, true, false, false, 100.0, + 100.0, 2.0, 100, 1 }, + { BACNET_LIGHTS_STOP, false, false, false, false, true, 100.0, 100.0, + 1.0, 100, 1 }, + { BACNET_LIGHTS_STOP, false, false, false, false, false, 100.0, 100.0, + 2.0, 100, 1 }, + }; + unsigned i; - data.operation = BACNET_LIGHTS_NONE; - data.use_target_level = false; - data.use_ramp_rate = false; - data.use_step_increment = false; - data.use_fade_time = false; - data.use_priority = false; - data.target_level = 0.0; - data.ramp_rate = 100.0; - data.step_increment = 1.0; - data.fade_time = 100; - data.priority = 1; - testBACnetLightingCommand(&data); - data.operation = BACNET_LIGHTS_STOP; - data.use_target_level = true; - data.use_ramp_rate = true; - data.use_step_increment = true; - data.use_fade_time = true; - data.use_priority = true; - testBACnetLightingCommand(&data); + for (i = 0; i < ARRAY_SIZE(test_data); i++) { + printf("test-lighting-command[%s]\n", + bactext_lighting_operation_name(test_data[i].operation)); + testBACnetLightingCommand(&test_data[i]); + } } /** * @} @@ -94,11 +133,16 @@ static void testBACnetColorCommand(BACNET_COLOR_COMMAND *data) status = color_command_same(&test_data, data); zassert_true(status, NULL); len = color_command_encode(apdu, data); - apdu_len = color_command_decode(apdu, sizeof(apdu), &error_code, - &test_data); - zassert_true(len > 0, NULL); - zassert_true(apdu_len > 0, NULL); + apdu_len = color_command_decode(apdu, len, &error_code, &test_data); + zassert_true(len > 0, "color-command[%s] failed to encode!", + bactext_color_operation_name(data->operation)); + zassert_true(apdu_len > 0, "color-command[%s] failed to decode!", + bactext_color_operation_name(data->operation)); status = color_command_same(&test_data, data); + while (len) { + len--; + apdu_len = color_command_decode(apdu, len, NULL, NULL); + } } #if defined(CONFIG_ZTEST_NEW_API) @@ -107,16 +151,51 @@ ZTEST(lighting_tests, testBACnetColorCommandAll) static void testBACnetColorCommandAll(void) #endif { - BACNET_COLOR_COMMAND data = { 0 }; + BACNET_COLOR_COMMAND test_data[] = { + { .operation = BACNET_COLOR_OPERATION_NONE, + .target.color_temperature = 0, + .transit.fade_time = 0 }, + { .operation = BACNET_COLOR_OPERATION_STOP, + .target.color_temperature = 0, + .transit.fade_time = 0 }, + { .operation = BACNET_COLOR_OPERATION_FADE_TO_COLOR, + .target.color.x_coordinate = 0.0, + .target.color.y_coordinate = 0.0, + .transit.fade_time = 0 }, + { .operation = BACNET_COLOR_OPERATION_FADE_TO_COLOR, + .target.color.x_coordinate = 0.0, + .target.color.y_coordinate = 0.0, + .transit.fade_time = 2000 }, + { .operation = BACNET_COLOR_OPERATION_FADE_TO_CCT, + .target.color_temperature = 1800, + .transit.fade_time = 0 }, + { .operation = BACNET_COLOR_OPERATION_FADE_TO_CCT, + .target.color_temperature = 1800, + .transit.fade_time = 2000 }, + { .operation = BACNET_COLOR_OPERATION_RAMP_TO_CCT, + .target.color_temperature = 1800, + .transit.ramp_rate = 0 }, + { .operation = BACNET_COLOR_OPERATION_RAMP_TO_CCT, + .target.color_temperature = 1800, + .transit.ramp_rate = 20 }, + { .operation = BACNET_COLOR_OPERATION_STEP_UP_CCT, + .target.color_temperature = 1800, + .transit.step_increment = 0 }, + { .operation = BACNET_COLOR_OPERATION_STEP_UP_CCT, + .target.color_temperature = 1800, + .transit.step_increment = 1 }, + { .operation = BACNET_COLOR_OPERATION_STEP_DOWN_CCT, + .target.color_temperature = 5000, + .transit.step_increment = 0 }, + { .operation = BACNET_COLOR_OPERATION_STEP_DOWN_CCT, + .target.color_temperature = 5000, + .transit.step_increment = 1 }, + }; + unsigned i; - data.operation = BACNET_COLOR_OPERATION_NONE; - data.target.color_temperature = 0; - data.transit.fade_time = 0; - testBACnetColorCommand(&data); - data.operation = BACNET_COLOR_OPERATION_STOP; - data.target.color_temperature = 0; - data.transit.fade_time = 0; - testBACnetColorCommand(&data); + for (i = 0; i < ARRAY_SIZE(test_data); i++) { + testBACnetColorCommand(&test_data[i]); + } } #if defined(CONFIG_ZTEST_NEW_API) @@ -146,11 +225,14 @@ static void testBACnetXYColor(void) null_len = xy_color_context_encode(NULL, tag_number, &value); len = xy_color_context_encode(apdu, tag_number, &value); zassert_equal(null_len, len, NULL); - test_len = xy_color_context_decode(apdu, sizeof(apdu), tag_number, - &test_value); + test_len = xy_color_context_decode(apdu, len, tag_number, &test_value); zassert_equal(test_len, len, NULL); status = xy_color_same(&value, &test_value); zassert_true(status, NULL); + while (len) { + len--; + test_len = xy_color_context_decode(apdu, len, tag_number, NULL); + } } #if defined(CONFIG_ZTEST_NEW_API) @@ -159,10 +241,9 @@ ZTEST_SUITE(lighting_tests, NULL, NULL, NULL, NULL, NULL); void test_main(void) { ztest_test_suite(lighting_tests, - ztest_unit_test(testBACnetLightingCommandAll), - ztest_unit_test(testBACnetColorCommandAll), - ztest_unit_test(testBACnetXYColor) - ); + ztest_unit_test(testBACnetLightingCommandAll), + ztest_unit_test(testBACnetColorCommandAll), + ztest_unit_test(testBACnetXYColor)); ztest_run_test_suite(lighting_tests); } diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index aedded9b..f2e376e9 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -223,6 +223,8 @@ set(BACNETSTACK_SRCS ${BACNETSTACK_SRC}/bacnet/basic/sys/key.h ${BACNETSTACK_SRC}/bacnet/basic/sys/keylist.c ${BACNETSTACK_SRC}/bacnet/basic/sys/keylist.h + ${BACNETSTACK_SRC}/bacnet/basic/sys/linear.c + ${BACNETSTACK_SRC}/bacnet/basic/sys/linear.h ${BACNETSTACK_SRC}/bacnet/basic/sys/mstimer.c ${BACNETSTACK_SRC}/bacnet/basic/sys/mstimer.h ${BACNETSTACK_SRC}/bacnet/basic/sys/ringbuf.c diff --git a/zephyr/tests/bacnet/basic/object/color_object/CMakeLists.txt b/zephyr/tests/bacnet/basic/object/color_object/CMakeLists.txt index 50b92210..d33319d9 100644 --- a/zephyr/tests/bacnet/basic/object/color_object/CMakeLists.txt +++ b/zephyr/tests/bacnet/basic/object/color_object/CMakeLists.txt @@ -44,6 +44,7 @@ if(BOARD STREQUAL unit_testing) ${BACNET_SRC}/timestamp.c ${BACNET_SRC}/basic/sys/days.c ${BACNET_SRC}/basic/sys/keylist.c + ${BACNET_SRC}/basic/sys/linear.c ${BACNET_SRC}/bacdevobjpropref.c ${BACNET_SRC}/bactext.c ${BACNET_SRC}/indtext.c diff --git a/zephyr/tests/bacnet/basic/object/color_temperature/CMakeLists.txt b/zephyr/tests/bacnet/basic/object/color_temperature/CMakeLists.txt index 78d23cad..4e181ea2 100644 --- a/zephyr/tests/bacnet/basic/object/color_temperature/CMakeLists.txt +++ b/zephyr/tests/bacnet/basic/object/color_temperature/CMakeLists.txt @@ -44,6 +44,7 @@ if(BOARD STREQUAL unit_testing) ${BACNET_SRC}/timestamp.c ${BACNET_SRC}/basic/sys/days.c ${BACNET_SRC}/basic/sys/keylist.c + ${BACNET_SRC}/basic/sys/linear.c ${BACNET_SRC}/bacdevobjpropref.c ${BACNET_SRC}/bactext.c ${BACNET_SRC}/indtext.c diff --git a/zephyr/tests/bacnet/basic/object/lo/CMakeLists.txt b/zephyr/tests/bacnet/basic/object/lo/CMakeLists.txt index da4f3355..062d0517 100644 --- a/zephyr/tests/bacnet/basic/object/lo/CMakeLists.txt +++ b/zephyr/tests/bacnet/basic/object/lo/CMakeLists.txt @@ -26,6 +26,7 @@ if(BOARD STREQUAL unit_testing) list(APPEND SOURCES ${BACNET_SRC_PATH}.c ${BACNET_TEST_PATH}/src/main.c + ${BACNET_TEST_PATH}/../mock/device_mock.c ) get_filename_component(BACNET_OBJECT_SRC ${BACNET_SRC_PATH} PATH) @@ -51,6 +52,8 @@ if(BOARD STREQUAL unit_testing) ${BACNET_SRC}/dailyschedule.c ${BACNET_SRC}/weeklyschedule.c ${BACNET_SRC}/basic/sys/bigend.c + ${BACNET_SRC}/basic/sys/linear.c + ${BACNET_SRC}/basic/sys/keylist.c ${BACNET_SRC}/bactimevalue.c ) diff --git a/zephyr/tests/bacnet/lighting/CMakeLists.txt b/zephyr/tests/bacnet/lighting/CMakeLists.txt index 41c510f6..4f1b6256 100644 --- a/zephyr/tests/bacnet/lighting/CMakeLists.txt +++ b/zephyr/tests/bacnet/lighting/CMakeLists.txt @@ -34,6 +34,8 @@ if(BOARD STREQUAL unit_testing) ${BACNET_SRC}/bacstr.c ${BACNET_SRC}/bacint.c ${BACNET_SRC}/bacreal.c + ${BACNET_SRC}/bactext.c + ${BACNET_SRC}/indtext.c ${BACNET_SRC}/datetime.c ${BACNET_SRC}/timestamp.c ${BACNET_SRC}/basic/sys/days.c