From 38d213b47c87725abdad7f198b98703bf64b2f41 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Wed, 13 Jul 2022 09:54:36 -0500 Subject: [PATCH] Feature/color objects color command (#302) * added BACnetColorCommand and BACnetxyColor encoding and unit testing * Added Color object and unit testing. * Added Color Temperature object and Unit test * Fix BVLC unit test warning. * add port Makefile for extra types * added RGB to and from CIE xy utility in sys folder, and add unit tests. * added cmake-win32 target * Change RP and RPM to use known property decoder. Add color object RP and RPM decoding and printing Fix RPM print for new reserved range above 4194303 Change default protocol-revision to 24 for Color object * Integrate Color and Color Temperature objects into demo apps Co-authored-by: Steve Karg --- CMakeLists.txt | 15 + Makefile | 9 +- apps/Makefile | 2 +- apps/gateway/Makefile | 2 + apps/server/Makefile | 2 + ports/at91sam7s/Makefile | 8 +- ports/at91sam7s/bacnet.ewp | 3 + ports/atmega168/Makefile | 2 + ports/bdk-atxx4-mstp/Makefile | 2 + ports/linux/bip-init.c | 1 - ports/stm32f10x/Makefile | 4 + ports/stm32f10x/bacnet.ewp | 3 + ports/stm32f4xx/Makefile | 4 + ports/stm32f4xx/bacnet.ewp | 3 + ports/xplained/Makefile | 237 ++++ ports/xplained/bacnet.cproj | 280 ++-- src/bacnet/bacapp.c | 496 ++++++-- src/bacnet/bacapp.h | 11 + src/bacnet/bacdcode.c | 35 +- src/bacnet/bacdef.h | 2 +- src/bacnet/bacdevobjpropref.c | 5 +- src/bacnet/bacenum.h | 22 +- src/bacnet/bactext.c | 7 +- src/bacnet/basic/object/color_object.c | 917 ++++++++++++++ src/bacnet/basic/object/color_object.h | 133 ++ src/bacnet/basic/object/color_temperature.c | 1124 +++++++++++++++++ src/bacnet/basic/object/color_temperature.h | 165 +++ src/bacnet/basic/object/device.c | 131 +- src/bacnet/basic/service/h_apdu.c | 6 +- src/bacnet/basic/service/h_apdu.h | 2 +- src/bacnet/basic/service/h_rp_a.c | 5 +- src/bacnet/basic/service/h_rpm_a.c | 29 +- src/bacnet/basic/sys/color_rgb.c | 486 +++++++ src/bacnet/basic/sys/color_rgb.h | 44 + src/bacnet/lighting.c | 759 ++++++++++- src/bacnet/lighting.h | 150 ++- src/bacnet/property.c | 17 + test/CMakeLists.txt | 2 + test/bacnet/bacapp/CMakeLists.txt | 1 + test/bacnet/bacdevobjpropref/CMakeLists.txt | 1 + .../basic/binding/address/CMakeLists.txt | 1 + test/bacnet/basic/object/acc/CMakeLists.txt | 1 + .../object/access_credential/CMakeLists.txt | 1 + .../basic/object/access_door/CMakeLists.txt | 1 + .../basic/object/access_rights/CMakeLists.txt | 1 + .../basic/object/access_user/CMakeLists.txt | 1 + .../basic/object/access_zone/CMakeLists.txt | 1 + test/bacnet/basic/object/ai/CMakeLists.txt | 1 + test/bacnet/basic/object/ao/CMakeLists.txt | 1 + test/bacnet/basic/object/av/CMakeLists.txt | 1 + test/bacnet/basic/object/bi/CMakeLists.txt | 1 + test/bacnet/basic/object/bo/CMakeLists.txt | 1 + test/bacnet/basic/object/bv/CMakeLists.txt | 1 + .../basic/object/color_object/CMakeLists.txt | 59 + .../basic/object/color_object/src/main.c | 88 ++ .../object/color_temperature/CMakeLists.txt | 59 + .../basic/object/color_temperature/src/main.c | 88 ++ .../basic/object/command/CMakeLists.txt | 1 + .../bacnet/basic/object/device/CMakeLists.txt | 3 + test/bacnet/basic/object/lc/CMakeLists.txt | 1 + test/bacnet/basic/object/lo/CMakeLists.txt | 1 + test/bacnet/basic/object/lo/src/main.c | 39 +- test/bacnet/basic/object/lsp/CMakeLists.txt | 1 + .../basic/object/ms-input/CMakeLists.txt | 1 + test/bacnet/basic/object/mso/CMakeLists.txt | 1 + test/bacnet/basic/object/msv/CMakeLists.txt | 1 + .../basic/object/netport/CMakeLists.txt | 1 + test/bacnet/basic/object/osv/CMakeLists.txt | 1 + test/bacnet/basic/object/piv/CMakeLists.txt | 1 + .../basic/object/schedule/CMakeLists.txt | 1 + .../bacnet/basic/sys/color_rgb/CMakeLists.txt | 44 + test/bacnet/basic/sys/color_rgb/src/main.c | 104 ++ test/bacnet/cov/CMakeLists.txt | 1 + test/bacnet/datalink/bvlc/src/main.c | 1 + test/bacnet/lighting/src/main.c | 74 +- test/bacnet/lso/CMakeLists.txt | 1 + test/bacnet/ptransfer/CMakeLists.txt | 1 + test/bacnet/rpm/CMakeLists.txt | 1 + test/bacnet/timesync/CMakeLists.txt | 1 + test/bacnet/wp/CMakeLists.txt | 1 + 80 files changed, 5369 insertions(+), 347 deletions(-) create mode 100644 ports/xplained/Makefile create mode 100644 src/bacnet/basic/object/color_object.c create mode 100644 src/bacnet/basic/object/color_object.h create mode 100644 src/bacnet/basic/object/color_temperature.c create mode 100644 src/bacnet/basic/object/color_temperature.h create mode 100644 src/bacnet/basic/sys/color_rgb.c create mode 100644 src/bacnet/basic/sys/color_rgb.h create mode 100644 test/bacnet/basic/object/color_object/CMakeLists.txt create mode 100644 test/bacnet/basic/object/color_object/src/main.c create mode 100644 test/bacnet/basic/object/color_temperature/CMakeLists.txt create mode 100644 test/bacnet/basic/object/color_temperature/src/main.c create mode 100644 test/bacnet/basic/sys/color_rgb/CMakeLists.txt create mode 100644 test/bacnet/basic/sys/color_rgb/src/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 7cd47231..84fab93c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,14 @@ option( set(BACNET_PROTOCOL_REVISION 19) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +set(CMAKE_CXX_FLAGS "-Wall -Wextra") +set(CMAKE_CXX_FLAGS_DEBUG "-g") +set(CMAKE_CXX_FLAGS_RELEASE "-O3") + # # library # @@ -159,6 +167,10 @@ add_library(${PROJECT_NAME} src/bacnet/basic/object/bv.h src/bacnet/basic/object/channel.c src/bacnet/basic/object/channel.h + src/bacnet/basic/object/color_object.c + src/bacnet/basic/object/color_object.h + src/bacnet/basic/object/color_temperature.c + src/bacnet/basic/object/color_temperature.h # src/bacnet/basic/object/client/device-client.c src/bacnet/basic/object/command.c src/bacnet/basic/object/command.h @@ -310,6 +322,8 @@ add_library(${PROJECT_NAME} src/bacnet/basic/services.h src/bacnet/basic/sys/bigend.c src/bacnet/basic/sys/bigend.h + src/bacnet/basic/sys/color_rgb.c + src/bacnet/basic/sys/color_rgb.h src/bacnet/basic/sys/days.c src/bacnet/basic/sys/days.h src/bacnet/basic/sys/debug.c @@ -715,6 +729,7 @@ message(STATUS "BACNET: CMAKE_C_COMPILER_ID:............\"${CMAKE_C_COMPILER_ID} message(STATUS "BACNET: CMAKE_C_COMPILER_VERSION:.......\"${CMAKE_C_COMPILER_VERSION}\"") message(STATUS "BACNET: CMAKE_CXX_COMPILER_ID:..........\"${CMAKE_CXX_COMPILER_ID}\"") message(STATUS "BACNET: CMAKE_CXX_COMPILER_VERSION:.....\"${CMAKE_CXX_COMPILER_VERSION}\"") +message(STATUS "BACNET: CMAKE_BUILD_TYPE:...............\"${CMAKE_BUILD_TYPE}\"") message(STATUS "BACNET: CMAKE_INSTALL_PREFIX:...........\"${CMAKE_INSTALL_PREFIX}\"") message(STATUS "BACNET: BACNET_PROTOCOL_REVISION:.......\"${BACNET_PROTOCOL_REVISION}\"") message(STATUS "BACNET: BACDL_BIP6:.....................\"${BACDL_BIP6}\"") diff --git a/Makefile b/Makefile index f43fad61..b9b43a6e 100644 --- a/Makefile +++ b/Makefile @@ -45,12 +45,18 @@ apps: lib: $(MAKE) -s -C apps $@ +CMAKE_BUILD_DIR=build .PHONY: cmake cmake: - CMAKE_BUILD_DIR=build [ -d $(CMAKE_BUILD_DIR) ] || mkdir -p $(CMAKE_BUILD_DIR) [ -d $(CMAKE_BUILD_DIR) ] && cd $(CMAKE_BUILD_DIR) && cmake .. -DBUILD_SHARED_LIBS=ON && cmake --build . --clean-first +.PHONY: cmake-win32 +cmake-win32: + mkdir -p $(CMAKE_BUILD_DIR) + cd $(CMAKE_BUILD_DIR) && cmake ../ -DBACNET_STACK_BUILD_APPS=ON && cmake --build ./ --clean-first + cp $(CMAKE_BUILD_DIR)/Debug/*.exe ./bin/. + .PHONY: abort abort: $(MAKE) -s -C apps $@ @@ -243,6 +249,7 @@ splint: CPPCHECK_OPTIONS = --enable=warning,portability CPPCHECK_OPTIONS += --template=gcc +CPPCHECK_OPTIONS += --inline-suppr CPPCHECK_OPTIONS += --suppress=selfAssignment CPPCHECK_OPTIONS += --suppress=integerOverflow CPPCHECK_OPTIONS += --error-exitcode=1 diff --git a/apps/Makefile b/apps/Makefile index 137a4346..7280677a 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -112,7 +112,7 @@ BACNET_DEFINES += -DBACFILE BACNET_DEFINES += -DINTRINSIC_REPORTING BACNET_DEFINES += -DBACNET_TIME_MASTER BACNET_DEFINES += -DBACNET_PROPERTY_LISTS=1 -BACNET_DEFINES += -DBACNET_PROTOCOL_REVISION=17 +BACNET_DEFINES += -DBACNET_PROTOCOL_REVISION=24 # put all the flags together INCLUDES = -I$(BACNET_SRC_DIR) -I$(BACNET_PORT_DIR) diff --git a/apps/gateway/Makefile b/apps/gateway/Makefile index f1421b34..9f9c28f8 100644 --- a/apps/gateway/Makefile +++ b/apps/gateway/Makefile @@ -14,6 +14,8 @@ SRC = main.c \ $(BACNET_OBJECT_DIR)/bo.c \ $(BACNET_OBJECT_DIR)/bv.c \ $(BACNET_OBJECT_DIR)/channel.c \ + $(BACNET_OBJECT_DIR)/color_object.c \ + $(BACNET_OBJECT_DIR)/color_temperature.c \ $(BACNET_OBJECT_DIR)/command.c \ $(BACNET_OBJECT_DIR)/csv.c \ $(BACNET_OBJECT_DIR)/device.c \ diff --git a/apps/server/Makefile b/apps/server/Makefile index 9ae38519..e2796d27 100644 --- a/apps/server/Makefile +++ b/apps/server/Makefile @@ -13,6 +13,8 @@ SRC = main.c \ $(BACNET_OBJECT_DIR)/bo.c \ $(BACNET_OBJECT_DIR)/bv.c \ $(BACNET_OBJECT_DIR)/channel.c \ + $(BACNET_OBJECT_DIR)/color_object.c \ + $(BACNET_OBJECT_DIR)/color_temperature.c \ $(BACNET_OBJECT_DIR)/command.c \ $(BACNET_OBJECT_DIR)/csv.c \ $(BACNET_OBJECT_DIR)/iv.c \ diff --git a/ports/at91sam7s/Makefile b/ports/at91sam7s/Makefile index 9db9fb54..152b7e71 100644 --- a/ports/at91sam7s/Makefile +++ b/ports/at91sam7s/Makefile @@ -30,10 +30,14 @@ BACNET_BASIC = $(BACNET_CORE)/basic INCLUDES = -I. -I$(BACNET_SRC) #OPTIMIZATION = -O0 OPTIMIZATION = -Os -CFLAGS = -fno-common $(INCLUDES) $(BACNET_FLAGS) -Wall -g +CFLAGS = -fno-common $(INCLUDES) $(BACNET_FLAGS) -g CFLAGS += -mno-thumb-interwork # dead code removal CFLAGS += -fdata-sections -ffunction-sections +# warning control +CFLAGS += -Wall +CFLAGS += -Wno-char-subscripts + LIBRARY = lib$(TARGET).a # -Wa, Pass comma-separated on to the assembler AFLAGS = -Wa,-ahls,-mapcs-32,-adhlns=$(<:.s=.lst) @@ -46,6 +50,7 @@ LDFLAGS += -Wl,--gc-sections,-static CPFLAGS = --output-target=binary ODFLAGS = -x --syms + ASRC = crt.s PORTSRC = main.c \ @@ -100,6 +105,7 @@ CORESRC = $(BACNET_CORE)/abort.c \ $(BACNET_CORE)/reject.c \ $(BACNET_CORE)/rp.c \ $(BACNET_CORE)/rpm.c \ + $(BACNET_CORE)/timestamp.c \ $(BACNET_CORE)/whohas.c \ $(BACNET_CORE)/whois.c \ $(BACNET_CORE)/wp.c diff --git a/ports/at91sam7s/bacnet.ewp b/ports/at91sam7s/bacnet.ewp index bd4ee565..7574b68b 100644 --- a/ports/at91sam7s/bacnet.ewp +++ b/ports/at91sam7s/bacnet.ewp @@ -1181,6 +1181,9 @@ $PROJ_DIR$\..\..\src\bacnet\rpm.c + + $PROJ_DIR$\..\..\src\bacnet\timestamp.c + $PROJ_DIR$\..\..\src\bacnet\whohas.c diff --git a/ports/atmega168/Makefile b/ports/atmega168/Makefile index 224cd3ba..93e8b0ae 100644 --- a/ports/atmega168/Makefile +++ b/ports/atmega168/Makefile @@ -112,6 +112,8 @@ CFLAGS = $(COMMON) CFLAGS += -ffunction-sections -fdata-sections CFLAGS += -Wall -gdwarf-2 $(BFLAGS) $(OPTIMIZATION) -fsigned-char CFLAGS += -MD -MP -MT $(*F).o -MF dep/$(@F).d +# silence some warnings +CFLAGS += -Wno-switch ## Assembly specific flags ASMFLAGS = $(COMMON) diff --git a/ports/bdk-atxx4-mstp/Makefile b/ports/bdk-atxx4-mstp/Makefile index 33e1f505..a32a09aa 100644 --- a/ports/bdk-atxx4-mstp/Makefile +++ b/ports/bdk-atxx4-mstp/Makefile @@ -127,6 +127,7 @@ CORESRC = \ $(BACNET_CORE)/bacint.c \ $(BACNET_CORE)/bacreal.c \ $(BACNET_CORE)/bacstr.c \ + $(BACNET_CORE)/datetime.c \ $(BACNET_CORE)/dcc.c \ $(BACNET_CORE)/iam.c \ $(BACNET_CORE)/ihave.c \ @@ -237,6 +238,7 @@ CFLAGS += -fshort-enums CFLAGS += -Wall CFLAGS += -Wstrict-prototypes CFLAGS += -Wmissing-prototypes +CFLAGS += -Wno-char-subscripts # put it all together CFLAGS += -gdwarf-2 $(BFLAGS) $(OPTIMIZATION) CFLAGS += -MD -MP -MT $(*F).o -MF dep/$(@F).d diff --git a/ports/linux/bip-init.c b/ports/linux/bip-init.c index 38afb863..0d206e4c 100644 --- a/ports/linux/bip-init.c +++ b/ports/linux/bip-init.c @@ -745,7 +745,6 @@ void bip_set_interface(char *ifname) { struct in_addr local_address; struct in_addr netmask; - short flags; int rv = 0; /* setup local address */ diff --git a/ports/stm32f10x/Makefile b/ports/stm32f10x/Makefile index b306f72d..9efc6ed8 100644 --- a/ports/stm32f10x/Makefile +++ b/ports/stm32f10x/Makefile @@ -61,6 +61,7 @@ BACNET_SRC = \ $(BACNET_CORE)/bacreal.c \ $(BACNET_CORE)/bacstr.c \ $(BACNET_CORE)/datalink/crc.c \ + $(BACNET_CORE)/datetime.c \ $(BACNET_CORE)/dcc.c \ $(BACNET_CORE)/iam.c \ $(BACNET_CORE)/ihave.c \ @@ -73,6 +74,7 @@ BACNET_SRC = \ $(BACNET_CORE)/reject.c \ $(BACNET_CORE)/rp.c \ $(BACNET_CORE)/rpm.c \ + $(BACNET_CORE)/timestamp.c \ $(BACNET_CORE)/whohas.c \ $(BACNET_CORE)/whois.c \ $(BACNET_CORE)/wp.c @@ -158,6 +160,8 @@ CFLAGS += -Wall CFLAGS += -Wno-missing-braces # don't warn about missing prototypes since STM32 library doesn't have some CFLAGS += -Wno-missing-prototypes +# don't warn about array subscript being char +CFLAGS += -Wno-char-subscripts # -Wa, Pass comma-separated on to the assembler AFLAGS = -Wa,-ahls,-mapcs-32,-adhlns=$(<:.s=.lst) diff --git a/ports/stm32f10x/bacnet.ewp b/ports/stm32f10x/bacnet.ewp index 7c7b4416..e3ce92a6 100644 --- a/ports/stm32f10x/bacnet.ewp +++ b/ports/stm32f10x/bacnet.ewp @@ -1111,6 +1111,9 @@ $PROJ_DIR$\..\..\src\bacnet\rpm.c + + $PROJ_DIR$\..\..\src\bacnet\timestamp.c + $PROJ_DIR$\..\..\src\bacnet\whohas.c diff --git a/ports/stm32f4xx/Makefile b/ports/stm32f4xx/Makefile index d393319a..30e5a550 100644 --- a/ports/stm32f4xx/Makefile +++ b/ports/stm32f4xx/Makefile @@ -59,6 +59,7 @@ BACNET_SRC = \ $(BACNET_CORE)/bacreal.c \ $(BACNET_CORE)/bacstr.c \ $(BACNET_CORE)/datalink/crc.c \ + $(BACNET_CORE)/datetime.c \ $(BACNET_CORE)/dcc.c \ $(BACNET_CORE)/iam.c \ $(BACNET_CORE)/ihave.c \ @@ -71,6 +72,7 @@ BACNET_SRC = \ $(BACNET_CORE)/reject.c \ $(BACNET_CORE)/rp.c \ $(BACNET_CORE)/rpm.c \ + $(BACNET_CORE)/timestamp.c \ $(BACNET_CORE)/whohas.c \ $(BACNET_CORE)/whois.c \ $(BACNET_CORE)/wp.c @@ -155,6 +157,8 @@ CFLAGS += -Wall CFLAGS += -Wno-missing-braces # don't warn about missing prototypes since STM32 library doesn't have some CFLAGS += -Wno-missing-prototypes +# don't warn about array subscript being char +CFLAGS += -Wno-char-subscripts # -Wa, Pass comma-separated on to the assembler AFLAGS = -Wa,-ahls,-mapcs-32,-adhlns=$(<:.s=.lst) diff --git a/ports/stm32f4xx/bacnet.ewp b/ports/stm32f4xx/bacnet.ewp index 1977e427..d6e3d7aa 100644 --- a/ports/stm32f4xx/bacnet.ewp +++ b/ports/stm32f4xx/bacnet.ewp @@ -1117,6 +1117,9 @@ $PROJ_DIR$\..\..\src\bacnet\rpm.c + + $PROJ_DIR$\..\..\src\bacnet\timestamp.c + $PROJ_DIR$\..\..\src\bacnet\timesync.c diff --git a/ports/xplained/Makefile b/ports/xplained/Makefile new file mode 100644 index 00000000..a73a3c70 --- /dev/null +++ b/ports/xplained/Makefile @@ -0,0 +1,237 @@ +############################################################################### +# Makefile for BACnet +############################################################################### + +## General Flags +MCU = atxmega256a3bu +TARGET = bacnet +## Tools +#Set the toolchain command names (only the ones needed are defined) +PREFIX ?= "C:\Program Files (x86)\Atmel\Studio\7.0\toolchain\avr8\avr8-gnu-toolchain\bin\avr-" + +CC = $(PREFIX)gcc +OBJCOPY = $(PREFIX)objcopy +OBJDUMP = $(PREFIX)objdump +AR = $(PREFIX)ar +SIZE = $(PREFIX)size + +# Source locations +SDK_DIR = ASF +BACNET_DIR = ../../src +BACNET_CORE = $(BACNET_DIR)/bacnet +BACNET_BASIC = $(BACNET_CORE)/basic + +# local files for this project +CSRC = main.c \ + adc-hdw.c \ + ai.c \ + mstimer-init.c \ + device.c \ + dlmstp.c \ + netport.c \ + stack.c \ + rs485.c \ + led.c + +# common demo files needed +BASICSRC = $(BACNET_BASIC)/tsm/tsm.c \ + $(BACNET_BASIC)/sys/bigend.c \ + $(BACNET_BASIC)/sys/fifo.c \ + $(BACNET_BASIC)/sys/ringbuf.c \ + $(BACNET_BASIC)/sys/mstimer.c \ + $(BACNET_BASIC)/npdu/h_npdu.c \ + $(BACNET_BASIC)/service/h_apdu.c \ + $(BACNET_BASIC)/service/h_rd.c \ + $(BACNET_BASIC)/service/h_rp.c \ + $(BACNET_BASIC)/service/h_rpm.c \ + $(BACNET_BASIC)/service/h_whois.c \ + $(BACNET_BASIC)/service/h_whohas.c \ + $(BACNET_BASIC)/service/s_cov.c \ + $(BACNET_BASIC)/service/s_iam.c \ + $(BACNET_BASIC)/service/s_ihave.c \ + $(BACNET_BASIC)/service/s_rp.c \ + $(BACNET_BASIC)/service/s_whois.c \ + $(BACNET_BASIC)/service/s_wp.c \ + $(BACNET_BASIC)/service/h_noserv.c + +# core BACnet stack files +CORESRC = \ + $(BACNET_CORE)/datalink/crc.c \ + $(BACNET_CORE)/abort.c \ + $(BACNET_CORE)/bacaddr.c \ + $(BACNET_CORE)/bacapp.c \ + $(BACNET_CORE)/bacdcode.c \ + $(BACNET_CORE)/bacerror.c \ + $(BACNET_CORE)/bacint.c \ + $(BACNET_CORE)/bacreal.c \ + $(BACNET_CORE)/bacstr.c \ + $(BACNET_CORE)/datetime.c \ + $(BACNET_CORE)/dcc.c \ + $(BACNET_CORE)/iam.c \ + $(BACNET_CORE)/ihave.c \ + $(BACNET_CORE)/memcopy.c \ + $(BACNET_CORE)/hostnport.c \ + $(BACNET_CORE)/npdu.c \ + $(BACNET_CORE)/lighting.c \ + $(BACNET_CORE)/proplist.c \ + $(BACNET_CORE)/rd.c \ + $(BACNET_CORE)/reject.c \ + $(BACNET_CORE)/rp.c \ + $(BACNET_CORE)/rpm.c \ + $(BACNET_CORE)/wp.c \ + $(BACNET_CORE)/whohas.c \ + $(BACNET_CORE)/whois.c + +SDK_CSRC = \ + $(SDK_DIR)/xmega/drivers/tc/tc.c \ + $(SDK_DIR)/common/services/clock/xmega/sysclk.c \ + $(SDK_DIR)/common/services/ioport/xmega/ioport_compat.c \ + $(SDK_DIR)/common/services/sleepmgr/xmega/sleepmgr.c \ + $(SDK_DIR)/xmega/boards/xmega_a3bu_xplained/init.c \ + $(SDK_DIR)/xmega/drivers/adc/adc.c \ + $(SDK_DIR)/xmega/drivers/adc/xmega_aau/adc_aau.c \ + $(SDK_DIR)/xmega/drivers/rtc32/rtc32.c \ + $(SDK_DIR)/xmega/drivers/tc/tc.c \ + $(SDK_DIR)/common/drivers/nvm/xmega/xmega_nvm.c \ + $(SDK_DIR)/common/services/serial/usart_serial.c \ + $(SDK_DIR)/common/utils/stdio/read.c \ + $(SDK_DIR)/common/utils/stdio/write.c \ + $(SDK_DIR)/xmega/drivers/nvm/nvm.c \ + $(SDK_DIR)/xmega/drivers/usart/usart.c \ + $(SDK_DIR)/xmega/drivers/wdt/wdt.c \ + $(SDK_DIR)/xmega/services/pwm/pwm.c \ + $(SDK_DIR)/xmega/services/timeout/timeout.c + +SDK_ASRC = \ + $(SDK_DIR)/xmega/drivers/cpu/ccp.s \ + $(SDK_DIR)/xmega/drivers/nvm/nvm_asm.s + +## Include Directories +SDK_INCLUDES = -I$(SDK_DIR)/xmega/drivers/rtc32 +SDK_INCLUDES += -I$(SDK_DIR)/xmega/drivers/pmic +SDK_INCLUDES += -I$(SDK_DIR)/xmega/boards/xmega_a3bu_xplained +SDK_INCLUDES += -I$(SDK_DIR)/xmega/utils/preprocessor +SDK_INCLUDES += -I$(SDK_DIR)/common/utils +SDK_INCLUDES += -I$(SDK_DIR)/common/services/sleepmgr +SDK_INCLUDES += -I$(SDK_DIR)/xmega/drivers/sleep +SDK_INCLUDES += -I$(SDK_DIR)/common/services/gpio +SDK_INCLUDES += -I$(SDK_DIR)/xmega/drivers/tc +SDK_INCLUDES += -I$(SDK_DIR)/xmega/drivers/adc +SDK_INCLUDES += -I$(SDK_DIR)/xmega/drivers/cpu +SDK_INCLUDES += -I$(SDK_DIR)/common/boards +SDK_INCLUDES += -I$(SDK_DIR)/common/services/ioport +SDK_INCLUDES += -I$(SDK_DIR)/xmega/drivers/nvm +SDK_INCLUDES += -I$(SDK_DIR)/xmega/boards +SDK_INCLUDES += -I$(SDK_DIR)/xmega/utils +SDK_INCLUDES += -I$(SDK_DIR)/xmega/drivers/wdt +SDK_INCLUDES += -I$(SDK_DIR)/common/services/clock +SDK_INCLUDES += -I$(SDK_DIR)/common/services/delay +SDK_INCLUDES += -I$(SDK_DIR)/xmega/drivers/usart +SDK_INCLUDES += -I$(SDK_DIR)/xmega/services/pwm +SDK_INCLUDES += -I$(SDK_DIR)/common/drivers/nvm +SDK_INCLUDES += -I$(SDK_DIR)/common/services/serial/xmega_usart +SDK_INCLUDES += -I$(SDK_DIR)/common/services/serial +SDK_INCLUDES += -I$(SDK_DIR)/common/utils/stdio/stdio_serial +SDK_INCLUDES += -I$(SDK_DIR)/xmega/services/timeout + +INCLUDES = -I. -I./config -I$(BACNET_DIR) $(SDK_INCLUDES) + +# Source to Object conversion +COBJ = $(CSRC:.c=.o) +BASICOBJ = $(BASICSRC:.c=.o) +COREOBJ = $(CORESRC:.c=.o) +SDK_COBJ = $(SDK_CSRC:.c=.o) +SDK_AOBJ = $(SDK_ASRC:.s=.o) + +LIBRARY = lib$(TARGET).a + +## Options common to compile, link and assembly rules +SDK_FLAGS = -mmcu=$(MCU) + +OPTIMIZATION = -Os -g3 + +## Compile options common for all C compilation units. +BFLAGS = -DBACDL_MSTP +BFLAGS += -DMAX_APDU=128 +BFLAGS += -DMAX_TSM_TRANSACTIONS=1 +BFLAGS += -DMSTP_PDU_PACKET_COUNT=2 +BFLAGS += -DMAX_ADDRESS_CACHE=32 +BFLAGS += -DMAX_ANALOG_INPUTS=8 +BFLAGS += -DBACNET_PROTOCOL_REVISION=9 + +CFLAGS = $(SDK_FLAGS) +# dead code removal +CFLAGS += -ffunction-sections -fdata-sections +CFLAGS += -fpack-struct -fshort-enums -mrelax +CFLAGS += -Wall -gdwarf-2 $(BFLAGS) $(OPTIMIZATION) +CFLAGS += -fsigned-char -std=gnu99 +CFLAGS += -fno-strict-aliasing +CFLAGS += -Wstrict-prototypes +CFLAGS += -Wmissing-prototypes +CFLAGS += -Werror-implicit-function-declaration +CFLAGS += -Wpointer-arith +CFLAGS += -MD -MP -MT $(*F).o -MF dep/$(@F).d +CFLAGS += -DIOPORT_XMEGA_COMPAT +# silence some warnings +CFLAGS += -Wno-switch + +## Assembly specific flags +AFLAGS = -Wa,-gdwarf2 + +## Linker flags +LDFLAGS = $(SDK_FLAGS) +#dead code removal +#LDFLAGS += -Wl,-nostartfiles,-nostdlib +LDFLAGS += -Wl,--gc-sections,-static +LDFLAGS += -Wl,-Map=$(TARGET).map,-L.,-l$(TARGET) +#LDFLAGS += -Wl,-Map=$(TARGET).map + +## Intel Hex file production flags +HEX_FLASH_FLAGS = -R .eeprom +HEX_EEPROM_FLAGS = -j .eeprom +HEX_EEPROM_FLAGS += --set-section-flags=.eeprom="alloc,load" +HEX_EEPROM_FLAGS += --change-section-lma .eeprom=0 --no-change-warnings + +## Objects that must be built in order to link +OBJECTS = $(COBJ) $(BASICOBJ) $(SDK_COBJ) $(SDK_AOBJ) + +## Build +TARGET_ELF=$(TARGET).elf + +all: $(LIBRARY) $(TARGET_ELF) $(TARGET).hex $(TARGET).lst Makefile size + +##Link +$(TARGET_ELF): $(OBJECTS) $(LIBRARY) + $(CC) $(OBJECTS) $(LDFLAGS) -o $@ + +%.hex: $(TARGET_ELF) + $(OBJCOPY) -O ihex $(HEX_FLASH_FLAGS) $< $@ + +%.lst: $(TARGET_ELF) + $(OBJDUMP) -h -S $< > $@ + +lib: $(LIBRARY) + +$(LIBRARY): $(COREOBJ) Makefile + $(AR) rcs $@ $(COREOBJ) + $(OBJDUMP) --syms $@ > $(LIBRARY:.a=.lst) + +.c.o: + $(CC) -c $(INCLUDES) $(CFLAGS) $*.c -o $@ + +.s.o: + $(CC) -c $(AFLAGS) $*.s -o $@ + +size: ${TARGET_ELF} + @echo + @${SIZE} ${TARGET_ELF} + +## Clean target +.PHONY: clean +clean: + -rm -rf $(OBJECTS) $(TARGET_ELF) dep/* + -rm -rf $(LIBRARY) $(COREOBJ) $(LIBRARY:.a=.lst) + -rm -rf $(TARGET).hex $(TARGET).lst $(TARGET).map + +## Other dependencies +-include $(shell mkdir dep 2>/dev/null) $(wildcard dep/*) diff --git a/ports/xplained/bacnet.cproj b/ports/xplained/bacnet.cproj index 1685112c..198152b2 100644 --- a/ports/xplained/bacnet.cproj +++ b/ports/xplained/bacnet.cproj @@ -27,144 +27,144 @@ 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + com.atmel.avrdbg.tool.jtagice3plus JTAG @@ -586,6 +586,10 @@ compile + + compile + bacnet-stack\datetime.c + compile bacnet-stack\hostnport.c diff --git a/src/bacnet/bacapp.c b/src/bacnet/bacapp.c index 6aa0c355..437f8b67 100644 --- a/src/bacnet/bacapp.c +++ b/src/bacnet/bacapp.c @@ -152,11 +152,25 @@ int bacapp_encode_application_data( break; break; + case BACNET_APPLICATION_TAG_DATETIME: + apdu_len = bacapp_encode_datetime(apdu, + &value->type.Date_Time); + break; case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: /* BACnetLightingCommand */ apdu_len = lighting_command_encode( apdu, &value->type.Lighting_Command); break; + case BACNET_APPLICATION_TAG_XY_COLOR: + /* BACnetxyColor */ + apdu_len = xy_color_encode( + apdu, &value->type.XY_Color); + break; + case BACNET_APPLICATION_TAG_COLOR_COMMAND: + /* BACnetColorCommand */ + apdu_len = color_command_encode( + apdu, &value->type.Color_Command); + break; case BACNET_APPLICATION_TAG_HOST_N_PORT: /* BACnetHostNPort */ apdu_len = host_n_port_encode(apdu, @@ -286,10 +300,24 @@ int bacapp_decode_data(uint8_t *apdu, } break; #endif #if defined (BACAPP_TYPES_EXTRA) + case BACNET_APPLICATION_TAG_DATETIME: + len = bacapp_decode_datetime(apdu, + &value->type.Date_Time); + break; case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: len = lighting_command_decode( apdu, len_value_type, &value->type.Lighting_Command); break; + case BACNET_APPLICATION_TAG_XY_COLOR: + /* BACnetxyColor */ + len = xy_color_decode( + apdu, len_value_type, &value->type.XY_Color); + break; + case BACNET_APPLICATION_TAG_COLOR_COMMAND: + /* BACnetColorCommand */ + len = color_command_decode( + apdu, len_value_type, NULL, &value->type.Color_Command); + break; case BACNET_APPLICATION_TAG_HOST_N_PORT: len = host_n_port_decode( apdu, len_value_type, NULL, @@ -597,10 +625,24 @@ int bacapp_encode_context_data_value(uint8_t *apdu, break; #endif #if defined (BACAPP_TYPES_EXTRA) + case BACNET_APPLICATION_TAG_DATETIME: + apdu_len = bacapp_encode_context_datetime(apdu, + context_tag_number, &value->type.Date_Time); + break; case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: apdu_len = lighting_command_encode_context(apdu, context_tag_number, &value->type.Lighting_Command); break; + case BACNET_APPLICATION_TAG_XY_COLOR: + /* BACnetxyColor */ + apdu_len = xy_color_context_encode( + apdu, context_tag_number, &value->type.XY_Color); + break; + case BACNET_APPLICATION_TAG_COLOR_COMMAND: + /* BACnetColorCommand */ + apdu_len = color_command_context_encode( + apdu, context_tag_number, &value->type.Color_Command); + break; case BACNET_APPLICATION_TAG_HOST_N_PORT: apdu_len = host_n_port_context_encode(apdu, context_tag_number, &value->type.Host_Address); @@ -811,6 +853,15 @@ BACNET_APPLICATION_TAG bacapp_context_tag_type( break; } break; + case PROP_COLOR_COMMAND: + switch (tag_number) { + case 0: + tag = BACNET_APPLICATION_TAG_COLOR_COMMAND; + break; + default: + break; + } + break; case PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES: case PROP_GROUP_MEMBERS: switch (tag_number) { @@ -920,6 +971,153 @@ int bacapp_decode_generic_property( } #endif +#if defined(BACAPP_TYPES_EXTRA) +/** + * @brief Decodes a well-known, possibly complex property value + * Used to reverse operations in bacapp_encode_application_data + * @param apdu - buffer of data to be decoded + * @param max_apdu_len - number of bytes in the buffer + * @param value - stores the decoded property value + * @param property - context property identifier + * @return number of bytes decoded, or ERROR if errors occur + */ +int bacapp_decode_known_property(uint8_t *apdu, + int max_apdu_len, + BACNET_APPLICATION_DATA_VALUE *value, + BACNET_OBJECT_TYPE object_type, + BACNET_PROPERTY_ID property) +{ + int len = 0; + + switch (property) { + case PROP_MEMBER_OF: + case PROP_ZONE_MEMBERS: + case PROP_DOOR_MEMBERS: + case PROP_SUBORDINATE_LIST: + case PROP_ACCESS_EVENT_CREDENTIAL: + case PROP_ACCESS_DOORS: + case PROP_ZONE_FROM: + case PROP_ZONE_TO: + case PROP_CREDENTIALS_IN_ZONE: + case PROP_LAST_CREDENTIAL_ADDED: + case PROP_LAST_CREDENTIAL_REMOVED: + case PROP_ENTRY_POINTS: + case PROP_EXIT_POINTS: + case PROP_MEMBERS: + case PROP_CREDENTIALS: + case PROP_ACCOMPANIMENT: + case PROP_BELONGS_TO: + case PROP_LAST_ACCESS_POINT: + /* Properties using BACnetDeviceObjectReference */ + value->tag = BACNET_APPLICATION_TAG_DEVICE_OBJECT_REFERENCE; + len = bacapp_decode_device_obj_ref( + apdu, &value->type.Device_Object_Reference); + break; + case PROP_TIME_OF_ACTIVE_TIME_RESET: + case PROP_TIME_OF_STATE_COUNT_RESET: + case PROP_CHANGE_OF_STATE_TIME: + case PROP_MAXIMUM_VALUE_TIMESTAMP: + case PROP_MINIMUM_VALUE_TIMESTAMP: + case PROP_VALUE_CHANGE_TIME: + case PROP_START_TIME: + case PROP_STOP_TIME: + case PROP_MODIFICATION_DATE: + case PROP_UPDATE_TIME: + case PROP_COUNT_CHANGE_TIME: + case PROP_LAST_CREDENTIAL_ADDED_TIME: + case PROP_LAST_CREDENTIAL_REMOVED_TIME: + case PROP_ACTIVATION_TIME: + case PROP_EXPIRATION_TIME: + case PROP_LAST_USE_TIME: + /* Properties using BACnetDateTime value */ + value->tag = BACNET_APPLICATION_TAG_DATETIME; + len = bacapp_decode_datetime(apdu, &value->type.Date_Time); + break; + case PROP_OBJECT_PROPERTY_REFERENCE: + case PROP_LOG_DEVICE_OBJECT_PROPERTY: + case PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES: + /* Properties using BACnetDeviceObjectPropertyReference */ + value->tag = + BACNET_APPLICATION_TAG_DEVICE_OBJECT_PROPERTY_REFERENCE; + len = bacapp_decode_device_obj_property_ref(apdu, + &value->type.Device_Object_Property_Reference); + break; + case PROP_MANIPULATED_VARIABLE_REFERENCE: + case PROP_CONTROLLED_VARIABLE_REFERENCE: + case PROP_INPUT_REFERENCE: + /* Properties using BACnetObjectPropertyReference */ + value->tag = BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE; + len = bacapp_decode_obj_property_ref( + apdu, max_apdu_len, + &value->type.Object_Property_Reference); + break; + case PROP_EVENT_TIME_STAMPS: + case PROP_LAST_RESTORE_TIME: + case PROP_TIME_OF_DEVICE_RESTART: + case PROP_ACCESS_EVENT_TIME: + /* Properties using BACnetTimeStamp */ + value->tag = BACNET_APPLICATION_TAG_TIMESTAMP; + len = bacapp_decode_timestamp(apdu, &value->type.Time_Stamp); + break; + case PROP_DEFAULT_COLOR: + case PROP_TRACKING_VALUE: + /* Properties using BACnetxyColor */ + value->tag = BACNET_APPLICATION_TAG_XY_COLOR; + len = xy_color_decode(apdu, max_apdu_len, + &value->type.XY_Color); + break; + case PROP_PRESENT_VALUE: + if (object_type == OBJECT_COLOR) { + /* Properties using BACnetxyColor */ + value->tag = BACNET_APPLICATION_TAG_XY_COLOR; + len = xy_color_decode(apdu, max_apdu_len, + &value->type.XY_Color); + } else { + /* Decode a "classic" simple property */ + len = bacapp_decode_generic_property(apdu, max_apdu_len, value, + property); + } + break; + case PROP_COLOR_COMMAND: + /* Properties using BACnetColorCommand */ + value->tag = BACNET_APPLICATION_TAG_COLOR_COMMAND; + len = color_command_decode(apdu, max_apdu_len, NULL, + &value->type.Color_Command); + break; + case PROP_LIST_OF_GROUP_MEMBERS: + /* Properties using ReadAccessSpecification */ + case PROP_WEEKLY_SCHEDULE: + /* BACnetDailySchedule[7] (Schedule) */ + case PROP_EXCEPTION_SCHEDULE: + /* BACnetSpecialEvent (Schedule) */ + case PROP_DATE_LIST: + /* FIXME: Properties using : BACnetCalendarEntry */ + case PROP_ACTIVE_COV_SUBSCRIPTIONS: + /* FIXME: BACnetCOVSubscription */ + case PROP_EFFECTIVE_PERIOD: + /* FIXME: Properties using BACnetDateRange (Schedule) */ + case PROP_RECIPIENT_LIST: + /* FIXME: Properties using BACnetDestination */ + case PROP_TIME_SYNCHRONIZATION_RECIPIENTS: + case PROP_RESTART_NOTIFICATION_RECIPIENTS: + case PROP_UTC_TIME_SYNCHRONIZATION_RECIPIENTS: + /* FIXME: Properties using BACnetRecipient */ + case PROP_DEVICE_ADDRESS_BINDING: + case PROP_MANUAL_SLAVE_ADDRESS_BINDING: + case PROP_SLAVE_ADDRESS_BINDING: + /* FIXME: BACnetAddressBinding */ + case PROP_ACTION: + default: + /* Decode a "classic" simple property */ + len = bacapp_decode_generic_property(apdu, max_apdu_len, value, + property); + break; + } + + return len; +} +#endif + #if defined (BACAPP_TYPES_EXTRA) /** * @brief Determine the BACnet Context Data number of APDU bytes consumed @@ -1153,6 +1351,133 @@ int bacapp_data_len( return total_len; } +#if defined(BACAPP_DATE) +/* 135.1-4.4 Notational Rules for Parameter Values +(j) +dates are represented enclosed in parenthesis: +(Monday, 24-January-1998). +Any "wild card" or unspecified field is shown by an asterisk (X'2A'): +(Monday, *-January-1998). +The omission of day of week implies that the day is unspecified: +(24-January-1998); +*/ +static int bacapp_snprintf_date(char *str, size_t str_len, BACNET_DATE *bdate) +{ + int ret_val = 0; + int slen = 0; + + // false positive cppcheck - snprintf allows null pointers + // cppcheck-suppress nullPointer + // cppcheck-suppress ctunullpointer + slen = snprintf(str, str_len, "%s, %s", + bactext_day_of_week_name(bdate->wday), + bactext_month_name(bdate->month)); + if (str) { + str += slen; + if (str_len >= slen) { + str_len -= slen; + } else { + str_len = 0; + } + } + ret_val += slen; + if (bdate->day == 255) { + slen = snprintf(str, str_len, " (unspecified), "); + } else { + slen = snprintf(str, str_len, " %u, ", + (unsigned)bdate->day); + } + if (str) { + str += slen; + if (str_len >= slen) { + str_len -= slen; + } else { + str_len = 0; + } + } + ret_val += slen; + if (bdate->year == 2155) { + slen = snprintf(str, str_len, "(unspecified)"); + } else { + slen = snprintf(str, str_len, "%u", + (unsigned)bdate->year); + } + ret_val += slen; + + return ret_val; +} +#endif + +#if defined(BACAPP_TIME) +/* 135.1-4.4 Notational Rules for Parameter Values +(k) +times are represented as hours, minutes, seconds, hundredths in the format +hh:mm:ss.xx: 2:05:44.00, 16:54:59.99. Any "wild card" field is shown by an +asterisk (X'2A'): 16:54:*.*; */ +static int bacapp_snprintf_time(char *str, size_t str_len, BACNET_TIME *btime) +{ + int ret_val = 0; + int slen = 0; + + if (btime->hour == 255) { + slen = snprintf(str, str_len, "**:"); + } else { + // false positive cppcheck - snprintf allows null pointers + // cppcheck-suppress nullPointer + slen = snprintf(str, str_len, "%02u:", + (unsigned)btime->hour); + } + if (str) { + str += slen; + if (str_len >= slen) { + str_len -= slen; + } else { + str_len = 0; + } + } + ret_val += slen; + if (btime->min == 255) { + slen = snprintf(str, str_len, "**:"); + } else { + slen = snprintf(str, str_len, "%02u:", + (unsigned)btime->min); + } + if (str) { + str += slen; + if (str_len >= slen) { + str_len -= slen; + } else { + str_len = 0; + } + } + ret_val += slen; + if (btime->sec == 255) { + slen = snprintf(str, str_len, "**."); + } else { + slen = snprintf(str, str_len, "%02u.", + (unsigned)btime->sec); + } + if (str) { + str += slen; + if (str_len >= slen) { + str_len -= slen; + } else { + str_len = 0; + } + } + ret_val += slen; + if (btime->hundredths == 255) { + slen = snprintf(str, str_len, "**"); + } else { + slen = snprintf(str, str_len, "%02u", + (unsigned)btime->hundredths); + } + ret_val += slen; + + return ret_val; +} +#endif + /** * @brief Extract the value into a text string * @param str - the buffer to store the extracted value, or NULL for length @@ -1438,96 +1763,14 @@ int bacapp_snprintf_value( #endif #if defined(BACAPP_DATE) case BACNET_APPLICATION_TAG_DATE: - slen = snprintf(str, str_len, "%s, %s", - bactext_day_of_week_name(value->type.Date.wday), - bactext_month_name(value->type.Date.month)); - if (str) { - str += slen; - if (str_len >= slen) { - str_len -= slen; - } else { - str_len = 0; - } - } - ret_val += slen; - if (value->type.Date.day == 255) { - slen = snprintf(str, str_len, " (unspecified), "); - } else { - slen = snprintf(str, str_len, " %u, ", - (unsigned)value->type.Date.day); - } - if (str) { - str += slen; - if (str_len >= slen) { - str_len -= slen; - } else { - str_len = 0; - } - } - ret_val += slen; - if (value->type.Date.year == 2155) { - slen = snprintf(str, str_len, "(unspecified)"); - } else { - slen = snprintf(str, str_len, "%u", - (unsigned)value->type.Date.year); - } - ret_val += slen; + ret_val = bacapp_snprintf_date(str, str_len, + &value->type.Date); break; #endif #if defined(BACAPP_TIME) case BACNET_APPLICATION_TAG_TIME: - if (value->type.Time.hour == 255) { - slen = snprintf(str, str_len, "**:"); - } else { - slen= snprintf(str, str_len, "%02u:", - (unsigned)value->type.Time.hour); - } - if (str) { - str += slen; - if (str_len >= slen) { - str_len -= slen; - } else { - str_len = 0; - } - } - ret_val += slen; - if (value->type.Time.min == 255) { - slen = snprintf(str, str_len, "**:"); - } else { - slen = snprintf(str, str_len, "%02u:", - (unsigned)value->type.Time.min); - } - if (str) { - str += slen; - if (str_len >= slen) { - str_len -= slen; - } else { - str_len = 0; - } - } - ret_val += slen; - if (value->type.Time.sec == 255) { - slen = snprintf(str, str_len, "**."); - } else { - slen = snprintf(str, str_len, "%02u.", - (unsigned)value->type.Time.sec); - } - if (str) { - str += slen; - if (str_len >= slen) { - str_len -= slen; - } else { - str_len = 0; - } - } - ret_val += slen; - if (value->type.Time.hundredths == 255) { - slen = snprintf(str, str_len, "**"); - } else { - slen = snprintf(str, str_len, "%02u", - (unsigned)value->type.Time.hundredths); - } - ret_val += slen; + ret_val = bacapp_snprintf_time(str, str_len, + &value->type.Time); break; #endif #if defined(BACAPP_OBJECT_ID) @@ -1567,6 +1810,22 @@ int bacapp_snprintf_value( break; #endif #if defined (BACAPP_TYPES_EXTRA) + case BACNET_APPLICATION_TAG_DATETIME: + slen = bacapp_snprintf_date(str, str_len, + &value->type.Date); + ret_val += slen; + if (str) { + str += slen; + if (str_len >= slen) { + str_len -= slen; + } else { + str_len = 0; + } + } + slen = bacapp_snprintf_time(str, str_len, + &value->type.Time); + ret_val += slen; + break; case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: slen = snprintf(str, str_len, "("); if (str) { @@ -1594,6 +1853,40 @@ int bacapp_snprintf_value( slen = snprintf(str, str_len, ")"); ret_val += slen; 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); + break; + case BACNET_APPLICATION_TAG_COLOR_COMMAND: + /* BACnetColorCommand */ + slen = snprintf(str, str_len, "("); + if (str) { + str += slen; + if (str_len >= slen) { + str_len -= slen; + } else { + str_len = 0; + } + } + ret_val += slen; + slen = snprintf(str, str_len, "%s", + bactext_color_operation_name( + value->type.Color_Command.operation)); + if (str) { + str += slen; + if (str_len >= slen) { + str_len -= slen; + } else { + str_len = 0; + } + } + ret_val += slen; + /* FIXME: add the Lighting Command optional values */ + slen = snprintf(str, str_len, ")"); + ret_val += slen; + break; case BACNET_APPLICATION_TAG_HOST_N_PORT: if (value->type.Host_Address.host_ip_address) { octet_str = octetstring_value( @@ -1712,6 +2005,7 @@ 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)) { @@ -1830,6 +2124,20 @@ bool bacapp_parse_application_data(BACNET_APPLICATION_TAG tag_number, case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: /* FIXME: add parsing for lighting command */ 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; + } + break; + case BACNET_APPLICATION_TAG_COLOR_COMMAND: + /* FIXME: add parsing for BACnetColorCommand */ + break; case BACNET_APPLICATION_TAG_HOST_N_PORT: count = sscanf(argv, "%3u.%3u.%3u.%3u:%5u", &a[0], &a[1], &a[2], &a[3], &p); @@ -2025,10 +2333,26 @@ bool bacapp_same_value(BACNET_APPLICATION_DATA_VALUE *value, break; #endif #if defined (BACAPP_TYPES_EXTRA) + case BACNET_APPLICATION_TAG_DATETIME: + if (datetime_compare(&value->type.Date_Time, + &test_value->type.Date_Time) == 0) { + status = true; + } + break; case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: status = lighting_command_same(&value->type.Lighting_Command, &test_value->type.Lighting_Command); break; + case BACNET_APPLICATION_TAG_XY_COLOR: + /* BACnetxyColor */ + status = xy_color_same(&value->type.XY_Color, + &test_value->type.XY_Color); + break; + case BACNET_APPLICATION_TAG_COLOR_COMMAND: + /* BACnetColorCommand */ + status = color_command_same(&value->type.Color_Command, + &test_value->type.Color_Command); + break; case BACNET_APPLICATION_TAG_HOST_N_PORT: status = host_n_port_same(&value->type.Host_Address, &value->type.Host_Address); diff --git a/src/bacnet/bacapp.h b/src/bacnet/bacapp.h index 35f603ae..6cfaafcf 100644 --- a/src/bacnet/bacapp.h +++ b/src/bacnet/bacapp.h @@ -35,6 +35,7 @@ #include "bacnet/lighting.h" #include "bacnet/bacdevobjpropref.h" #include "bacnet/hostnport.h" +#include "bacnet/timestamp.h" struct BACnet_Application_Data_Value; typedef struct BACnet_Application_Data_Value { @@ -80,7 +81,11 @@ typedef struct BACnet_Application_Data_Value { BACNET_OBJECT_ID Object_Id; #endif #if defined (BACAPP_TYPES_EXTRA) + BACNET_TIMESTAMP Time_Stamp; + BACNET_DATE_TIME Date_Time; BACNET_LIGHTING_COMMAND Lighting_Command; + BACNET_COLOR_COMMAND Color_Command; + BACNET_XY_COLOR XY_Color; BACNET_HOST_N_PORT Host_Address; BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE Device_Object_Property_Reference; @@ -204,6 +209,12 @@ extern "C" { int max_apdu_len, BACNET_APPLICATION_DATA_VALUE * value, BACNET_PROPERTY_ID prop); + BACNET_STACK_EXPORT + int bacapp_decode_known_property(uint8_t *apdu, + int max_apdu_len, + BACNET_APPLICATION_DATA_VALUE *value, + BACNET_OBJECT_TYPE object_type, + BACNET_PROPERTY_ID property); BACNET_STACK_EXPORT bool bacapp_copy( diff --git a/src/bacnet/bacdcode.c b/src/bacnet/bacdcode.c index 67796d65..457a1075 100644 --- a/src/bacnet/bacdcode.c +++ b/src/bacnet/bacdcode.c @@ -2948,7 +2948,7 @@ int decode_context_date(uint8_t *apdu, uint8_t tag_number, BACNET_DATE *bdate) /** * Encode a simple ACK and returns the number of apdu bytes consumed. * - * @param apdu Transmit buffer + * @param apdu - buffer to hold encoded data, or NULL for length * @param invoke_id ID invoked * @param service_choice Service being acked * @@ -2956,9 +2956,11 @@ int decode_context_date(uint8_t *apdu, uint8_t tag_number, BACNET_DATE *bdate) */ int encode_simple_ack(uint8_t *apdu, uint8_t invoke_id, uint8_t service_choice) { - apdu[0] = PDU_TYPE_SIMPLE_ACK; - apdu[1] = invoke_id; - apdu[2] = service_choice; + if (apdu) { + apdu[0] = PDU_TYPE_SIMPLE_ACK; + apdu[1] = invoke_id; + apdu[2] = service_choice; + } return 3; } @@ -2966,7 +2968,7 @@ int encode_simple_ack(uint8_t *apdu, uint8_t invoke_id, uint8_t service_choice) /** * Encode a BACnetAddress and returns the number of apdu bytes consumed. * - * @param apdu Transmit buffer + * @param apdu - buffer to hold encoded data, or NULL for length * @param destination Pointer to the destination address to be encoded. * * @return number of apdu bytes created @@ -2979,20 +2981,23 @@ int encode_bacnet_address(uint8_t *apdu, BACNET_ADDRESS *destination) if (destination) { /* network number */ apdu_len += - encode_application_unsigned(&apdu[apdu_len], destination->net); + encode_application_unsigned(apdu, destination->net); /* encode mac address as an octet-string */ if (destination->len != 0) { octetstring_init(&mac_addr, destination->adr, destination->len); } else { octetstring_init(&mac_addr, destination->mac, destination->mac_len); } - apdu_len += encode_application_octet_string(&apdu[apdu_len], &mac_addr); + if (apdu) { + apdu += apdu_len; + } + apdu_len += encode_application_octet_string(apdu, &mac_addr); } return apdu_len; } /** - * Dencode a BACnetAddress and returns the number of apdu bytes consumed. + * Decode a BACnetAddress and returns the number of apdu bytes consumed. * * @param apdu Receive buffer * @param destination Pointer to the destination address structure to be filled @@ -3045,9 +3050,17 @@ int encode_context_bacnet_address( uint8_t *apdu, uint8_t tag_number, BACNET_ADDRESS *destination) { int apdu_len = 0; - apdu_len += encode_opening_tag(&apdu[apdu_len], tag_number); - apdu_len += encode_bacnet_address(&apdu[apdu_len], destination); - apdu_len += encode_closing_tag(&apdu[apdu_len], tag_number); + + apdu_len += encode_opening_tag(apdu, tag_number); + if (apdu) { + apdu += apdu_len; + } + apdu_len += encode_bacnet_address(apdu, destination); + if (apdu) { + apdu += apdu_len; + } + apdu_len += encode_closing_tag(apdu, tag_number); + return apdu_len; } diff --git a/src/bacnet/bacdef.h b/src/bacnet/bacdef.h index 1f6ec914..39559c3f 100644 --- a/src/bacnet/bacdef.h +++ b/src/bacnet/bacdef.h @@ -41,7 +41,7 @@ /* Although this stack can implement a later revision, * sometimes another revision is desired */ #ifndef BACNET_PROTOCOL_REVISION -#define BACNET_PROTOCOL_REVISION 19 +#define BACNET_PROTOCOL_REVISION 24 #endif /* there are a few dependencies on the BACnet Protocol-Revision */ diff --git a/src/bacnet/bacdevobjpropref.c b/src/bacnet/bacdevobjpropref.c index a983031d..cc657229 100644 --- a/src/bacnet/bacdevobjpropref.c +++ b/src/bacnet/bacdevobjpropref.c @@ -516,7 +516,7 @@ int bacapp_decode_obj_property_ref(uint8_t *apdu, int apdu_len = 0; int len = 0; BACNET_OBJECT_ID object_identifier; - BACNET_PROPERTY_ID property_identifier; + uint32_t property_identifier; BACNET_UNSIGNED_INTEGER unsigned_value; if (apdu && (apdu_len_max > 0)) { @@ -540,7 +540,8 @@ int bacapp_decode_obj_property_ref(uint8_t *apdu, if (reference) { reference->object_identifier.type = object_identifier.type; reference->object_identifier.instance = object_identifier.instance; - reference->property_identifier = property_identifier; + reference->property_identifier = + (BACNET_PROPERTY_ID)property_identifier; reference->property_array_index = BACNET_ARRAY_ALL; } /* property-array-index [2] Unsigned OPTIONAL */ diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h index 3cd75581..ce824123 100644 --- a/src/bacnet/bacenum.h +++ b/src/bacnet/bacenum.h @@ -1401,7 +1401,11 @@ typedef enum { /* BACnetLightingCommand */ BACNET_APPLICATION_TAG_LIGHTING_COMMAND, /* BACnetHostNPort */ - BACNET_APPLICATION_TAG_HOST_N_PORT + BACNET_APPLICATION_TAG_HOST_N_PORT, + /* BACnetxyColor */ + BACNET_APPLICATION_TAG_XY_COLOR, + /* BACnetColorCommand */ + BACNET_APPLICATION_TAG_COLOR_COMMAND } BACNET_APPLICATION_TAG; /* note: these are not the real values, */ @@ -2042,6 +2046,22 @@ typedef enum BACnetColorOperation { BACNET_COLOR_OPERATION_MAX = 7 } BACNET_COLOR_OPERATION; +typedef enum BACnetColorOperationInProgress { + BACNET_COLOR_OPERATION_IN_PROGRESS_IDLE = 0, + BACNET_COLOR_OPERATION_IN_PROGRESS_FADE_ACTIVE = 1, + BACNET_COLOR_OPERATION_IN_PROGRESS_RAMP_ACTIVE = 2, + BACNET_COLOR_OPERATION_IN_PROGRESS_NOT_CONTROLLED = 3, + BACNET_COLOR_OPERATION_IN_PROGRESS_OTHER = 4, + BACNET_COLOR_OPERATION_IN_PROGRESS_MAX = 5 +} BACNET_COLOR_OPERATION_IN_PROGRESS; + +typedef enum BACnetColorTransition { + BACNET_COLOR_TRANSITION_NONE = 0, + BACNET_COLOR_TRANSITION_FADE = 1, + BACNET_COLOR_TRANSITION_RAMP = 2, + BACNET_COLOR_TRANSITION_MAX = 3, +} BACNET_COLOR_TRANSITION; + /* NOTE: BACNET_DAYS_OF_WEEK is different than BACNET_WEEKDAY */ /* 0=Monday-6=Sunday */ typedef enum BACnetDaysOfWeek { diff --git a/src/bacnet/bactext.c b/src/bacnet/bactext.c index b669fcdd..1107e0e9 100644 --- a/src/bacnet/bactext.c +++ b/src/bacnet/bactext.c @@ -740,7 +740,12 @@ INDTEXT_DATA bacnet_property_names[] = { { PROP_COLOR_REFERENCE, "color-reference" }, { PROP_DEFAULT_COLOR, "default-color" }, { PROP_DEFAULT_COLOR_TEMPERATURE, "default-color-temperature" }, - { PROP_OVERRIDE_COLOR_REFERENCE, "override-color-reference" }, { 0, NULL } + { PROP_OVERRIDE_COLOR_REFERENCE, "override-color-reference" }, + { PROP_COLOR_COMMAND, "color-command" }, + { PROP_HIGH_END_TRIM, "high-end-trim" }, + { PROP_LOW_END_TRIM, "low-end-trim" }, + { PROP_TRIM_FADE_TIME, "trim-fade-time" }, + { 0, NULL } }; const char *bactext_property_name(unsigned index) diff --git a/src/bacnet/basic/object/color_object.c b/src/bacnet/basic/object/color_object.c new file mode 100644 index 00000000..6187e4cd --- /dev/null +++ b/src/bacnet/basic/object/color_object.c @@ -0,0 +1,917 @@ +/** + * @file + * @author Steve Karg + * @date June 2022 + * @brief Color objects, customize for your use + * + * @section DESCRIPTION + * + * The Color object is an object with a present-value that + * uses an x,y color single precision floating point data type. + * + * @section LICENSE + * + * Copyright (C) 2022 Steve Karg + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacerror.h" +#include "bacnet/bacapp.h" +#include "bacnet/bactext.h" +#include "bacnet/cov.h" +#include "bacnet/apdu.h" +#include "bacnet/npdu.h" +#include "bacnet/abort.h" +#include "bacnet/lighting.h" +#include "bacnet/reject.h" +#include "bacnet/rp.h" +#include "bacnet/wp.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/sys/keylist.h" +/* me! */ +#include "color_object.h" + +struct object_data { + bool Changed : 1; + bool Write_Enabled : 1; + BACNET_XY_COLOR Present_Value; + BACNET_XY_COLOR Tracking_Value; + BACNET_COLOR_COMMAND Color_Command; + BACNET_COLOR_OPERATION_IN_PROGRESS In_Progress; + BACNET_XY_COLOR Default_Color; + uint32_t Default_Fade_Time; + BACNET_COLOR_TRANSITION Transition; + const char *Object_Name; + const char *Description; +}; +/* Key List for storing the object data sorted by instance number */ +static OS_Keylist Object_List; +/* callback for present value writes */ +static color_write_present_value_callback Color_Write_Present_Value_Callback; + +/* These three arrays are used by the ReadPropertyMultiple handler */ +static const int Color_Properties_Required[] = { PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_PRESENT_VALUE, PROP_TRACKING_VALUE, + PROP_COLOR_COMMAND, PROP_IN_PROGRESS, PROP_DEFAULT_COLOR, + PROP_DEFAULT_FADE_TIME, -1 }; + +static const int Color_Properties_Optional[] = { PROP_DESCRIPTION, + PROP_TRANSITION, -1 }; + +static const int Color_Properties_Proprietary[] = { -1 }; + +/** + * Returns the list of required, optional, and proprietary properties. + * Used by ReadPropertyMultiple service. + * + * @param pRequired - pointer to list of int terminated by -1, of + * BACnet required properties for this object. + * @param pOptional - pointer to list of int terminated by -1, of + * BACnet optkional properties for this object. + * @param pProprietary - pointer to list of int terminated by -1, of + * BACnet proprietary properties for this object. + */ +void Color_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary) +{ + if (pRequired) { + *pRequired = Color_Properties_Required; + } + if (pOptional) { + *pOptional = Color_Properties_Optional; + } + if (pProprietary) { + *pProprietary = Color_Properties_Proprietary; + } + + return; +} + +/** + * Determines if a given Color instance is valid + * + * @param object_instance - object-instance number of the object + * + * @return true if the instance is valid, and false if not + */ +bool Color_Valid_Instance(uint32_t object_instance) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + return true; + } + + return false; +} + +/** + * Determines the number of Color objects + * + * @return Number of Color objects + */ +unsigned Color_Count(void) +{ + return Keylist_Count(Object_List); +} + +/** + * Determines the object instance-number for a given 0..N index + * of Color objects where N is Color_Count(). + * + * @param index - 0..N where N is Color_Count() + * + * @return object instance-number for the given index + */ +uint32_t Color_Index_To_Instance(unsigned index) +{ + return Keylist_Key(Object_List, index); +} + +/** + * For a given object instance-number, determines a 0..N index + * of Color objects where N is Color_Count(). + * + * @param object_instance - object-instance number of the object + * + * @return index for the given instance-number, or Color_Count() + * if not valid. + */ +unsigned Color_Instance_To_Index(uint32_t object_instance) +{ + return Keylist_Index(Object_List, object_instance); +} + +/** + * For a given object instance-number, determines the present-value + * + * @param object_instance - object-instance number of the object + * + * @return present-value of the object + */ +bool Color_Present_Value(uint32_t object_instance, BACNET_XY_COLOR *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + xy_color_copy(value, &pObject->Present_Value); + status = true; + } + + return status; +} + +/** + * For a given object instance-number, sets the present-value + * + * @param object_instance - object-instance number of the object + * @param value - floating point Color + * + * @return true if values are within range and present-value is set. + */ +bool Color_Present_Value_Set(uint32_t object_instance, BACNET_XY_COLOR *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + xy_color_copy(&pObject->Present_Value, value); + status = true; + } + + return status; +} + +/** + * 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 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_Present_Value_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; + 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; + } 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, determines the present-value + * + * @param object_instance - object-instance number of the object + * + * @return present-value of the object + */ +bool Color_Tracking_Value(uint32_t object_instance, BACNET_XY_COLOR *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + xy_color_copy(value, &pObject->Tracking_Value); + status = true; + } + + return status; +} + +/** + * For a given object instance-number, sets the present-value + * + * @param object_instance - object-instance number of the object + * @param value - floating point Color + * + * @return true if values are within range and present-value is set. + */ +bool Color_Tracking_Value_Set(uint32_t object_instance, BACNET_XY_COLOR *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + xy_color_copy(&pObject->Tracking_Value, value); + status = true; + } + + return status; +} + +/** + * For a given object instance-number, gets the property value + * + * @param object_instance - object-instance number of the object + * @param value - color command data + * @return true if the property value is copied + */ +bool Color_Command(uint32_t object_instance, BACNET_COLOR_COMMAND *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject && value) { + color_command_copy(value, &pObject->Color_Command); + 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 - color command data + * @return true if values are within range and value is set. + */ +bool Color_Command_Set(uint32_t object_instance, BACNET_COLOR_COMMAND *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject && value) { + color_command_copy(&pObject->Color_Command, value); + status = true; + } + + 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_COLOR_OPERATION_IN_PROGRESS Color_In_Progress(uint32_t object_instance) +{ + BACNET_COLOR_OPERATION_IN_PROGRESS value = + BACNET_COLOR_OPERATION_IN_PROGRESS_MAX; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->In_Progress; + } + + 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_OPERATION_IN_PROGRESS + * @return true if values are within range and value is set. + */ +bool Color_In_Progress_Set( + uint32_t object_instance, BACNET_COLOR_OPERATION_IN_PROGRESS value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (value < BACNET_COLOR_OPERATION_IN_PROGRESS_MAX) { + pObject->In_Progress = value; + status = true; + } + } + + return status; +} + +/** + * For a given object instance-number, determines the present-value + * + * @param object_instance - object-instance number of the object + * + * @return present-value of the object + */ +bool Color_Default_Color(uint32_t object_instance, BACNET_XY_COLOR *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + xy_color_copy(value, &pObject->Default_Color); + status = true; + } + + return status; +} + +/** + * For a given object instance-number, sets the present-value + * + * @param object_instance - object-instance number of the object + * @param value - floating point Color + * + * @return true if values are within range and present-value is set. + */ +bool Color_Default_Color_Set(uint32_t object_instance, BACNET_XY_COLOR *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + xy_color_copy(&pObject->Default_Color, value); + status = true; + } + + return status; +} + +/** + * For a given object instance-number, gets the property value + * + * @param object_instance - object-instance number of the object + * @return property value + */ +uint32_t Color_Default_Fade_Time(uint32_t object_instance) +{ + uint32_t value = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Default_Fade_Time; + } + + 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_OPERATION_IN_PROGRESS + * @return true if values are within range and value is set. + */ +bool Color_Default_Fade_Time_Set(uint32_t object_instance, uint32_t value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if ((value == 0) || + ((value >= BACNET_COLOR_FADE_TIME_MIN) && + (value <= BACNET_COLOR_FADE_TIME_MAX))) { + pObject->Default_Fade_Time = value; + } + status = true; + } + + 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_COLOR_TRANSITION Color_Transition(uint32_t object_instance) +{ + BACNET_COLOR_TRANSITION value = BACNET_COLOR_OPERATION_IN_PROGRESS_MAX; + 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 Color_Transition_Set( + uint32_t object_instance, BACNET_COLOR_TRANSITION value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (value < BACNET_COLOR_TRANSITION_MAX) { + pObject->Transition = value; + status = true; + } + } + + return status; +} + +/** + * For a given object instance-number, loads the object-name into + * a characterstring. Note that the object name must be unique + * within this device. + * + * @param object_instance - object-instance number of the object + * @param object_name - holds the object-name retrieved + * + * @return true if object-name was retrieved + */ +bool Color_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) +{ + bool status = false; + struct object_data *pObject; + char name_text[16] = "COLOR-4194303"; + + 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), "COLOR-%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 Color_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_COLOR) && + (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 *Color_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 Color_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; +} + +/** + * ReadProperty handler for this object. For the given ReadProperty + * data, the application_data is loaded or the error flags are set. + * + * @param rpdata - BACNET_READ_PROPERTY_DATA data, including + * requested data and space for the reply, or error response. + * + * @return number of APDU bytes in the response, or + * BACNET_STATUS_ERROR on error. + */ +int Color_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = 0; /* return value */ + BACNET_CHARACTER_STRING char_string; + uint8_t *apdu = NULL; + BACNET_XY_COLOR color_value = { 0.0, 0.0 }; + BACNET_COLOR_COMMAND color_command = { 0 }; + + if ((rpdata == NULL) || (rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + + apdu = rpdata->application_data; + switch (rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_application_object_id( + &apdu[0], rpdata->object_type, rpdata->object_instance); + break; + case PROP_OBJECT_NAME: + Color_Object_Name(rpdata->object_instance, &char_string); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = + encode_application_enumerated(&apdu[0], rpdata->object_type); + break; + case PROP_PRESENT_VALUE: + if (Color_Present_Value(rpdata->object_instance, &color_value)) { + apdu_len = xy_color_encode(apdu, &color_value); + } + break; + case PROP_TRACKING_VALUE: + if (Color_Tracking_Value(rpdata->object_instance, &color_value)) { + apdu_len = xy_color_encode(apdu, &color_value); + } + break; + case PROP_COLOR_COMMAND: + if (Color_Command(rpdata->object_instance, &color_command)) { + apdu_len = color_command_encode(apdu, &color_command); + } + break; + case PROP_IN_PROGRESS: + apdu_len = encode_application_enumerated( + apdu, Color_In_Progress(rpdata->object_instance)); + break; + case PROP_DEFAULT_COLOR: + if (Color_Default_Color(rpdata->object_instance, &color_value)) { + apdu_len = xy_color_encode(apdu, &color_value); + } + break; + case PROP_DEFAULT_FADE_TIME: + apdu_len = encode_application_unsigned( + apdu, Color_Default_Fade_Time(rpdata->object_instance)); + break; + case PROP_TRANSITION: + apdu_len = encode_application_enumerated( + apdu, Color_Transition(rpdata->object_instance)); + break; + case PROP_DESCRIPTION: + characterstring_init_ansi( + &char_string, Color_Description(rpdata->object_instance)); + apdu_len = encode_application_character_string(apdu, &char_string); + break; + default: + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = BACNET_STATUS_ERROR; + break; + } + /* only array properties can have array options */ + if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) && + (rpdata->object_property != PROP_EVENT_TIME_STAMPS) && + (rpdata->array_index != BACNET_ARRAY_ALL)) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** + * WriteProperty handler for this object. For the given WriteProperty + * data, the application_data is loaded or the error flags are set. + * + * @param wp_data - BACNET_WRITE_PROPERTY_DATA data, including + * requested data and space for the reply, or error response. + * + * @return false if an error is loaded, true if no errors + */ +bool Color_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + /* decode the some of the request */ + len = bacapp_decode_application_data( + wp_data->application_data, wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + if (len < 0) { + /* error while decoding - a value larger than we can handle */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + return false; + } + if ((wp_data->object_property != PROP_PRIORITY_ARRAY) && + (wp_data->object_property != PROP_EVENT_TIME_STAMPS) && + (wp_data->array_index != BACNET_ARRAY_ALL)) { + /* only array properties can have array options */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + return false; + } + switch (wp_data->object_property) { + case PROP_PRESENT_VALUE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_REAL); + 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_OBJECT_IDENTIFIER: + case PROP_OBJECT_TYPE: + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + 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; +} + +/** + * @brief Sets a callback used when present-value is written from BACnet + * @param cb - callback used to provide indications + */ +void Color_Write_Present_Value_Callback_Set( + color_write_present_value_callback cb) +{ + Color_Write_Present_Value_Callback = cb; +} + +/** + * @brief Determines a object write-enabled flag state + * @param object_instance - object-instance number of the object + * @return write-enabled status flag + */ +bool Color_Write_Enabled(uint32_t object_instance) +{ + bool value = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Write_Enabled; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the write-enabled flag + * @param object_instance - object-instance number of the object + */ +void Color_Write_Enable(uint32_t object_instance) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Write_Enabled = true; + } +} + +/** + * @brief For a given object instance-number, clears the write-enabled flag + * @param object_instance - object-instance number of the object + */ +void Color_Write_Disable(uint32_t object_instance) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Write_Enabled = false; + } +} + +/** + * Creates a Color object + * @param object_instance - object-instance number of the object + */ +bool Color_Create(uint32_t object_instance) +{ + bool status = false; + struct object_data *pObject = NULL; + int index = 0; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + 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; + 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->Changed = false; + pObject->Write_Enabled = false; + /* add to list */ + index = Keylist_Data_Add(Object_List, object_instance, pObject); + if (index >= 0) { + status = true; + Device_Inc_Database_Revision(); + } + } + } + + return status; +} + +/** + * Deletes an Color object + * @param object_instance - object-instance number of the object + * @return true if the object is deleted + */ +bool Color_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; + Device_Inc_Database_Revision(); + } + + return status; +} + +/** + * Deletes all the Colors and their data + */ +void Color_Cleanup(void) +{ + struct object_data *pObject; + + if (Object_List) { + do { + pObject = Keylist_Data_Pop(Object_List); + if (pObject) { + free(pObject); + Device_Inc_Database_Revision(); + } + } while (pObject); + Keylist_Delete(Object_List); + Object_List = NULL; + } +} + +/** + * Initializes the Color object data + */ +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 new file mode 100644 index 00000000..58498185 --- /dev/null +++ b/src/bacnet/basic/object/color_object.h @@ -0,0 +1,133 @@ +/** + * @file + * @author Steve Karg + * @date June 2022 + * @brief Color object, customize for your use + * + * @section DESCRIPTION + * + * The Color object is an object with a present-value that + * uses an x,y color single precision floating point data type. + * + * @section LICENSE + * + * Copyright (C) 2022 Steve Karg + * + * SPDX-License-Identifier: MIT + */ +#ifndef BACNET_COLOR_OBJECT_H +#define BACNET_COLOR_OBJECT_H + +#include +#include +#include "bacnet/bacnet_stack_exports.h" +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacerror.h" +#include "bacnet/rp.h" +#include "bacnet/wp.h" + +/** + * @brief Callback for gateway write present value request + * @param object_instance - object-instance number of the object + * @param old_value - BACnetXYColor value prior to write + * @param value - BACnetXYColor value of the write + */ +typedef void (*color_write_present_value_callback)(uint32_t object_instance, + BACNET_XY_COLOR *old_value, + BACNET_XY_COLOR *value); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +void Color_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary); +BACNET_STACK_EXPORT +bool Color_Valid_Instance(uint32_t object_instance); +BACNET_STACK_EXPORT +unsigned Color_Count(void); +BACNET_STACK_EXPORT +uint32_t Color_Index_To_Instance(unsigned index); +BACNET_STACK_EXPORT +unsigned Color_Instance_To_Index(uint32_t object_instance); + +BACNET_STACK_EXPORT +bool Color_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name); +BACNET_STACK_EXPORT +bool Color_Name_Set(uint32_t object_instance, char *new_name); + +BACNET_STACK_EXPORT +int Color_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata); + +BACNET_STACK_EXPORT +bool Color_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data); + +BACNET_STACK_EXPORT +bool Color_Present_Value_Set(uint32_t object_instance, BACNET_XY_COLOR *value); +BACNET_STACK_EXPORT +bool Color_Present_Value(uint32_t object_instance, BACNET_XY_COLOR *value); +BACNET_STACK_EXPORT +void Color_Write_Present_Value_Callback_Set( + color_write_present_value_callback cb); + +BACNET_STACK_EXPORT +bool Color_Tracking_Value_Set(uint32_t object_instance, BACNET_XY_COLOR *value); +BACNET_STACK_EXPORT +bool Color_Tracking_Value(uint32_t object_instance, BACNET_XY_COLOR *value); + +BACNET_STACK_EXPORT +bool Color_Command(uint32_t object_instance, BACNET_COLOR_COMMAND *value); +BACNET_STACK_EXPORT +bool Color_Command_Set(uint32_t object_instance, BACNET_COLOR_COMMAND *value); + +BACNET_STACK_EXPORT +bool Color_Default_Color_Set(uint32_t object_instance, BACNET_XY_COLOR *value); +BACNET_STACK_EXPORT +bool Color_Default_Color(uint32_t object_instance, BACNET_XY_COLOR *value); + +BACNET_STACK_EXPORT +uint32_t Color_Default_Fade_Time(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Color_Default_Fade_Time_Set(uint32_t object_instance, uint32_t value); + +BACNET_STACK_EXPORT +BACNET_COLOR_OPERATION_IN_PROGRESS Color_In_Progress(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Color_In_Progress_Set( + uint32_t object_instance, BACNET_COLOR_OPERATION_IN_PROGRESS value); + +BACNET_STACK_EXPORT +BACNET_COLOR_TRANSITION Color_Transition(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Color_Transition_Set( + uint32_t object_instance, BACNET_COLOR_TRANSITION value); + +BACNET_STACK_EXPORT +char *Color_Description(uint32_t instance); +BACNET_STACK_EXPORT +bool Color_Description_Set(uint32_t instance, char *new_name); + +BACNET_STACK_EXPORT +bool Color_Write_Enabled(uint32_t instance); +BACNET_STACK_EXPORT +void Color_Write_Enable(uint32_t instance); +BACNET_STACK_EXPORT +void Color_Write_Disable(uint32_t instance); + +BACNET_STACK_EXPORT +bool Color_Create(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Color_Delete(uint32_t object_instance); +BACNET_STACK_EXPORT +void Color_Cleanup(void); +BACNET_STACK_EXPORT +void Color_Init(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/object/color_temperature.c b/src/bacnet/basic/object/color_temperature.c new file mode 100644 index 00000000..bcd76a16 --- /dev/null +++ b/src/bacnet/basic/object/color_temperature.c @@ -0,0 +1,1124 @@ +/** + * @file + * @author Steve Karg + * @date July 2022 + * @brief Color Temperature object, customize for your use + * + * @section DESCRIPTION + * + * The Color Temperature object is an object with a present-value that + * uses an Color Temperature INTEGER type + * + * @section LICENSE + * + * Copyright (C) 2022 Steve Karg + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacerror.h" +#include "bacnet/bacapp.h" +#include "bacnet/bactext.h" +#include "bacnet/cov.h" +#include "bacnet/apdu.h" +#include "bacnet/npdu.h" +#include "bacnet/abort.h" +#include "bacnet/lighting.h" +#include "bacnet/reject.h" +#include "bacnet/rp.h" +#include "bacnet/wp.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/sys/keylist.h" +/* me! */ +#include "color_temperature.h" + +struct object_data { + bool Changed : 1; + bool Write_Enabled : 1; + uint32_t Present_Value; + uint32_t Tracking_Value; + BACNET_COLOR_COMMAND Color_Command; + BACNET_COLOR_OPERATION_IN_PROGRESS In_Progress; + uint32_t Default_Color_Temperature; + uint32_t Default_Fade_Time; + uint32_t Default_Ramp_Rate; + uint32_t Default_Step_Increment; + BACNET_COLOR_TRANSITION Transition; + uint32_t Present_Value_Minimum; + uint32_t Present_Value_Maximum; + const char *Object_Name; + const char *Description; +}; +/* Key List for storing the object data sorted by instance number */ +static OS_Keylist Object_List; +/* callback for present value writes */ +static color_temperature_write_present_value_callback + Color_Temperature_Write_Present_Value_Callback; + +/* These three arrays are used by the ReadPropertyMultiple handler */ +static const int Color_Temperature_Properties_Required[] = { + PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, + PROP_PRESENT_VALUE, PROP_TRACKING_VALUE, PROP_COLOR_COMMAND, + PROP_IN_PROGRESS, PROP_DEFAULT_COLOR_TEMPERATURE, PROP_DEFAULT_FADE_TIME, + PROP_DEFAULT_RAMP_RATE, PROP_DEFAULT_STEP_INCREMENT, -1 +}; + +static const int Color_Temperature_Properties_Optional[] = { PROP_DESCRIPTION, + PROP_TRANSITION, PROP_MIN_PRES_VALUE, PROP_MAX_PRES_VALUE, -1 }; + +static const int Color_Temperature_Properties_Proprietary[] = { -1 }; + +/** + * Returns the list of required, optional, and proprietary properties. + * Used by ReadPropertyMultiple service. + * + * @param pRequired - pointer to list of int terminated by -1, of + * BACnet required properties for this object. + * @param pOptional - pointer to list of int terminated by -1, of + * BACnet optkional properties for this object. + * @param pProprietary - pointer to list of int terminated by -1, of + * BACnet proprietary properties for this object. + */ +void Color_Temperature_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary) +{ + if (pRequired) { + *pRequired = Color_Temperature_Properties_Required; + } + if (pOptional) { + *pOptional = Color_Temperature_Properties_Optional; + } + if (pProprietary) { + *pProprietary = Color_Temperature_Properties_Proprietary; + } + + return; +} + +/** + * Determines if a given Color instance is valid + * + * @param object_instance - object-instance number of the object + * + * @return true if the instance is valid, and false if not + */ +bool Color_Temperature_Valid_Instance(uint32_t object_instance) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + return true; + } + + return false; +} + +/** + * Determines the number of Color objects + * + * @return Number of Color objects + */ +unsigned Color_Temperature_Count(void) +{ + return Keylist_Count(Object_List); +} + +/** + * Determines the object instance-number for a given 0..N index + * of Color objects where N is Color_Temperature_Count(). + * + * @param index - 0..N where N is Color_Temperature_Count() + * + * @return object instance-number for the given index + */ +uint32_t Color_Temperature_Index_To_Instance(unsigned index) +{ + return Keylist_Key(Object_List, index); +} + +/** + * For a given object instance-number, determines a 0..N index + * of Color objects where N is Color_Temperature_Count(). + * + * @param object_instance - object-instance number of the object + * + * @return index for the given instance-number, or Color_Temperature_Count() + * if not valid. + */ +unsigned Color_Temperature_Instance_To_Index(uint32_t object_instance) +{ + return Keylist_Index(Object_List, object_instance); +} + +/** + * For a given object instance-number, determines the present-value + * + * @param object_instance - object-instance number of the object + * @return present-value of the object + */ +uint32_t Color_Temperature_Present_Value(uint32_t object_instance) +{ + uint32_t value = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Present_Value; + } + + return value; +} + +/** + * For a given object instance-number, sets the present-value + * + * @param object_instance - object-instance number of the object + * @param value - floating point Color + * + * @return true if values are within range and present-value is set. + */ +bool Color_Temperature_Present_Value_Set( + uint32_t object_instance, uint32_t value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Present_Value = value; + status = true; + } + + return status; +} + +/** + * 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 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_Present_Value_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; + 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; + pObject->Present_Value = value; + if (Color_Temperature_Write_Present_Value_Callback) { + Color_Temperature_Write_Present_Value_Callback( + object_instance, old_value, 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, determines the present-value + * + * @param object_instance - object-instance number of the object + * + * @return present-value of the object + */ +uint32_t Color_Temperature_Tracking_Value(uint32_t object_instance) +{ + uint32_t value = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Tracking_Value; + } + + return value; +} + +/** + * For a given object instance-number, sets the present-value + * + * @param object_instance - object-instance number of the object + * @param value - floating point Color + * + * @return true if values are within range and present-value is set. + */ +bool Color_Temperature_Tracking_Value_Set( + uint32_t object_instance, uint32_t value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Tracking_Value = value; + status = true; + } + + return status; +} + +/** + * For a given object instance-number, determines the present-value + * + * @param object_instance - object-instance number of the object + * @return present-value of the object + */ +uint32_t Color_Temperature_Min_Pres_Value(uint32_t object_instance) +{ + uint32_t value = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Present_Value_Minimum; + } + + return value; +} + +/** + * For a given object instance-number, sets the present-value + * + * @param object_instance - object-instance number of the object + * @param value - color temperature Kelvin + * + * @return true if values are within range and present-value is set. + */ +bool Color_Temperature_Min_Pres_Value_Set( + uint32_t object_instance, uint32_t value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Present_Value_Minimum = value; + status = true; + } + + return status; +} + +/** + * For a given object instance-number, determines the present-value + * + * @param object_instance - object-instance number of the object + * @return present-value of the object + */ +uint32_t Color_Temperature_Max_Pres_Value(uint32_t object_instance) +{ + uint32_t value = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Present_Value_Maximum; + } + + return value; +} + +/** + * For a given object instance-number, sets the present-value + * + * @param object_instance - object-instance number of the object + * @param value - color temperature Kelvin + * + * @return true if values are within range and present-value is set. + */ +bool Color_Temperature_Max_Pres_Value_Set( + uint32_t object_instance, uint32_t value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Present_Value_Maximum = value; + status = true; + } + + return status; +} + +/** + * For a given object instance-number, gets the property value + * + * @param object_instance - object-instance number of the object + * @param value - color command data + * @return true if the property value is copied + */ +bool Color_Temperature_Command( + uint32_t object_instance, BACNET_COLOR_COMMAND *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject && value) { + color_command_copy(value, &pObject->Color_Command); + 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 - color command data + * @return true if values are within range and value is set. + */ +bool Color_Temperature_Command_Set( + uint32_t object_instance, BACNET_COLOR_COMMAND *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject && value) { + color_command_copy(&pObject->Color_Command, value); + status = true; + } + + 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_COLOR_OPERATION_IN_PROGRESS Color_Temperature_In_Progress( + uint32_t object_instance) +{ + BACNET_COLOR_OPERATION_IN_PROGRESS value = + BACNET_COLOR_OPERATION_IN_PROGRESS_MAX; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->In_Progress; + } + + 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_OPERATION_IN_PROGRESS + * @return true if values are within range and value is set. + */ +bool Color_Temperature_In_Progress_Set( + uint32_t object_instance, BACNET_COLOR_OPERATION_IN_PROGRESS value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (value < BACNET_COLOR_OPERATION_IN_PROGRESS_MAX) { + pObject->In_Progress = value; + status = true; + } + } + + return status; +} + +/** + * For a given object instance-number, determines the present-value + * + * @param object_instance - object-instance number of the object + * + * @return present-value of the object + */ +uint32_t Color_Temperature_Default_Color_Temperature(uint32_t object_instance) +{ + uint32_t value = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Default_Color_Temperature; + } + + return value; +} + +/** + * For a given object instance-number, sets the present-value + * + * @param object_instance - object-instance number of the object + * @param value - color temperature Kelvin + * + * @return true if values are within range and present-value is set. + */ +bool Color_Temperature_Default_Color_Temperature_Set( + uint32_t object_instance, uint32_t value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Default_Color_Temperature = value; + status = true; + } + + return status; +} + +/** + * For a given object instance-number, gets the property value + * + * @param object_instance - object-instance number of the object + * @return property value + */ +uint32_t Color_Temperature_Default_Fade_Time(uint32_t object_instance) +{ + uint32_t value = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Default_Fade_Time; + } + + 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_OPERATION_IN_PROGRESS + * @return true if values are within range and value is set. + */ +bool Color_Temperature_Default_Fade_Time_Set( + uint32_t object_instance, uint32_t value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if ((value == 0) || + ((value >= BACNET_COLOR_FADE_TIME_MIN) && + (value <= BACNET_COLOR_FADE_TIME_MAX))) { + pObject->Default_Fade_Time = value; + } + status = true; + } + + return status; +} + +/** + * For a given object instance-number, gets the property value + * + * @param object_instance - object-instance number of the object + * @return property value + */ +uint32_t Color_Temperature_Default_Ramp_Rate(uint32_t object_instance) +{ + uint32_t value = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Default_Ramp_Rate; + } + + return value; +} + +/** + * For a given object instance-number, sets the property value + * + * @param object_instance - object-instance number of the object + * @param value - property value + * @return true if values are within range and value is set. + */ +bool Color_Temperature_Default_Ramp_Rate_Set( + uint32_t object_instance, uint32_t value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Default_Ramp_Rate = value; + status = true; + } + + return status; +} + +/** + * For a given object instance-number, gets the property value + * + * @param object_instance - object-instance number of the object + * @return property value + */ +uint32_t Color_Temperature_Default_Step_Increment(uint32_t object_instance) +{ + uint32_t value = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Default_Step_Increment; + } + + return value; +} + +/** + * For a given object instance-number, sets the property value + * + * @param object_instance - object-instance number of the object + * @param value - property value + * @return true if values are within range and value is set. + */ +bool Color_Temperature_Default_Step_Increment_Set( + uint32_t object_instance, uint32_t value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Default_Step_Increment = value; + status = true; + } + + 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_COLOR_TRANSITION Color_Temperature_Transition(uint32_t object_instance) +{ + BACNET_COLOR_TRANSITION value = BACNET_COLOR_OPERATION_IN_PROGRESS_MAX; + 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 Color_Temperature_Transition_Set( + uint32_t object_instance, BACNET_COLOR_TRANSITION value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (value < BACNET_COLOR_TRANSITION_MAX) { + pObject->Transition = value; + status = true; + } + } + + return status; +} + +/** + * For a given object instance-number, loads the object-name into + * a characterstring. Note that the object name must be unique + * within this device. + * + * @param object_instance - object-instance number of the object + * @param object_name - holds the object-name retrieved + * + * @return true if object-name was retrieved + */ +bool Color_Temperature_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) +{ + bool status = false; + struct object_data *pObject; + char name_text[16] = "COLOR-4194303"; + + 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), "COLOR-%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 Color_Temperature_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_COLOR) && + (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 *Color_Temperature_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 Color_Temperature_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; +} + +/** + * ReadProperty handler for this object. For the given ReadProperty + * data, the application_data is loaded or the error flags are set. + * + * @param rpdata - BACNET_READ_PROPERTY_DATA data, including + * requested data and space for the reply, or error response. + * + * @return number of APDU bytes in the response, or + * BACNET_STATUS_ERROR on error. + */ +int Color_Temperature_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = 0; /* return value */ + BACNET_CHARACTER_STRING char_string; + uint8_t *apdu = NULL; + BACNET_COLOR_COMMAND color_command = { 0 }; + + if ((rpdata == NULL) || (rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + + apdu = rpdata->application_data; + switch (rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_application_object_id( + &apdu[0], rpdata->object_type, rpdata->object_instance); + break; + case PROP_OBJECT_NAME: + Color_Temperature_Object_Name( + rpdata->object_instance, &char_string); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = + encode_application_enumerated(&apdu[0], rpdata->object_type); + break; + case PROP_PRESENT_VALUE: + apdu_len = + encode_application_unsigned(apdu, + Color_Temperature_Present_Value( + rpdata->object_instance)); + break; + case PROP_MIN_PRES_VALUE: + apdu_len = + encode_application_unsigned(apdu, + Color_Temperature_Min_Pres_Value( + rpdata->object_instance)); + break; + case PROP_MAX_PRES_VALUE: + apdu_len = + encode_application_unsigned(apdu, + Color_Temperature_Max_Pres_Value( + rpdata->object_instance)); + break; + case PROP_TRACKING_VALUE: + apdu_len = + encode_application_unsigned(apdu, + Color_Temperature_Tracking_Value( + rpdata->object_instance)); + break; + case PROP_COLOR_COMMAND: + if (Color_Temperature_Command(rpdata->object_instance, &color_command)) { + apdu_len = color_command_encode(apdu, &color_command); + } + break; + case PROP_IN_PROGRESS: + apdu_len = + encode_application_enumerated(apdu, + Color_Temperature_In_Progress(rpdata->object_instance)); + break; + case PROP_DEFAULT_COLOR_TEMPERATURE: + apdu_len = + encode_application_unsigned(apdu, + Color_Temperature_Default_Color_Temperature( + rpdata->object_instance)); + break; + case PROP_DEFAULT_FADE_TIME: + apdu_len = + encode_application_unsigned(apdu, + Color_Temperature_Default_Fade_Time(rpdata->object_instance)); + break; + case PROP_DEFAULT_RAMP_RATE: + apdu_len = + encode_application_unsigned(apdu, + Color_Temperature_Default_Ramp_Rate(rpdata->object_instance)); + break; + case PROP_DEFAULT_STEP_INCREMENT: + apdu_len = + encode_application_unsigned(apdu, + Color_Temperature_Default_Step_Increment(rpdata->object_instance)); + break; + case PROP_TRANSITION: + apdu_len = + encode_application_enumerated(apdu, + Color_Temperature_Transition(rpdata->object_instance)); + break; + case PROP_DESCRIPTION: + characterstring_init_ansi( + &char_string, Color_Temperature_Description(rpdata->object_instance)); + apdu_len = + encode_application_character_string(apdu, &char_string); + break; + default: + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = BACNET_STATUS_ERROR; + break; + } + /* only array properties can have array options */ + if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) && + (rpdata->object_property != PROP_EVENT_TIME_STAMPS) && + (rpdata->array_index != BACNET_ARRAY_ALL)) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** + * WriteProperty handler for this object. For the given WriteProperty + * data, the application_data is loaded or the error flags are set. + * + * @param wp_data - BACNET_WRITE_PROPERTY_DATA data, including + * requested data and space for the reply, or error response. + * + * @return false if an error is loaded, true if no errors + */ +bool Color_Temperature_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + /* decode the some of the request */ + len = bacapp_decode_application_data( + wp_data->application_data, wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + if (len < 0) { + /* error while decoding - a value larger than we can handle */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + return false; + } + if ((wp_data->object_property != PROP_PRIORITY_ARRAY) && + (wp_data->object_property != PROP_EVENT_TIME_STAMPS) && + (wp_data->array_index != BACNET_ARRAY_ALL)) { + /* only array properties can have array options */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + return false; + } + switch (wp_data->object_property) { + case PROP_PRESENT_VALUE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + status = Color_Temperature_Present_Value_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: + 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; +} + +/** + * @brief Sets a callback used when present-value is written from BACnet + * @param cb - callback used to provide indications + */ +void Color_Temperature_Write_Present_Value_Callback_Set( + color_temperature_write_present_value_callback cb) +{ + Color_Temperature_Write_Present_Value_Callback = cb; +} + +/** + * @brief Determines a object write-enabled flag state + * @param object_instance - object-instance number of the object + * @return write-enabled status flag + */ +bool Color_Temperature_Write_Enabled(uint32_t object_instance) +{ + bool value = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Write_Enabled; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the write-enabled flag + * @param object_instance - object-instance number of the object + */ +void Color_Temperature_Write_Enable(uint32_t object_instance) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Write_Enabled = true; + } +} + +/** + * @brief For a given object instance-number, clears the write-enabled flag + * @param object_instance - object-instance number of the object + */ +void Color_Temperature_Write_Disable(uint32_t object_instance) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Write_Enabled = false; + } +} + +/** + * Creates a Color object + * @param object_instance - object-instance number of the object + */ +bool Color_Temperature_Create(uint32_t object_instance) +{ + bool status = false; + struct object_data *pObject = NULL; + int index = 0; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + pObject = calloc(1, sizeof(struct object_data)); + if (pObject) { + 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->Changed = false; + pObject->Write_Enabled = false; + /* add to list */ + index = Keylist_Data_Add(Object_List, object_instance, pObject); + if (index >= 0) { + status = true; + Device_Inc_Database_Revision(); + } + } + } + + return status; +} + +/** + * Deletes an Color object + * @param object_instance - object-instance number of the object + * @return true if the object is deleted + */ +bool Color_Temperature_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; + Device_Inc_Database_Revision(); + } + + return status; +} + +/** + * Deletes all the Colors and their data + */ +void Color_Temperature_Cleanup(void) +{ + struct object_data *pObject; + + if (Object_List) { + do { + pObject = Keylist_Data_Pop(Object_List); + if (pObject) { + free(pObject); + Device_Inc_Database_Revision(); + } + } while (pObject); + Keylist_Delete(Object_List); + Object_List = NULL; + } +} + +/** + * Initializes the Color object data + */ +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 new file mode 100644 index 00000000..4dd3664d --- /dev/null +++ b/src/bacnet/basic/object/color_temperature.h @@ -0,0 +1,165 @@ +/** + * @file + * @author Steve Karg + * @date July 2022 + * @brief Color Temperature object, customize for your use + * + * @section DESCRIPTION + * + * The Color Temperature object is an object with a present-value that + * uses an Color Temperature INTEGER type + * + * @section LICENSE + * + * Copyright (C) 2022 Steve Karg + * + * SPDX-License-Identifier: MIT + */ +#ifndef BACNET_COLOR_TEMPERATURE_H +#define BACNET_COLOR_TEMPERATURE_H + +#include +#include +#include "bacnet/bacnet_stack_exports.h" +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacerror.h" +#include "bacnet/rp.h" +#include "bacnet/wp.h" + +/** + * @brief Callback for gateway 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 + */ +typedef void (*color_temperature_write_present_value_callback)( + uint32_t object_instance, uint32_t old_value, uint32_t value); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +void Color_Temperature_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary); +BACNET_STACK_EXPORT +bool Color_Temperature_Valid_Instance(uint32_t object_instance); +BACNET_STACK_EXPORT +unsigned Color_Temperature_Count(void); +BACNET_STACK_EXPORT +uint32_t Color_Temperature_Index_To_Instance(unsigned index); +BACNET_STACK_EXPORT +unsigned Color_Temperature_Instance_To_Index(uint32_t object_instance); + +BACNET_STACK_EXPORT +bool Color_Temperature_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name); +BACNET_STACK_EXPORT +bool Color_Temperature_Name_Set(uint32_t object_instance, char *new_name); + +BACNET_STACK_EXPORT +int Color_Temperature_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata); + +BACNET_STACK_EXPORT +bool Color_Temperature_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data); + +BACNET_STACK_EXPORT +bool Color_Temperature_Present_Value_Set( + uint32_t object_instance, uint32_t value); +BACNET_STACK_EXPORT +uint32_t Color_Temperature_Present_Value(uint32_t object_instance); +BACNET_STACK_EXPORT +void Color_Temperature_Write_Present_Value_Callback_Set( + color_temperature_write_present_value_callback cb); + +BACNET_STACK_EXPORT +bool Color_Temperature_Tracking_Value_Set( + uint32_t object_instance, uint32_t value); +BACNET_STACK_EXPORT +uint32_t Color_Temperature_Tracking_Value(uint32_t object_instance); + +BACNET_STACK_EXPORT +uint32_t Color_Temperature_Min_Pres_Value(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Color_Temperature_Min_Pres_Value_Set( + uint32_t object_instance, uint32_t value); + +BACNET_STACK_EXPORT +uint32_t Color_Temperature_Max_Pres_Value(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Color_Temperature_Max_Pres_Value_Set( + uint32_t object_instance, uint32_t value); + +BACNET_STACK_EXPORT +bool Color_Temperature_Command( + uint32_t object_instance, BACNET_COLOR_COMMAND *value); +BACNET_STACK_EXPORT +bool Color_Temperature_Command_Set( + uint32_t object_instance, BACNET_COLOR_COMMAND *value); + +BACNET_STACK_EXPORT +bool Color_Temperature_Default_Color_Temperature_Set( + uint32_t object_instance, uint32_t value); +BACNET_STACK_EXPORT +uint32_t Color_Temperature_Default_Color_Temperature(uint32_t object_instance); + +BACNET_STACK_EXPORT +uint32_t Color_Temperature_Default_Fade_Time(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Color_Temperature_Default_Fade_Time_Set( + uint32_t object_instance, uint32_t value); + +BACNET_STACK_EXPORT +uint32_t Color_Temperature_Default_Ramp_Rate( + uint32_t object_instance); +BACNET_STACK_EXPORT +bool Color_Temperature_Default_Ramp_Rate_Set( + uint32_t object_instance, uint32_t value); + +BACNET_STACK_EXPORT +uint32_t Color_Temperature_Default_Step_Increment( + uint32_t object_instance); +BACNET_STACK_EXPORT +bool Color_Temperature_Default_Step_Increment_Set( + uint32_t object_instance, uint32_t value); + +BACNET_STACK_EXPORT +BACNET_COLOR_OPERATION_IN_PROGRESS Color_Temperature_In_Progress( + uint32_t object_instance); +BACNET_STACK_EXPORT +bool Color_Temperature_In_Progress_Set( + uint32_t object_instance, BACNET_COLOR_OPERATION_IN_PROGRESS value); + +BACNET_STACK_EXPORT +BACNET_COLOR_TRANSITION Color_Temperature_Transition(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Color_Temperature_Transition_Set( + uint32_t object_instance, BACNET_COLOR_TRANSITION value); + +BACNET_STACK_EXPORT +char *Color_Temperature_Description(uint32_t instance); +BACNET_STACK_EXPORT +bool Color_Temperature_Description_Set(uint32_t instance, char *new_name); + +BACNET_STACK_EXPORT +bool Color_Temperature_Write_Enabled(uint32_t instance); +BACNET_STACK_EXPORT +void Color_Temperature_Write_Enable(uint32_t instance); +BACNET_STACK_EXPORT +void Color_Temperature_Write_Disable(uint32_t instance); + +BACNET_STACK_EXPORT +bool Color_Temperature_Create(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Color_Temperature_Delete(uint32_t object_instance); +BACNET_STACK_EXPORT +void Color_Temperature_Cleanup(void); +BACNET_STACK_EXPORT +void Color_Temperature_Init(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index 8d878a07..e46bf2c0 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -75,6 +75,10 @@ #if defined(BACFILE) #include "bacnet/basic/object/bacfile.h" #endif /* defined(BACFILE) */ +#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); @@ -223,6 +227,20 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */ }, #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 */ }, + { 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 */ }, +#endif #if defined(BACFILE) { OBJECT_FILE, bacfile_init, bacfile_count, bacfile_index_to_instance, bacfile_valid_instance, bacfile_object_name, bacfile_read_property, @@ -1312,6 +1330,91 @@ 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; + switch (rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + /* only array properties can have array options */ + if (rpdata->array_index != BACNET_ARRAY_ALL) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } else { + /* Device Object exception: requested instance + may not match our instance if a wildcard */ + if (rpdata->object_type == OBJECT_DEVICE) { + rpdata->object_instance = Object_Instance_Number; + } + apdu_len = encode_application_object_id( + &apdu[0], rpdata->object_type, rpdata->object_instance); + } + break; + case PROP_OBJECT_NAME: + /* only array properties can have array options */ + if (rpdata->array_index != BACNET_ARRAY_ALL) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } else { + characterstring_init_ansi(&char_string, ""); + if (pObject->Object_Name) { + (void)pObject->Object_Name( + rpdata->object_instance, &char_string); + } + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + } + break; + case PROP_OBJECT_TYPE: + /* only array properties can have array options */ + if (rpdata->array_index != BACNET_ARRAY_ALL) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } else { + apdu_len = encode_application_enumerated( + &apdu[0], rpdata->object_type); + } + break; +#if (BACNET_PROTOCOL_REVISION >= 14) + case PROP_PROPERTY_LIST: + Device_Objects_Property_List( + rpdata->object_type, rpdata->object_instance, &property_list); + apdu_len = property_list_encode(rpdata, + property_list.Required.pList, property_list.Optional.pList, + property_list.Proprietary.pList); + break; +#endif + default: + if (pObject->Object_Read_Property) { + apdu_len = pObject->Object_Read_Property(rpdata); + } + break; + } + + return apdu_len; +} + /** Looks up the requested Object and Property, and encodes its Value in an * APDU. * @ingroup ObjIntf @@ -1325,9 +1428,6 @@ int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) { int apdu_len = BACNET_STATUS_ERROR; struct object_functions *pObject = NULL; -#if (BACNET_PROTOCOL_REVISION >= 14) - struct special_property_list_t property_list; -#endif /* initialize the default return values */ rpdata->error_class = ERROR_CLASS_OBJECT; @@ -1336,22 +1436,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) { -#if (BACNET_PROTOCOL_REVISION >= 14) - if ((int)rpdata->object_property == PROP_PROPERTY_LIST) { - Device_Objects_Property_List(rpdata->object_type, - rpdata->object_instance, &property_list); - apdu_len = property_list_encode(rpdata, - property_list.Required.pList, - property_list.Optional.pList, - property_list.Proprietary.pList); - } else -#endif - { - apdu_len = pObject->Object_Read_Property(rpdata); - } - } + 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; @@ -1816,6 +1908,9 @@ void Device_Init(object_functions_t *object_table) } pObject++; } +#if (BACNET_PROTOCOL_REVISION >= 24) + Color_Create(1); +#endif } bool DeviceGetRRInfo(BACNET_READ_RANGE_DATA *pRequest, /* Info on the request */ diff --git a/src/bacnet/basic/service/h_apdu.c b/src/bacnet/basic/service/h_apdu.c index 5d10662b..fb31d4b8 100644 --- a/src/bacnet/basic/service/h_apdu.c +++ b/src/bacnet/basic/service/h_apdu.c @@ -313,7 +313,7 @@ static union { * @param service_choice Service, see SERVICE_CONFIRMED_X enumeration. * @return true if the service uses a Complex Error function */ -bool apdu_complex_error(BACNET_CONFIRMED_SERVICE service_choice) +bool apdu_complex_error(uint8_t service_choice) { bool status = false; @@ -547,8 +547,8 @@ void apdu_handler(BACNET_ADDRESS *src, uint8_t *service_request = NULL; uint16_t service_request_len = 0; int len = 0; /* counts where we are in PDU */ - uint32_t error_code = 0; - uint32_t error_class = 0; + BACNET_ERROR_CODE error_code = ERROR_CODE_SUCCESS; + BACNET_ERROR_CLASS error_class = ERROR_CLASS_SERVICES; uint8_t reason = 0; bool server = false; diff --git a/src/bacnet/basic/service/h_apdu.h b/src/bacnet/basic/service/h_apdu.h index f21c3926..728c8dcd 100644 --- a/src/bacnet/basic/service/h_apdu.h +++ b/src/bacnet/basic/service/h_apdu.h @@ -161,7 +161,7 @@ extern "C" { BACNET_STACK_EXPORT bool apdu_complex_error( - BACNET_CONFIRMED_SERVICE service_choice); + uint8_t service_choice); BACNET_STACK_EXPORT void apdu_set_error_handler( diff --git a/src/bacnet/basic/service/h_rp_a.c b/src/bacnet/basic/service/h_rp_a.c index 99336da8..f7b2132e 100644 --- a/src/bacnet/basic/service/h_rp_a.c +++ b/src/bacnet/basic/service/h_rp_a.c @@ -64,8 +64,9 @@ void rp_ack_print_data(BACNET_READ_PROPERTY_DATA *data) /* FIXME: what if application_data_len is bigger than 255? */ /* value? need to loop until all of the len is gone... */ for (;;) { - len = bacapp_decode_application_data( - application_data, (unsigned)application_data_len, &value); + len = bacapp_decode_known_property( + application_data, (unsigned)application_data_len, &value, + data->object_type, data->object_property); if (first_value && (len < application_data_len)) { first_value = false; #if PRINT_ENABLED diff --git a/src/bacnet/basic/service/h_rpm_a.c b/src/bacnet/basic/service/h_rpm_a.c index 94b58ad2..2d453d04 100644 --- a/src/bacnet/basic/service/h_rpm_a.c +++ b/src/bacnet/basic/service/h_rpm_a.c @@ -115,17 +115,20 @@ int rpm_ack_decode_service_request( value = calloc(1, sizeof(BACNET_APPLICATION_DATA_VALUE)); rpm_property->value = value; while (value && (apdu_len > 0)) { - if (IS_CONTEXT_SPECIFIC(*apdu)) { - len = bacapp_decode_context_data(apdu, apdu_len, value, - rpm_property->propertyIdentifier); - } else { - len = bacapp_decode_application_data( - apdu, apdu_len, value); - } + len = bacapp_decode_known_property( + apdu, (unsigned)apdu_len, value, + rpm_object->object_type, + rpm_property->propertyIdentifier); /* If len == 0 then it's an empty structure, which is OK. */ if (len < 0) { /* problem decoding */ - /* calling function will free the memory */ +#if PRINT_ENABLED + fprintf(stderr, "RPM Ack: unable to decode! %s:%s\n", + bactext_object_type_name(rpm_object->object_type), + bactext_property_name( + rpm_property->propertyIdentifier)); +#endif + /* note: caller will free the memory */ return BACNET_STATUS_ERROR; } decoded_len += len; @@ -220,11 +223,17 @@ void rpm_ack_print_data(BACNET_READ_ACCESS_DATA *rpm_data) listOfProperties = rpm_data->listOfProperties; while (listOfProperties) { #if PRINT_ENABLED - if (listOfProperties->propertyIdentifier < 512) { + if ((listOfProperties->propertyIdentifier < 512) || + (listOfProperties->propertyIdentifier > 4194303)) { + /* Enumerated values 0-511 and 4194304+ are reserved + for definition by ASHRAE.*/ fprintf(stdout, " %s: ", bactext_property_name( listOfProperties->propertyIdentifier)); } else { + /* Enumerated values 512-4194303 may be used + by others subject to the procedures and + constraints described in Clause 23. */ fprintf(stdout, " proprietary %u: ", (unsigned)listOfProperties->propertyIdentifier); } @@ -357,7 +366,7 @@ void handler_read_property_multiple_ack(uint8_t *service_request, rpm_data = rpm_data_free(rpm_data); } } else { -#if 1 +#if PRINT_ENABLED fprintf(stderr, "RPM Ack Malformed! Freeing memory...\n"); #endif while (rpm_data) { diff --git a/src/bacnet/basic/sys/color_rgb.c b/src/bacnet/basic/sys/color_rgb.c new file mode 100644 index 00000000..d40d3936 --- /dev/null +++ b/src/bacnet/basic/sys/color_rgb.c @@ -0,0 +1,486 @@ +/** + * @file + * @author Steve Karg + * @date 2022 + * @brief computes sRGB to and from from CIE xy and brightness + * + * @section LICENSE + * + * Public domain algorithms from Philips and W3C + * + */ +#include +#include +#include +#include +#include +#include "bacnet/basic/sys/color_rgb.h" + +/** + * @brief Clamp a double precision value between two limits + * @param d - value to be clamped + * @param min - minimum value to clamp within + * @param max - maximum value to clamp within + * @return value clamped between min and max inclusive + */ +static double clamp(double d, double min, double max) +{ + if (isnan(d)) { + return min; + } else { + const double t = d < min ? min : d; + return t > max ? max : t; + } +} + +/** + * @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) +{ + /* Get the RGB values from your color object + and convert them to be between 0 and 1. + So the RGB color (255, 0, 100) becomes (1.0, 0.0, 0.39) */ + float red = (float)r; + float green = (float)g; + float blue = (float)b; + + red /= 255.0f; + 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); + + /* Convert the RGB values to XYZ using the + Wide RGB D65 conversion formula */ + float X = red * 0.649926f + green * 0.103455f + blue * 0.197109f; + float Y = red * 0.234327f + green * 0.743075f + blue * 0.022598f; + float Z = red * 0.0000000f + green * 0.053077f + blue * 1.035763f; + + /* Calculate the xy values from the XYZ values */ + float x = X / (X + Y + Z); + float y = Y / (X + Y + Z); + + x = clamp(x, 0.0f, 1.0f); + y = clamp(y, 0.0f, 1.0f); + + /* copy to return values if possible */ + if (x_coordinate) { + *x_coordinate = x; + } + if (y_coordinate) { + *y_coordinate = y; + } + + /* Use the Y value of XYZ as brightness + The Y value indicates the brightness + of the converted color. */ + Y = Y*255.0f; + Y = clamp(Y, 0.0f, 255.0f); + if (brightness) { + *brightness = (uint8_t)Y; + } +} + +/** + * @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) +{ + /* Calculate XYZ values */ + float x = x_coordinate; + float y = y_coordinate; + float z = 1.0f - x - y; + float Y = brightness; + Y /= 255.0f; + float X = (Y / y) * x; + float Z = (Y / y) * z; + + /* Convert to RGB using Wide RGB D65 conversion + (THIS IS A D50 conversion currently) */ + float r = X * 1.4628067f - Y * 0.1840623f - Z * 0.2743606f; + float g = -X * 0.5217933f + Y * 1.4472381f + Z * 0.0677227f; + float 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; + + /* Convert the RGB values to your color object + The rgb values from the above formulas are + between 0.0 and 1.0. */ + r = r*255.0f; + r = clamp(r, 0.0f, 255.0f); + g = g*255; + g = clamp(g, 0.0f, 255.0f); + b = b*255; + b = clamp(b, 0.0f, 255.0f); + /* copy to return value if possible */ + if (red) { + *red = (uint8_t)r; + } + if (green) { + *green = (uint8_t)g; + } + if (blue) { + *blue = (uint8_t)b; + } +} + +/* table for converting RGB to and from ASCII color names */ +struct css_color_rgb { + const char *name; + uint8_t red; + uint8_t green; + uint8_t blue; +}; +static struct css_color_rgb CSS_Color_RGB_Table[] = { + {"aliceblue", 240, 248, 255}, + {"antiquewhite", 250, 235, 215}, + {"aqua", 0, 255, 255}, + {"aquamarine", 127, 255, 212}, + {"azure", 240, 255, 255}, + {"beige", 245, 245, 220}, + {"bisque", 255, 228, 196}, + {"black", 0, 0, 0}, + {"blanchedalmond", 255, 235, 205}, + {"blue", 0, 0, 255}, + {"blueviolet", 138, 43, 226}, + {"brown", 165, 42, 42}, + {"burlywood", 222, 184, 135}, + {"cadetblue", 95, 158, 160}, + {"chartreuse", 127, 255, 0}, + {"chocolate", 210, 105, 30}, + {"coral", 255, 127, 80}, + {"cornflowerblue", 100, 149, 237}, + {"cornsilk", 255, 248, 220}, + {"crimson", 220, 20, 60}, + {"cyan", 0, 255, 255}, + {"darkblue", 0, 0, 139}, + {"darkcyan", 0, 139, 139}, + {"darkgoldenrod", 184, 134, 11}, + {"darkgray", 169, 169, 169}, + {"darkgreen", 0, 100, 0}, + {"darkgrey", 169, 169, 169}, + {"darkkhaki", 189, 183, 107}, + {"darkmagenta", 139, 0, 139}, + {"darkolivegreen", 85, 107, 47}, + {"darkorange", 255, 140, 0}, + {"darkorchid", 153, 50, 204}, + {"darkred", 139, 0, 0}, + {"darksalmon", 233, 150, 122}, + {"darkseagreen", 143, 188, 143}, + {"darkslateblue", 72, 61, 139}, + {"darkslategray", 47, 79, 79}, + {"darkslategrey", 47, 79, 79}, + {"darkturquoise", 0, 206, 209}, + {"darkviolet", 148, 0, 211}, + {"deeppink", 255, 20, 147}, + {"deepskyblue", 0, 191, 255}, + {"dimgray", 105, 105, 105}, + {"dimgrey", 105, 105, 105}, + {"dodgerblue", 30, 144, 255}, + {"firebrick", 178, 34, 34}, + {"floralwhite", 255, 250, 240}, + {"forestgreen", 34, 139, 34}, + {"fuchsia", 255, 0, 255}, + {"gainsboro", 220, 220, 220}, + {"ghostwhite", 248, 248, 255}, + {"gold", 255, 215, 0}, + {"goldenrod", 218, 165, 32}, + {"gray", 128, 128, 128}, + {"green", 0, 128, 0}, + {"greenyellow", 173, 255, 47}, + {"grey", 128, 128, 128}, + {"honeydew", 240, 255, 240}, + {"hotpink", 255, 105, 180}, + {"indianred", 205, 92, 92}, + {"indigo", 75, 0, 130}, + {"ivory", 255, 255, 240}, + {"khaki", 240, 230, 140}, + {"lavender", 230, 230, 250}, + {"lavenderblush", 255, 240, 245}, + {"lawngreen", 124, 252, 0}, + {"lemonchiffon", 255, 250, 205}, + {"lightblue", 173, 216, 230}, + {"lightcoral", 240, 128, 128}, + {"lightcyan", 224, 255, 255}, + {"lightgoldenrodyellow", 250, 250, 210}, + {"lightgray", 211, 211, 211}, + {"lightgreen", 144, 238, 144}, + {"lightgrey", 211, 211, 211}, + {"lightpink", 255, 182, 193}, + {"lightsalmon", 255, 160, 122}, + {"lightseagreen", 32, 178, 170}, + {"lightskyblue", 135, 206, 250}, + {"lightslategray", 119, 136, 153}, + {"lightslategrey", 119, 136, 153}, + {"lightsteelblue", 176, 196, 222}, + {"lightyellow", 255, 255, 224}, + {"lime", 0, 255, 0}, + {"limegreen", 50, 205, 50}, + {"linen", 250, 240, 230}, + {"magenta", 255, 0, 255}, + {"maroon", 128, 0, 0}, + {"mediumaquamarine", 102, 205, 170}, + {"mediumblue", 0, 0, 205}, + {"mediumorchid", 186, 85, 211}, + {"mediumpurple", 147, 112, 219}, + {"mediumseagreen", 60, 179, 113}, + {"mediumslateblue", 123, 104, 238}, + {"mediumspringgreen", 0, 250, 154}, + {"mediumturquoise", 72, 209, 204}, + {"mediumvioletred", 199, 21, 133}, + {"midnightblue", 25, 25, 112}, + {"mintcream", 245, 255, 250}, + {"mistyrose", 255, 228, 225}, + {"moccasin", 255, 228, 181}, + {"navajowhite", 255, 222, 173}, + {"navy", 0, 0, 128}, + {"navyblue", 0, 0, 128}, + {"oldlace", 253, 245, 230}, + {"olive", 128, 128, 0}, + {"olivedrab", 107, 142, 35}, + {"orange", 255, 165, 0}, + {"orangered", 255, 69, 0}, + {"orchid", 218, 112, 214}, + {"palegoldenrod", 238, 232, 170}, + {"palegreen", 152, 251, 152}, + {"paleturquoise", 175, 238, 238}, + {"palevioletred", 219, 112, 147}, + {"papayawhip", 255, 239, 213}, + {"peachpuff", 255, 218, 185}, + {"peru", 205, 133, 63}, + {"pink", 255, 192, 203}, + {"plum", 221, 160, 221}, + {"powderblue", 176, 224, 230}, + {"purple", 128, 0, 128}, + {"red", 255, 0, 0}, + {"rosybrown", 188, 143, 143}, + {"royalblue", 65, 105, 225}, + {"saddlebrown", 139, 69, 19}, + {"salmon", 250, 128, 114}, + {"sandybrown", 244, 164, 96}, + {"seagreen", 46, 139, 87}, + {"seashell", 255, 245, 238}, + {"sienna", 160, 82, 45}, + {"silver", 192, 192, 192}, + {"skyblue", 135, 206, 235}, + {"slateblue", 106, 90, 205}, + {"slategray", 112, 128, 144}, + {"slategrey", 112, 128, 144}, + {"snow", 255, 250, 250}, + {"springgreen", 0, 255, 127}, + {"steelblue", 70, 130, 180}, + {"tan", 210, 180, 140}, + {"teal", 0, 128, 128}, + {"thistle", 216, 191, 216}, + {"tomato", 255, 99, 71}, + {"turquoise", 64, 224, 208}, + {"violet", 238, 130, 238}, + {"wheat", 245, 222, 179}, + {"white", 255, 255, 255}, + {"whitesmoke", 245, 245, 245}, + {"yellow", 255, 255, 0}, + {"yellowgreen", 154, 205, 50}, + {NULL, 0, 0, 0} +}; + +/** + * @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 + * @return name - CSS color name from W3C or "" if not found + * @note Official CSS3 colors from w3.org: + * https://www.w3.org/TR/2010/PR-css3-color-20101028/#html4 + * names do not have spaces + */ +const char * color_rgb_to_ascii(uint8_t red, uint8_t green, uint8_t blue) +{ + const char * name = ""; + unsigned index = 0; + + while (CSS_Color_RGB_Table[index].name) { + if ((red == CSS_Color_RGB_Table[index].red) && + (green == CSS_Color_RGB_Table[index].green) && + (blue == CSS_Color_RGB_Table[index].blue)) { + return CSS_Color_RGB_Table[index].name; + } + index++; + }; + + return name; +} + +/** + * @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 name - CSS color name from W3C + * @return index 0..color_rgb_count(), where color_rgb_count() is not found. + */ +unsigned color_rgb_from_ascii(uint8_t *red, uint8_t *green, uint8_t *blue, + const char *name) +{ + unsigned index = 0; + + while (CSS_Color_RGB_Table[index].name) { + if (strcmp(CSS_Color_RGB_Table[index].name, name) == 0) { + if (red) { + *red = CSS_Color_RGB_Table[index].red; + } + if (green) { + *green = CSS_Color_RGB_Table[index].green; + } + if (blue) { + *blue = CSS_Color_RGB_Table[index].blue; + } + break; + } + index++; + }; + + return index; +} + +/** + * @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 + * @return CSS ASCII color name from W3C or NULL if invalid index + */ +const char *color_rgb_from_index(unsigned target_index, uint8_t *red, uint8_t *green, uint8_t *blue) +{ + unsigned index = 0; + + while (CSS_Color_RGB_Table[index].name) { + if (target_index == index) { + if (red) { + *red = CSS_Color_RGB_Table[index].red; + } + if (green) { + *green = CSS_Color_RGB_Table[index].green; + } + if (blue) { + *blue = CSS_Color_RGB_Table[index].blue; + } + return CSS_Color_RGB_Table[index].name; + } + index++; + }; + + return NULL; +} + +/** + * @brief Gets the number of sRGB names from CSS3 defines in W3C + * @return the number of defined RGB names + */ +unsigned color_rgb_count(void) +{ + unsigned count = 0; + + while (CSS_Color_RGB_Table[count].name) { + count++; + } + + return count; +} + +/** + * @brief Return an RGB color from a color temperature in Kelvin. + * @note This is a rough approximation based on the formula + * provided by T. Helland + * http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/ + */ +void color_rgb_from_temperature( + uint16_t temperature_kelvin, + uint8_t *r, uint8_t *g, uint8_t *b) +{ + float red = 0, green = 0, blue = 0; + + if (temperature_kelvin < 1000) { + temperature_kelvin = 1000; + } else if (temperature_kelvin > 40000) { + temperature_kelvin = 40000; + } + temperature_kelvin /= 100; + + /* Calculate Red */ + if (temperature_kelvin <= 66) { + /* Red values below 6600 K are always 255 */ + red = 255.0; + } else { + red = temperature_kelvin - 60; + red = 329.698727446 * pow(red, -0.1332047592); + red = clamp(red, 0.0, 255.0); + } + /* Calculate Green */ + if (temperature_kelvin <= 66) { + /* Green values below 6600 K */ + green = temperature_kelvin; + green = 99.4708025861 * log(green) - 161.1195681661; + } else { + green = temperature_kelvin - 60; + green = 288.1221695283 * pow(green, -0.0755148492); + } + green = clamp(green, 0.0, 255.0); + /* Calculate Blue */ + if (temperature_kelvin >= 66) { + /* Blue values above 6600 K */ + blue = 255.0; + } else if (temperature_kelvin <= 19) { + /* Blue values below 1900 K */ + blue = 0.0; + } else { + blue = temperature_kelvin - 10; + blue = 138.5177312231 * log(blue) - 305.0447927307; + blue = clamp(blue, 0, 255); + } + if (r) { + *r = red; + } + if (g) { + *g = green; + } + if (b) { + *b = blue; + } +} diff --git a/src/bacnet/basic/sys/color_rgb.h b/src/bacnet/basic/sys/color_rgb.h new file mode 100644 index 00000000..2e754f82 --- /dev/null +++ b/src/bacnet/basic/sys/color_rgb.h @@ -0,0 +1,44 @@ +/** + * @file + * @author Steve Karg + * @date 2022 + * @brief API Color sRGB to CIE xy and brightness + */ +#ifndef COLOR_RGB_H +#define COLOR_RGB_H +#include +#include +#include "bacnet/bacnet_stack_exports.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +BACNET_STACK_EXPORT +void color_rgb_to_xy(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(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 +unsigned color_rgb_from_ascii(uint8_t *red, uint8_t *green, uint8_t *blue, + const char *name); +BACNET_STACK_EXPORT +const char *color_rgb_from_index(unsigned target_index, uint8_t *red, + uint8_t *green, uint8_t *blue); +BACNET_STACK_EXPORT +unsigned color_rgb_count(void); + +BACNET_STACK_EXPORT +void color_rgb_from_temperature( + uint16_t temperature_kelvin, + uint8_t *r, uint8_t *g, uint8_t *b); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/lighting.c b/src/bacnet/lighting.c index d5443c8c..34243666 100644 --- a/src/bacnet/lighting.c +++ b/src/bacnet/lighting.c @@ -1,36 +1,14 @@ -/*####COPYRIGHTBEGIN#### - ------------------------------------------- - Copyright (C) 2013 Steve Karg - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to: - The Free Software Foundation, Inc. - 59 Temple Place - Suite 330 - Boston, MA 02111-1307, USA. - - As a special exception, if other files instantiate templates or - use macros or inline functions from this file, or you compile - this file and link it with other works to produce a work based - on this file, this file does not by itself cause the resulting - work to be covered by the GNU General Public License. However - the source code for this file must still be made available in - accordance with section (3) of the GNU General Public License. - - This exception does not invalidate any other reasons why a work - based on this file might be covered by the GNU General Public - License. - ------------------------------------------- -####COPYRIGHTEND####*/ +/** + * @file + * @brief API for BACnetLightingCommand and BACnetColorCommand + * @author Steve Karg + * @date June 2022 + * @section LICENSE + * + * Copyright (C) 2022 Steve Karg + * + * SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 + */ #include #include #include @@ -38,11 +16,12 @@ #include #include #include -#include "bacnet/lighting.h" #include "bacnet/bacdcode.h" +#include "bacnet/bacreal.h" +#include "bacnet/lighting.h" #ifndef islessgreater -#define islessgreater( x, y) ((x) < (y) || (x) > (y)) +#define islessgreater(x, y) ((x) < (y) || (x) > (y)) #endif /** @file lighting.c Manipulate BACnet lighting command values */ @@ -313,3 +292,713 @@ bool lighting_command_same( return status; } + +/** + * @brief Encode a BACnetxyColor complex data type + * + * BACnetxyColor::= SEQUENCE { + * x-coordinate REAL, --(0.0 to 1.0) + * y-coordinate REAL --(0.0 to 1.0) + * } + * @param apdu - the APDU buffer, or NULL for length + * @param value - BACnetxyColor structure + * @return length of the encoded APDU buffer + */ +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); + apdu_len += len; + /* y-coordinate REAL */ + if (apdu) { + apdu_offset = &apdu[apdu_len]; + } + len = encode_bacnet_real(value->y_coordinate, apdu_offset); + apdu_len += len; + } + + return apdu_len; +} + +/** + * @brief Encode a context tagged BACnetxyColor complex data type + * @param apdu - the APDU buffer + * @param tag_number - the APDU buffer size + * @param value - BACnetxyColor structure + * @return length of the APDU buffer, or 0 if not able to encode + */ +int xy_color_context_encode( + uint8_t *apdu, uint8_t tag_number, BACNET_XY_COLOR *value) +{ + 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); + apdu_len += len; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + } + len = xy_color_encode(apdu_offset, value); + apdu_len += len; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + } + len = encode_closing_tag(apdu_offset, tag_number); + apdu_len += len; + } + + return apdu_len; +} + +/** + * @brief Decode the BACnetxyColor complex data type Value + * + * @param apdu - buffer of data to be decoded + * @param apdu_size - the size of the data buffer + * @param value - decoded BACnetxyColor, if decoded + * + * @return the number of apdu bytes consumed + */ +int xy_color_decode(uint8_t *apdu, uint32_t apdu_size, BACNET_XY_COLOR *value) +{ + float real_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 (value) { + value->x_coordinate = real_value; + } + apdu_len += len; + } + len = decode_real(&apdu[4], &real_value); + if (len == 4) { + if (value) { + value->y_coordinate = real_value; + } + apdu_len += len; + } + } + + return apdu_len; +} + +/** + * @brief Decode the BACnetxyColor complex data type Value + * + * @param apdu - buffer of data to be decoded + * @param apdu_size - the size of the data buffer + * @param value - decoded BACnetxyColor, if decoded + * + * @return the number of apdu bytes consumed + */ +int xy_color_context_decode(uint8_t *apdu, + uint32_t apdu_size, + uint8_t tag_number, + 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; + } + } + } + } + } + + return rlen; +} + +/** + * @brief Copy the BACnetxyColor complex data from src to dst + * @param dst - destination BACNET_XY_COLOR structure + * @param src - source BACNET_XY_COLOR structure + * @return true if successfully copied + */ +int xy_color_copy(BACNET_XY_COLOR *dst, BACNET_XY_COLOR *src) +{ + bool status = false; + + if (dst && src) { + dst->x_coordinate = src->x_coordinate; + dst->y_coordinate = src->y_coordinate; + status = true; + } + + return status; +} + +/** + * @brief Compare the BACnetxyColor complex data + * @param value1 - BACNET_XY_COLOR structure + * @param value2 - BACNET_XY_COLOR structure + * @return true if the same + */ +bool xy_color_same(BACNET_XY_COLOR *value1, BACNET_XY_COLOR *value2) +{ + bool status = false; + + if (value1 && value2) { + if ((value1->x_coordinate == value2->x_coordinate) && + (value1->y_coordinate == value2->y_coordinate)) { + status = true; + } + } + + return status; +} + +/** + * @brief Encode a BACnetColorCommand complex data type + * + * BACnetColorCommand structure ::= SEQUENCE { + * operation [0] BACnetColorOperation, + * target-color [1] BACnetxyColor OPTIONAL, + * target-color-temperature [2] Unsigned OPTIONAL, + * fade-time [3] Unsigned (100.. 86400000) OPTIONAL, + * ramp-rate [4] Unsigned (1..30000) OPTIONAL, + * step-increment [5] Unsigned (1..30000) OPTIONAL + * } + * + * @param apdu - the APDU buffer, or NULL for length + * @param value - BACnetColorCommand structure + * @return length of the encoded APDU buffer + */ +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); + apdu_len += 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); + apdu_len += 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) */ + unsigned_value = value->transit.fade_time; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + } + len = + encode_context_unsigned(apdu_offset, 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); + apdu_len += 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) */ + unsigned_value = value->transit.fade_time; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + } + len = + encode_context_unsigned(apdu_offset, 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); + apdu_len += 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) */ + unsigned_value = value->transit.ramp_rate; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + } + len = + encode_context_unsigned(apdu_offset, 4, unsigned_value); + apdu_len += len; + } + break; + case BACNET_COLOR_OPERATION_STEP_UP_CCT: + case BACNET_COLOR_OPERATION_STEP_DOWN_CCT: + if ((value->transit.step_increment >= + 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) */ + unsigned_value = value->transit.step_increment; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + } + len = + encode_context_unsigned(apdu_offset, 5, unsigned_value); + apdu_len += len; + } + break; + case BACNET_COLOR_OPERATION_STOP: + break; + default: + break; + } + } + + return apdu_len; +} + +/** + * @brief Encode a context tagged BACnetColorCommand complex data type + * @param apdu - the APDU buffer + * @param tag_number - the APDU buffer size + * @param address - IP address and port number + * @return length of the APDU buffer, or 0 if not able to encode + */ +int color_command_context_encode( + uint8_t *apdu, uint8_t tag_number, BACNET_COLOR_COMMAND *value) +{ + 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); + apdu_len += len; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + } + len = color_command_encode(apdu_offset, value); + apdu_len += len; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + } + len = encode_closing_tag(apdu_offset, tag_number); + apdu_len += len; + } + + return apdu_len; +} + +/** + * @brief Decode the BACnetColorCommand complex data + * + * BACnetColorCommand ::= SEQUENCE { + * operation [0] BACnetColorOperation, + * target-color [1] BACnetxyColor OPTIONAL, + * target-color-temperature [2] Unsigned OPTIONAL, + * fade-time [3] Unsigned (100.. 86400000) OPTIONAL, + * ramp-rate [4] Unsigned (1..30000) OPTIONAL, + * step-increment [5] Unsigned (1..30000) OPTIONAL + * } + * + * @param apdu - the APDU buffer + * @param apdu_len - the APDU buffer length + * @param value - BACnetColorCommand structure values + * @return length of the APDU buffer decoded, or ERROR, REJECT, or ABORT + */ +int color_command_decode(uint8_t *apdu, + uint16_t apdu_size, + BACNET_ERROR_CODE *error_code, + BACNET_COLOR_COMMAND *value) +{ + int len = 0; + int apdu_len = 0; + BACNET_UNSIGNED_INTEGER unsigned_value = 0; + BACNET_COLOR_OPERATION operation = BACNET_COLOR_OPERATION_NONE; + BACNET_XY_COLOR color; + + /* default reject code */ + if (error_code) { + *error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER; + } + /* check for value pointers */ + if ((apdu_size == 0) || (!apdu)) { + return BACNET_STATUS_REJECT; + } + /* operation [0] BACnetColorOperation */ + len = bacnet_unsigned_context_decode( + apdu, apdu_size - apdu_len, 0, &unsigned_value); + if (len <= 0) { + 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; + } + apdu_len += len; + if (unsigned_value >= BACNET_COLOR_OPERATION_MAX) { + if (error_code) { + *error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + } + return BACNET_STATUS_REJECT; + } + operation = (BACNET_COLOR_OPERATION)unsigned_value; + if (value) { + value->operation = operation; + } + switch (operation) { + case BACNET_COLOR_OPERATION_NONE: + 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 (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) { + /* 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) { + 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; + } + apdu_len += len; + if ((unsigned_value < BACNET_COLOR_FADE_TIME_MIN) || + (unsigned_value > BACNET_COLOR_FADE_TIME_MAX)) { + if (error_code) { + *error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + } + return BACNET_STATUS_REJECT; + } + 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) { + 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; + } + apdu_len += len; + if (unsigned_value > UINT16_MAX) { + if (error_code) { + *error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + } + return BACNET_STATUS_REJECT; + } + if (value) { + value->target.color_temperature = unsigned_value; + } + 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) { + 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; + } + apdu_len += len; + if ((unsigned_value < BACNET_COLOR_FADE_TIME_MIN) || + (unsigned_value > BACNET_COLOR_FADE_TIME_MAX)) { + if (error_code) { + *error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + } + return BACNET_STATUS_REJECT; + } + 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) { + 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; + } + apdu_len += len; + if (unsigned_value > UINT16_MAX) { + if (error_code) { + *error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + } + return BACNET_STATUS_REJECT; + } + if (value) { + value->target.color_temperature = unsigned_value; + } + if ((apdu_size - apdu_len) != 0) { + /* ramp-rate [4] Unsigned (1..30000) */ + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 4, &unsigned_value); + if (len <= 0) { + 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; + } + apdu_len += len; + if ((unsigned_value < BACNET_COLOR_RAMP_RATE_MIN) || + (unsigned_value > BACNET_COLOR_RAMP_RATE_MAX)) { + if (error_code) { + *error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + } + return BACNET_STATUS_REJECT; + } + 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; + } + } else { + if (error_code) { + *error_code = + ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER; + } + } + 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; + } + return BACNET_STATUS_REJECT; + } + if (value) { + value->transit.step_increment = unsigned_value; + } + break; + case BACNET_COLOR_OPERATION_STOP: + break; + default: + break; + } + + return apdu_len; +} + +/** + * @brief Copy the BACnetColorCommand complex data from src to dst + * @param dst - destination structure + * @param src - source structure + * @return true if successfully copied + */ +bool color_command_copy(BACNET_COLOR_COMMAND *dst, BACNET_COLOR_COMMAND *src) +{ + bool status = false; + + if (dst && src) { + memcpy(dst, src, sizeof(BACNET_COLOR_COMMAND)); + status = true; + } + + return status; +} + +/** + * @brief Compare the BACnetColorCommand complex data + * @param value1 - BACNET_COLOR_COMMAND structure + * @param value2 - BACNET_COLOR_COMMAND structure + * @return true if the same + */ +bool color_command_same( + BACNET_COLOR_COMMAND *value1, BACNET_COLOR_COMMAND *value2) +{ + bool status = false; + + if (value1 && value2 && (value1->operation == value2->operation)) { + switch (value1->operation) { + case BACNET_COLOR_OPERATION_NONE: + status = true; + break; + case BACNET_COLOR_OPERATION_FADE_TO_COLOR: + if ((value1->target.color.x_coordinate == + value2->target.color.x_coordinate) && + (value1->target.color.y_coordinate == + value2->target.color.y_coordinate) && + (value1->transit.fade_time == value2->transit.fade_time)) { + status = true; + } + break; + case BACNET_COLOR_OPERATION_FADE_TO_CCT: + if ((value1->target.color_temperature == + value2->target.color_temperature) && + (value1->transit.fade_time == value2->transit.fade_time)) { + status = true; + } + break; + case BACNET_COLOR_OPERATION_RAMP_TO_CCT: + if ((value1->target.color_temperature == + value2->target.color_temperature) && + (value1->transit.ramp_rate == value2->transit.ramp_rate)) { + status = true; + } + break; + case BACNET_COLOR_OPERATION_STEP_UP_CCT: + case BACNET_COLOR_OPERATION_STEP_DOWN_CCT: + if (value1->transit.step_increment == + value2->transit.step_increment) { + status = true; + } + break; + case BACNET_COLOR_OPERATION_STOP: + status = true; + break; + default: + break; + } + } + + return status; +} diff --git a/src/bacnet/lighting.h b/src/bacnet/lighting.h index d9bc63e2..0670505f 100644 --- a/src/bacnet/lighting.h +++ b/src/bacnet/lighting.h @@ -1,26 +1,15 @@ -/************************************************************************** -* -* Copyright (C) 2012 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 + * @brief API for BACnetLightingCommand and BACnetColorCommand + * @author Steve Karg + * @date June 2022 + * @section LICENSE + * + * Copyright (C) 2022 Steve Karg + * + * + * SPDX-License-Identifier: MIT + */ #ifndef LIGHTING_H #define LIGHTING_H @@ -55,6 +44,48 @@ typedef struct BACnetLightingCommand { uint8_t priority; } BACNET_LIGHTING_COMMAND; +/** + * BACnetxyColor::= SEQUENCE { + * x-coordinate REAL, --(0.0 to 1.0) + * y-coordinate REAL --(0.0 to 1.0) + * } + */ +typedef struct BACnetXYColor { + float x_coordinate; + float y_coordinate; +} BACNET_XY_COLOR; + +/** + * BACnetColorCommand ::= SEQUENCE { + * operation [0] BACnetColorOperation, + * target-color [1] BACnetxyColor OPTIONAL, + * target-color-temperature [2] Unsigned OPTIONAL, + * fade-time [3] Unsigned (100.. 86400000) OPTIONAL, + * ramp-rate [4] Unsigned (1..30000) OPTIONAL, + * step-increment [5] Unsigned (1..30000) OPTIONAL + * } + */ +typedef struct BACnetColorCommand { + BACNET_COLOR_OPERATION operation; + union { + BACNET_XY_COLOR color; + uint16_t color_temperature; + } target; + union { + uint32_t fade_time; + uint16_t ramp_rate; + uint16_t step_increment; + } transit; +} BACNET_COLOR_COMMAND; + +/* range restrictions */ +#define BACNET_COLOR_FADE_TIME_MIN 100ul +#define BACNET_COLOR_FADE_TIME_MAX 86400000ul +#define BACNET_COLOR_RAMP_RATE_MIN 1ul +#define BACNET_COLOR_RAMP_RATE_MAX 30000ul +#define BACNET_COLOR_STEP_INCREMENT_MIN 1ul +#define BACNET_COLOR_STEP_INCREMENT_MAX 30000ul + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -82,6 +113,79 @@ extern "C" { BACNET_LIGHTING_COMMAND * dst, BACNET_LIGHTING_COMMAND * src); + BACNET_STACK_EXPORT + int xy_color_encode(uint8_t *apdu, + BACNET_XY_COLOR *value); + BACNET_STACK_EXPORT + int xy_color_context_encode( + uint8_t * apdu, + uint8_t tag_number, + BACNET_XY_COLOR *value); + BACNET_STACK_EXPORT + int xy_color_decode( + uint8_t *apdu, + uint32_t apdu_size, + BACNET_XY_COLOR *value); + BACNET_STACK_EXPORT + int xy_color_context_decode( + uint8_t *apdu, + uint32_t apdu_size, + uint8_t tag_number, + BACNET_XY_COLOR *value); + BACNET_STACK_EXPORT + int xy_color_copy( + BACNET_XY_COLOR *dst, + BACNET_XY_COLOR *src); + BACNET_STACK_EXPORT + bool xy_color_same( + BACNET_XY_COLOR *value1, + BACNET_XY_COLOR *value2); + + BACNET_STACK_EXPORT + int color_command_encode( + uint8_t * apdu, + BACNET_COLOR_COMMAND *address); + BACNET_STACK_EXPORT + int color_command_context_encode( + uint8_t * apdu, + uint8_t tag_number, + BACNET_COLOR_COMMAND *address); + BACNET_STACK_EXPORT + int color_command_decode(uint8_t *apdu, + uint16_t apdu_len, + BACNET_ERROR_CODE *error_code, + BACNET_COLOR_COMMAND *address); + BACNET_STACK_EXPORT + bool color_command_copy( + BACNET_COLOR_COMMAND * dst, + BACNET_COLOR_COMMAND * src); + BACNET_STACK_EXPORT + bool color_command_same( + BACNET_COLOR_COMMAND * dst, + BACNET_COLOR_COMMAND * src); + + BACNET_STACK_EXPORT + void color_rgb_to_xy( + uint8_t r, + uint8_t g, + uint8_t b, + float *x_coordinate, + float *y_coordinate, + float *brightness); + BACNET_STACK_EXPORT + void color_rgb_from_xy( + uint8_t *red, + uint8_t *green, + uint8_t *blue, + float x_coordinate, + float y_coordinate, + float brightness); + BACNET_STACK_EXPORT + const char * color_rgb_to_ascii(uint8_t red, uint8_t green, uint8_t blue); + BACNET_STACK_EXPORT + bool color_rgb_from_ascii(uint8_t *red, uint8_t *green, uint8_t *blue, + const char *name); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/bacnet/property.c b/src/bacnet/property.c index a0bb0fbd..274e9328 100644 --- a/src/bacnet/property.c +++ b/src/bacnet/property.c @@ -398,6 +398,17 @@ static const int CharacterString_Value_Properties_Optional[] = { PROP_PROFILE_LOCATION, PROP_PROFILE_NAME, -1 }; +static const int Color_Properties_Required[] = { + PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, + PROP_PRESENT_VALUE, PROP_TRACKING_VALUE, PROP_COLOR_COMMAND, + PROP_IN_PROGRESS, PROP_DEFAULT_COLOR, PROP_DEFAULT_FADE_TIME, -1 +}; + +static const int Color_Properties_Optional[] = { PROP_RELIABILITY, + PROP_DESCRIPTION, PROP_TRANSITION, PROP_VALUE_SOURCE, + PROP_AUDIT_LEVEL, PROP_AUDITABLE_OPERATIONS, PROP_TAGS, + PROP_PROFILE_LOCATION, PROP_PROFILE_NAME, -1 }; + static const int Credential_Data_Input_Properties_Required[] = { PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_PRESENT_VALUE, PROP_STATUS_FLAGS, PROP_RELIABILITY, @@ -1141,6 +1152,9 @@ const int *property_list_optional(BACNET_OBJECT_TYPE object_type) case OBJECT_CHARACTERSTRING_VALUE: pList = CharacterString_Value_Properties_Optional; break; + case OBJECT_COLOR: + pList = Color_Properties_Optional; + break; case OBJECT_CREDENTIAL_DATA_INPUT: pList = Credential_Data_Input_Properties_Optional; break; @@ -1346,6 +1360,9 @@ const int *property_list_required(BACNET_OBJECT_TYPE object_type) case OBJECT_CHARACTERSTRING_VALUE: pList = CharacterString_Value_Properties_Required; break; + case OBJECT_COLOR: + pList = Color_Properties_Required; + break; case OBJECT_CREDENTIAL_DATA_INPUT: pList = Credential_Data_Input_Properties_Required; break; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 195c5098..c57d72da 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -89,6 +89,7 @@ list(APPEND testdirs bacnet/basic/object/bi bacnet/basic/object/bo bacnet/basic/object/bv + bacnet/basic/object/color_object bacnet/basic/object/command bacnet/basic/object/credential_data_input bacnet/basic/object/device @@ -104,6 +105,7 @@ list(APPEND testdirs bacnet/basic/object/piv bacnet/basic/object/schedule # basic/sys + bacnet/basic/sys/color_rgb bacnet/basic/sys/days bacnet/basic/sys/fifo bacnet/basic/sys/filename diff --git a/test/bacnet/bacapp/CMakeLists.txt b/test/bacnet/bacapp/CMakeLists.txt index 31e10ea1..4a20794b 100644 --- a/test/bacnet/bacapp/CMakeLists.txt +++ b/test/bacnet/bacapp/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/datalink/bvlc.c # Test and test library files diff --git a/test/bacnet/bacdevobjpropref/CMakeLists.txt b/test/bacnet/bacdevobjpropref/CMakeLists.txt index a3149264..24e1831c 100644 --- a/test/bacnet/bacdevobjpropref/CMakeLists.txt +++ b/test/bacnet/bacdevobjpropref/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/basic/binding/address/CMakeLists.txt b/test/bacnet/basic/binding/address/CMakeLists.txt index 42c330e6..47467c71 100644 --- a/test/bacnet/basic/binding/address/CMakeLists.txt +++ b/test/bacnet/basic/binding/address/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/basic/object/acc/CMakeLists.txt b/test/bacnet/basic/object/acc/CMakeLists.txt index 01f0d962..18c4749c 100644 --- a/test/bacnet/basic/object/acc/CMakeLists.txt +++ b/test/bacnet/basic/object/acc/CMakeLists.txt @@ -46,6 +46,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/basic/sys/days.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/access_credential/CMakeLists.txt b/test/bacnet/basic/object/access_credential/CMakeLists.txt index d4c30050..06a55863 100644 --- a/test/bacnet/basic/object/access_credential/CMakeLists.txt +++ b/test/bacnet/basic/object/access_credential/CMakeLists.txt @@ -50,6 +50,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/access_door/CMakeLists.txt b/test/bacnet/basic/object/access_door/CMakeLists.txt index ca00f7b6..a4c3bfc1 100644 --- a/test/bacnet/basic/object/access_door/CMakeLists.txt +++ b/test/bacnet/basic/object/access_door/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/access_rights/CMakeLists.txt b/test/bacnet/basic/object/access_rights/CMakeLists.txt index 2f64f65b..923eba22 100644 --- a/test/bacnet/basic/object/access_rights/CMakeLists.txt +++ b/test/bacnet/basic/object/access_rights/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/access_user/CMakeLists.txt b/test/bacnet/basic/object/access_user/CMakeLists.txt index 32eb83c4..ce57a259 100644 --- a/test/bacnet/basic/object/access_user/CMakeLists.txt +++ b/test/bacnet/basic/object/access_user/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/access_zone/CMakeLists.txt b/test/bacnet/basic/object/access_zone/CMakeLists.txt index 6eade962..379ed2c2 100644 --- a/test/bacnet/basic/object/access_zone/CMakeLists.txt +++ b/test/bacnet/basic/object/access_zone/CMakeLists.txt @@ -51,6 +51,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/ai/CMakeLists.txt b/test/bacnet/basic/object/ai/CMakeLists.txt index eb87ab95..83815df6 100644 --- a/test/bacnet/basic/object/ai/CMakeLists.txt +++ b/test/bacnet/basic/object/ai/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/memcopy.c ${SRC_DIR}/bacnet/wp.c # Test and test library files diff --git a/test/bacnet/basic/object/ao/CMakeLists.txt b/test/bacnet/basic/object/ao/CMakeLists.txt index 20fe06ab..96604c96 100644 --- a/test/bacnet/basic/object/ao/CMakeLists.txt +++ b/test/bacnet/basic/object/ao/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/av/CMakeLists.txt b/test/bacnet/basic/object/av/CMakeLists.txt index 1b3e0c7b..1fbfd8e9 100644 --- a/test/bacnet/basic/object/av/CMakeLists.txt +++ b/test/bacnet/basic/object/av/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/memcopy.c ${SRC_DIR}/bacnet/wp.c # Test and test library files diff --git a/test/bacnet/basic/object/bi/CMakeLists.txt b/test/bacnet/basic/object/bi/CMakeLists.txt index 6c9d3313..73cd2faa 100644 --- a/test/bacnet/basic/object/bi/CMakeLists.txt +++ b/test/bacnet/basic/object/bi/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/memcopy.c ${SRC_DIR}/bacnet/wp.c # Test and test library files diff --git a/test/bacnet/basic/object/bo/CMakeLists.txt b/test/bacnet/basic/object/bo/CMakeLists.txt index b2e5e856..20e24ba3 100644 --- a/test/bacnet/basic/object/bo/CMakeLists.txt +++ b/test/bacnet/basic/object/bo/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/bv/CMakeLists.txt b/test/bacnet/basic/object/bv/CMakeLists.txt index d0f4afa7..579f62fe 100644 --- a/test/bacnet/basic/object/bv/CMakeLists.txt +++ b/test/bacnet/basic/object/bv/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/color_object/CMakeLists.txt b/test/bacnet/basic/object/color_object/CMakeLists.txt new file mode 100644 index 00000000..bc49cbd7 --- /dev/null +++ b/test/bacnet/basic/object/color_object/CMakeLists.txt @@ -0,0 +1,59 @@ +# 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/object/color_object.c + # Support files and stubs (pathname alphabetical) + ${SRC_DIR}/bacnet/bacapp.c + ${SRC_DIR}/bacnet/bacdcode.c + ${SRC_DIR}/bacnet/bacdevobjpropref.c + ${SRC_DIR}/bacnet/bacint.c + ${SRC_DIR}/bacnet/bacreal.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c + ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/cov.c + ${SRC_DIR}/bacnet/datetime.c + ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/indtext.c + ${SRC_DIR}/bacnet/hostnport.c + ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c + ${SRC_DIR}/bacnet/memcopy.c + ${SRC_DIR}/bacnet/wp.c + # Test and test library files + ./src/main.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) diff --git a/test/bacnet/basic/object/color_object/src/main.c b/test/bacnet/basic/object/color_object/src/main.c new file mode 100644 index 00000000..99e16753 --- /dev/null +++ b/test/bacnet/basic/object/color_object/src/main.c @@ -0,0 +1,88 @@ +/** + * @file + * @brief Unit test for object + * @author Steve Karg + * @date June 2022 + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include + + +bool Device_Valid_Object_Name( + BACNET_CHARACTER_STRING * object_name, + BACNET_OBJECT_TYPE *object_type, + uint32_t * object_instance) +{ + return true; +} + +void Device_Inc_Database_Revision( + void) +{ + +} + + +/** + * @addtogroup bacnet_tests + * @{ + */ + +/** + * @brief Test + */ +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; + const uint32_t instance = 123; + + Color_Init(); + Color_Create(instance); + + rpdata.application_data = &apdu[0]; + rpdata.application_data_len = sizeof(apdu); + rpdata.object_type = OBJECT_COLOR; + 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; + rpdata.array_index = BACNET_ARRAY_ALL; + len = Color_Read_Property(&rpdata); + zassert_true(len >= 0, NULL); + 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", + bactext_property_name(rpdata.object_property)); + } + zassert_equal(len, test_len, NULL); + } + required_property++; + } + + return; +} +/** + * @} + */ + + +void test_main(void) +{ + 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 new file mode 100644 index 00000000..3d0f97fe --- /dev/null +++ b/test/bacnet/basic/object/color_temperature/CMakeLists.txt @@ -0,0 +1,59 @@ +# 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/object/color_temperature.c + # Support files and stubs (pathname alphabetical) + ${SRC_DIR}/bacnet/bacapp.c + ${SRC_DIR}/bacnet/bacdcode.c + ${SRC_DIR}/bacnet/bacdevobjpropref.c + ${SRC_DIR}/bacnet/bacint.c + ${SRC_DIR}/bacnet/bacreal.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c + ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/cov.c + ${SRC_DIR}/bacnet/datetime.c + ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/indtext.c + ${SRC_DIR}/bacnet/hostnport.c + ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c + ${SRC_DIR}/bacnet/memcopy.c + ${SRC_DIR}/bacnet/wp.c + # Test and test library files + ./src/main.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) diff --git a/test/bacnet/basic/object/color_temperature/src/main.c b/test/bacnet/basic/object/color_temperature/src/main.c new file mode 100644 index 00000000..4b405a7a --- /dev/null +++ b/test/bacnet/basic/object/color_temperature/src/main.c @@ -0,0 +1,88 @@ +/** + * @file + * @brief Unit test for object + * @author Steve Karg + * @date June 2022 + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include + + +bool Device_Valid_Object_Name( + BACNET_CHARACTER_STRING * object_name, + BACNET_OBJECT_TYPE *object_type, + uint32_t * object_instance) +{ + return true; +} + +void Device_Inc_Database_Revision( + void) +{ + +} + + +/** + * @addtogroup bacnet_tests + * @{ + */ + +/** + * @brief Test + */ +static void testColorTemperature(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; + const uint32_t instance = 123; + + Color_Temperature_Init(); + Color_Temperature_Create(instance); + + rpdata.application_data = &apdu[0]; + rpdata.application_data_len = sizeof(apdu); + rpdata.object_type = OBJECT_COLOR_TEMPERATURE; + 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; + rpdata.array_index = BACNET_ARRAY_ALL; + len = Color_Read_Property(&rpdata); + zassert_true(len >= 0, NULL); + 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", + bactext_property_name(rpdata.object_property)); + } + zassert_equal(len, test_len, NULL); + } + required_property++; + } + + return; +} +/** + * @} + */ + + +void test_main(void) +{ + ztest_test_suite(color_temperature_tests, + ztest_unit_test(testColorTemperature) + ); + + ztest_run_test_suite(color_temperature_tests); +} diff --git a/test/bacnet/basic/object/command/CMakeLists.txt b/test/bacnet/basic/object/command/CMakeLists.txt index 2c513d51..98b771a1 100644 --- a/test/bacnet/basic/object/command/CMakeLists.txt +++ b/test/bacnet/basic/object/command/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/device/CMakeLists.txt b/test/bacnet/basic/object/device/CMakeLists.txt index 1d9face1..46105bbc 100644 --- a/test/bacnet/basic/object/device/CMakeLists.txt +++ b/test/bacnet/basic/object/device/CMakeLists.txt @@ -54,6 +54,8 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/basic/object/bo.c ${SRC_DIR}/bacnet/basic/object/bv.c ${SRC_DIR}/bacnet/basic/object/channel.c + ${SRC_DIR}/bacnet/basic/object/color_object.c + ${SRC_DIR}/bacnet/basic/object/color_temperature.c ${SRC_DIR}/bacnet/basic/object/command.c ${SRC_DIR}/bacnet/basic/object/csv.c ${SRC_DIR}/bacnet/basic/object/iv.c @@ -72,6 +74,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/basic/service/h_cov.c ${SRC_DIR}/bacnet/basic/service/h_wp.c ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/basic/sys/keylist.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/lc/CMakeLists.txt b/test/bacnet/basic/object/lc/CMakeLists.txt index 8c4df833..cdf259f3 100644 --- a/test/bacnet/basic/object/lc/CMakeLists.txt +++ b/test/bacnet/basic/object/lc/CMakeLists.txt @@ -49,6 +49,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c ./stubs.c # Test and test library files diff --git a/test/bacnet/basic/object/lo/CMakeLists.txt b/test/bacnet/basic/object/lo/CMakeLists.txt index db50c050..334e2acc 100644 --- a/test/bacnet/basic/object/lo/CMakeLists.txt +++ b/test/bacnet/basic/object/lo/CMakeLists.txt @@ -49,6 +49,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/lo/src/main.c b/test/bacnet/basic/object/lo/src/main.c index d51464c0..a5c2c7e2 100644 --- a/test/bacnet/basic/object/lo/src/main.c +++ b/test/bacnet/basic/object/lo/src/main.c @@ -9,6 +9,7 @@ */ #include +#include #include /** @@ -22,27 +23,45 @@ static void testLightingOutput(void) { uint8_t apdu[MAX_APDU] = { 0 }; - int len = 0; + int len = 0, test_len = 0; uint32_t len_value = 0; uint8_t tag_number = 0; BACNET_OBJECT_TYPE decoded_type = 0; uint32_t decoded_instance = 0; BACNET_READ_PROPERTY_DATA rpdata; + BACNET_APPLICATION_DATA_VALUE value = {0}; + const int *required_property = NULL; + const uint32_t instance = 1; Lighting_Output_Init(); rpdata.application_data = &apdu[0]; rpdata.application_data_len = sizeof(apdu); rpdata.object_type = OBJECT_LIGHTING_OUTPUT; - rpdata.object_instance = 1; - rpdata.object_property = PROP_OBJECT_IDENTIFIER; + rpdata.object_instance = instance; rpdata.array_index = BACNET_ARRAY_ALL; - len = Lighting_Output_Read_Property(&rpdata); - zassert_not_equal(len, 0, NULL); - len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); - zassert_equal(tag_number, BACNET_APPLICATION_TAG_OBJECT_ID, NULL); - len = decode_object_id(&apdu[len], &decoded_type, &decoded_instance); - zassert_equal(decoded_type, rpdata.object_type, NULL); - zassert_equal(decoded_instance, rpdata.object_instance, NULL); + + Lighting_Output_Property_Lists(&required_property, NULL, NULL); + while ((*required_property) >= 0) { + rpdata.object_property = *required_property; + len = Lighting_Output_Read_Property(&rpdata); + zassert_true(len >= 0, NULL); + 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", + bactext_property_name(rpdata.object_property)); + } + if ((rpdata.object_property == PROP_LIGHTING_COMMAND) || + (rpdata.object_property == PROP_PRIORITY_ARRAY) || + (rpdata.object_property == PROP_TRACKING_VALUE)) { + /* FIXME: how to decode the complex data? */ + test_len = len; + } + zassert_equal(len, test_len, NULL); + } + required_property++; + } return; } diff --git a/test/bacnet/basic/object/lsp/CMakeLists.txt b/test/bacnet/basic/object/lsp/CMakeLists.txt index 00e5c7fa..4fb65519 100644 --- a/test/bacnet/basic/object/lsp/CMakeLists.txt +++ b/test/bacnet/basic/object/lsp/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/ms-input/CMakeLists.txt b/test/bacnet/basic/object/ms-input/CMakeLists.txt index 4ee07d61..5a720a42 100644 --- a/test/bacnet/basic/object/ms-input/CMakeLists.txt +++ b/test/bacnet/basic/object/ms-input/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/mso/CMakeLists.txt b/test/bacnet/basic/object/mso/CMakeLists.txt index 15ea943f..d2a45fe0 100644 --- a/test/bacnet/basic/object/mso/CMakeLists.txt +++ b/test/bacnet/basic/object/mso/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/msv/CMakeLists.txt b/test/bacnet/basic/object/msv/CMakeLists.txt index 1c5f4705..8aa989ec 100644 --- a/test/bacnet/basic/object/msv/CMakeLists.txt +++ b/test/bacnet/basic/object/msv/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/memcopy.c ${SRC_DIR}/bacnet/wp.c # Test and test library files diff --git a/test/bacnet/basic/object/netport/CMakeLists.txt b/test/bacnet/basic/object/netport/CMakeLists.txt index 39184f03..ec4261ef 100644 --- a/test/bacnet/basic/object/netport/CMakeLists.txt +++ b/test/bacnet/basic/object/netport/CMakeLists.txt @@ -50,6 +50,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c ${SRC_DIR}/bacnet/proplist.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/osv/CMakeLists.txt b/test/bacnet/basic/object/osv/CMakeLists.txt index 7e20eae9..83fbc904 100644 --- a/test/bacnet/basic/object/osv/CMakeLists.txt +++ b/test/bacnet/basic/object/osv/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/piv/CMakeLists.txt b/test/bacnet/basic/object/piv/CMakeLists.txt index c70781c6..cd5597e8 100644 --- a/test/bacnet/basic/object/piv/CMakeLists.txt +++ b/test/bacnet/basic/object/piv/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/schedule/CMakeLists.txt b/test/bacnet/basic/object/schedule/CMakeLists.txt index 42fdb29c..4516655a 100644 --- a/test/bacnet/basic/object/schedule/CMakeLists.txt +++ b/test/bacnet/basic/object/schedule/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/sys/color_rgb/CMakeLists.txt b/test/bacnet/basic/sys/color_rgb/CMakeLists.txt new file mode 100644 index 00000000..0ffe9160 --- /dev/null +++ b/test/bacnet/basic/sys/color_rgb/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/color_rgb.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/color_rgb/src/main.c b/test/bacnet/basic/sys/color_rgb/src/main.c new file mode 100644 index 00000000..a69bf5a6 --- /dev/null +++ b/test/bacnet/basic/sys/color_rgb/src/main.c @@ -0,0 +1,104 @@ +/* @file + * @brief test BACnet integer encode/decode APIs + * @date June 2022 + * @brief tests sRGB to and from from CIE xy and brightness API + * + * @section LICENSE + * Copyright (c) 2022 Steve Karg + * + * SPDX-License-Identifier: MIT + */ +#include +#include + +/** + * @addtogroup bacnet_tests + * @{ + */ + +/** + * 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); +} + +/** + * Unit Test for sRGB to CIE xy + */ +static void test_color_rgb_xy(void) +{ + 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); +} + +/** +* Unit Test for sRGB to CIE xy +*/ +static void test_color_rgb_ascii(void) +{ + unsigned count = color_rgb_count(); + zassert_true(count > 0, NULL); + const char *name, *test_name; + uint8_t red, green, blue; + uint8_t test_red, test_green, test_blue; + unsigned test_index; + float x_coordinate; + float y_coordinate; + float brightness; + + 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); + zassert_equal(i, test_index, NULL); + zassert_equal(red, test_red, NULL); + zassert_equal(green, test_green, NULL); + zassert_equal(blue, test_blue, NULL); + test_name = color_rgb_to_ascii(red, green, blue); + zassert_not_null(test_name, NULL); + } +} + +/** + * @} + */ + + +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_run_test_suite(color_rgb_tests); +} diff --git a/test/bacnet/cov/CMakeLists.txt b/test/bacnet/cov/CMakeLists.txt index 8ff10e79..4da8af1d 100644 --- a/test/bacnet/cov/CMakeLists.txt +++ b/test/bacnet/cov/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/memcopy.c # Test and test library files ./src/main.c diff --git a/test/bacnet/datalink/bvlc/src/main.c b/test/bacnet/datalink/bvlc/src/main.c index 915c0945..8c88bfac 100644 --- a/test/bacnet/datalink/bvlc/src/main.c +++ b/test/bacnet/datalink/bvlc/src/main.c @@ -10,6 +10,7 @@ #include /* For calloc() */ #include +#include #include /** diff --git a/test/bacnet/lighting/src/main.c b/test/bacnet/lighting/src/main.c index 867ba836..dfbdcab4 100644 --- a/test/bacnet/lighting/src/main.c +++ b/test/bacnet/lighting/src/main.c @@ -70,11 +70,83 @@ static void testBACnetLightingCommandAll(void) * @} */ +/** + * @brief Test + */ +static void testBACnetColorCommand(BACNET_COLOR_COMMAND *data) +{ + bool status = false; + BACNET_COLOR_COMMAND test_data = { 0 }; + int len = 0, apdu_len = 0; + uint8_t apdu[MAX_APDU] = { 0 }; + BACNET_ERROR_CODE error_code; + + status = color_command_copy(&test_data, NULL); + zassert_false(status, NULL); + status = color_command_copy(NULL, data); + zassert_false(status, NULL); + status = color_command_copy(&test_data, data); + zassert_true(status, NULL); + 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); + status = color_command_same(&test_data, data); +} + +static void testBACnetColorCommandAll(void) +{ + BACNET_COLOR_COMMAND data = { 0 }; + + 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); +} + +static void testBACnetXYColor(void) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + BACNET_XY_COLOR value = { 0 }; + BACNET_XY_COLOR test_value = { 0 }; + int len = 0, test_len = 0, null_len = 0; + uint8_t tag_number = 1; + bool status = false; + + value.x_coordinate = 1.0; + value.y_coordinate = 1.0; + + null_len = xy_color_encode(NULL, &value); + len = xy_color_encode(apdu, &value); + zassert_equal(null_len, len, NULL); + test_len = xy_color_decode(apdu, sizeof(apdu), &test_value); + zassert_equal(test_len, len, NULL); + status = xy_color_same(&value, &test_value); + zassert_true(status, NULL); + + 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); + zassert_equal(test_len, len, NULL); + status = xy_color_same(&value, &test_value); + zassert_true(status, NULL); +} void test_main(void) { ztest_test_suite(lighting_tests, - ztest_unit_test(testBACnetLightingCommandAll) + ztest_unit_test(testBACnetLightingCommandAll), + ztest_unit_test(testBACnetColorCommandAll), + ztest_unit_test(testBACnetXYColor) ); ztest_run_test_suite(lighting_tests); diff --git a/test/bacnet/lso/CMakeLists.txt b/test/bacnet/lso/CMakeLists.txt index ce8d6d18..00143bab 100644 --- a/test/bacnet/lso/CMakeLists.txt +++ b/test/bacnet/lso/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/memcopy.c # Test and test library files ./src/main.c diff --git a/test/bacnet/ptransfer/CMakeLists.txt b/test/bacnet/ptransfer/CMakeLists.txt index 0300ed6c..cfd822c0 100644 --- a/test/bacnet/ptransfer/CMakeLists.txt +++ b/test/bacnet/ptransfer/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/rpm/CMakeLists.txt b/test/bacnet/rpm/CMakeLists.txt index 0a52c488..ae658857 100644 --- a/test/bacnet/rpm/CMakeLists.txt +++ b/test/bacnet/rpm/CMakeLists.txt @@ -49,6 +49,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/memcopy.c # Test and test library files ./src/main.c diff --git a/test/bacnet/timesync/CMakeLists.txt b/test/bacnet/timesync/CMakeLists.txt index 2f46f68b..9bed9c6c 100644 --- a/test/bacnet/timesync/CMakeLists.txt +++ b/test/bacnet/timesync/CMakeLists.txt @@ -49,6 +49,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/wp/CMakeLists.txt b/test/bacnet/wp/CMakeLists.txt index cb0fe33d..8ac9ebe3 100644 --- a/test/bacnet/wp/CMakeLists.txt +++ b/test/bacnet/wp/CMakeLists.txt @@ -47,6 +47,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/indtext.c # Test and test library files ./src/main.c