diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..70bc3af --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# vi +*.swp + +# MacOS Finder +.DS_Store +._* + +# build +build + +# IDE +*idea + +*.so +*.so.1 +CMakeCache.txt +CMakeFiles diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..39763b8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,96 @@ +cmake_minimum_required(VERSION 2.4.0) + +# Set the plugin name to build +project(Delta) + +# Supported options: +# -DFLEDGE_INCLUDE +# -DFLEDGE_LIB +# -DFLEDGE_SRC +# -DFLEDGE_INSTALL +# +# If no -D options are given and FLEDGE_ROOT environment variable is set +# then Fledge libraries and header files are pulled from FLEDGE_ROOT path. +# The NOTIFICATION_SERVICE_INCLUDE_DIRS environment variable must point +# to Fledge Notification server include files, example +# export NOTIFICATION_SERVICE_INCLUDE_DIRS=/home/ubuntu/source/fledge-service-notification/C/services/common/include + +set(CMAKE_CXX_FLAGS "-std=c++11 -O3") + +# Set plugin type (south, north, filter, notificationDelivery, notificationRule) +set(PLUGIN_TYPE "notificationRule") + +set_source_files_properties(version.h PROPERTIES GENERATED TRUE) +add_custom_command( + OUTPUT version.h + DEPENDS ${CMAKE_SOURCE_DIR}/VERSION + COMMAND ${CMAKE_SOURCE_DIR}/mkversion ${CMAKE_SOURCE_DIR} + COMMENT "Generating version header" + VERBATIM +) +include_directories(${CMAKE_BINARY_DIR}) + +# Add here all needed Fledge libraries as list +set(NEEDED_FLEDGE_LIBS common-lib plugins-common-lib) + +# Find source files +file(GLOB SOURCES *.cpp) + +# Find Fledge includes and libs, by including FindFledge.cmak file +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}) +find_package(Fledge) +# If errors: make clean and remove Makefile +if (NOT FLEDGE_FOUND) + if (EXISTS "${CMAKE_BINARY_DIR}/Makefile") + execute_process(COMMAND make clean WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + file(REMOVE "${CMAKE_BINARY_DIR}/Makefile") + endif() + # Stop the build process + message(FATAL_ERROR "Fledge plugin '${PROJECT_NAME}' build error.") +endif() +# On success, FLEDGE_INCLUDE_DIRS and FLEDGE_LIB_DIRS variables are set + +# Add ./include +include_directories(include) + +# Add Fledge include dir(s) +include_directories(${FLEDGE_INCLUDE_DIRS}) + +# Add Notification server includes from NOTIFICATION_SERVICE_INCLUDE_DIRS env variable +if (NOT DEFINED ENV{NOTIFICATION_SERVICE_INCLUDE_DIRS}) + # Stop the build process + message(FATAL_ERROR "Fledge plugin '${PROJECT_NAME}' build error. " + "Notification server includes dir not set. Use NOTIFICATION_SERVICE_INCLUDE_DIRS env variable") +else() + message(STATUS "Notification server includes dir set to $ENV{NOTIFICATION_SERVICE_INCLUDE_DIRS}") +endif() + +include_directories($ENV{NOTIFICATION_SERVICE_INCLUDE_DIRS}) + +# Add other include paths this plugin needs +if (FLEDGE_SRC) + message(STATUS "Using third-party includes " ${FLEDGE_SRC}/C/thirdparty/Simple-Web-Server) + include_directories(${FLEDGE_SRC}/C/thirdparty/Simple-Web-Server) +else() + include_directories(${FLEDGE_INCLUDE_DIRS}/Simple-Web-Server) +endif() + +# Add Fledge lib path +link_directories(${FLEDGE_LIB_DIRS}) + +# Create shared library +add_library(${PROJECT_NAME} SHARED ${SOURCES} version.h) + +# Add Fledge library names +target_link_libraries(${PROJECT_NAME} ${NEEDED_FLEDGE_LIBS}) +# Add additional libraries + +# Set the build version +set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION 1) + +set(FLEDGE_INSTALL "" CACHE INTERNAL "") +# Install library +if (FLEDGE_INSTALL) + message(STATUS "Installing ${PROJECT_NAME} in ${FLEDGE_INSTALL}/plugins/${PLUGIN_TYPE}/${PROJECT_NAME}") + install(TARGETS ${PROJECT_NAME} DESTINATION ${FLEDGE_INSTALL}/plugins/${PLUGIN_TYPE}/${PROJECT_NAME}) +endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9f6dd28 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributing to Fledge + +The Fledge project welcomes contributions of any kind, please see [CONTRIBUTING.md](https://github.com/fledge-iot/fledge/blob/develop/CONTRIBUTING.md) in the main Fledge repository for details and guidance. diff --git a/Description b/Description new file mode 100644 index 0000000..7c773fe --- /dev/null +++ b/Description @@ -0,0 +1 @@ +Delta notification rule plugin package for Fledge. Sends notifications when values differ from the delta by more than a prescribed percentage. diff --git a/FindFledge.cmake b/FindFledge.cmake new file mode 100644 index 0000000..85659a8 --- /dev/null +++ b/FindFledge.cmake @@ -0,0 +1,142 @@ +# This CMake file locates the Fledge header files and libraries +# +# The following variables are set: +# FLEDGE_INCLUDE_DIRS - Path(s) to Fledge headers files found +# FLEDGE_LIB_DIRS - Path to Fledge shared libraries +# FLEDGE_SUCCESS - Set on succes +# +# In case of error use SEND_ERROR and return() +# + +# Set defaults paths of installed Fledge SDK package +set(FLEDGE_DEFAULT_INCLUDE_DIR "/usr/include/fledge" CACHE INTERNAL "") +set(FLEDGE_DEFAULT_LIB_DIR "/usr/lib/fledge" CACHE INTERNAL "") + +# CMakeLists.txt options +set(FLEDGE_SRC "" CACHE INTERNAL "") +set(FLEDGE_INCLUDE "" CACHE INTERNAL "") +set(FLEDGE_LIB "" CACHE INTERNAL "") + +# Return variables +set(FLEDGE_INCLUDE_DIRS "" CACHE INTERNAL "") +set(FLEDGE_LIB_DIRS "" CACHE INTERNAL "") +set(FLEDGE_FOUND "" CACHE INTERNAL "") + +# No options set +# If FLEDGE_ROOT env var is set, use it +if (NOT FLEDGE_SRC AND NOT FLEDGE_INCLUDE AND NOT FLEDGE_LIB) + if (DEFINED ENV{FLEDGE_ROOT}) + message(STATUS "No options set.\n" + " +Using found FLEDGE_ROOT $ENV{FLEDGE_ROOT}") + set(FLEDGE_SRC $ENV{FLEDGE_ROOT}) + endif() +endif() + +# -DFLEDGE_SRC=/some_path or FLEDGE_ROOT path +# Set return variable FLEDGE_INCLUDE_DIRS +if (FLEDGE_SRC) + unset(_INCLUDE_LIST CACHE) + file(GLOB_RECURSE _INCLUDE_COMMON "${FLEDGE_SRC}/C/common/*.h") + file(GLOB_RECURSE _INCLUDE_SERVICES "${FLEDGE_SRC}/C/services/common/*.h") + file(GLOB_RECURSE _INCLUDE_PLUGINS_FILTER_COMMON "${FLEDGE_SRC}/C/plugins/filter/common/*.h") + list(APPEND _INCLUDE_LIST ${_INCLUDE_COMMON} ${_INCLUDE_SERVICES} ${_INCLUDE_PLUGINS_FILTER_COMMON}) + foreach(_ITEM ${_INCLUDE_LIST}) + get_filename_component(_ITEM_PATH ${_ITEM} DIRECTORY) + list(APPEND FLEDGE_INCLUDE_DIRS ${_ITEM_PATH}) + endforeach() + # Add rapidjson header files + list(APPEND FLEDGE_INCLUDE_DIRS "${FLEDGE_SRC}/C/thirdparty/rapidjson/include") + unset(INCLUDE_LIST CACHE) + + list(REMOVE_DUPLICATES FLEDGE_INCLUDE_DIRS) + + string (REPLACE ";" "\n +" DISPLAY_PATHS "${FLEDGE_INCLUDE_DIRS}") + if (NOT DEFINED ENV{FLEDGE_ROOT}) + message(STATUS "Using -DFLEDGE_SRC option for includes\n +" "${DISPLAY_PATHS}") + else() + message(STATUS "Using FLEDGE_ROOT for includes\n +" "${DISPLAY_PATHS}") + endif() + + if (NOT FLEDGE_INCLUDE_DIRS) + message(SEND_ERROR "Needed Fledge header files not found in path ${FLEDGE_SRC}/C") + return() + endif() +else() + # -DFLEDGE_INCLUDE=/some_path + if (NOT FLEDGE_INCLUDE) + set(FLEDGE_INCLUDE ${FLEDGE_DEFAULT_INCLUDE_DIR}) + message(STATUS "Using Fledge dev package includes " ${FLEDGE_INCLUDE}) + else() + message(STATUS "Using -DFLEDGE_INCLUDE option " ${FLEDGE_INCLUDE}) + endif() + # Remove current value from cache + unset(_FIND_INCLUDES CACHE) + # Get up to date var from find_path + find_path(_FIND_INCLUDES NAMES plugin_api.h PATHS ${FLEDGE_INCLUDE}) + if (_FIND_INCLUDES) + list(APPEND FLEDGE_INCLUDE_DIRS ${_FIND_INCLUDES}) + endif() + # Remove current value from cache + unset(_FIND_INCLUDES CACHE) + + if (NOT FLEDGE_INCLUDE_DIRS) + message(SEND_ERROR "Needed Fledge header files not found in path ${FLEDGE_INCLUDE}") + return() + endif() +endif() + +# +# Fledge Libraries +# +# Check -DFLEDGE_LIB=/some path is valid +# or use FLEDGE_SRC/cmake_build/C/lib +# FLEDGE_SRC might have been set to FLEDGE_ROOT above +# +if (FLEDGE_SRC) + # Set return variable FLEDGE_LIB_DIRS + set(FLEDGE_LIB "${FLEDGE_SRC}/cmake_build/C/lib") + + if (NOT DEFINED ENV{FLEDGE_ROOT}) + message(STATUS "Using -DFLEDGE_SRC option for libs \n +" "${FLEDGE_SRC}/cmake_build/C/lib") + else() + message(STATUS "Using FLEDGE_ROOT for libs \n +" "${FLEDGE_SRC}/cmake_build/C/lib") + endif() + + if (NOT EXISTS "${FLEDGE_SRC}/cmake_build") + message(SEND_ERROR "Fledge has not been built yet in ${FLEDGE_SRC} Compile it first.") + return() + endif() + + # Set return variable FLEDGE_LIB_DIRS + set(FLEDGE_LIB_DIRS "${FLEDGE_SRC}/cmake_build/C/lib") +else() + if (NOT FLEDGE_LIB) + set(FLEDGE_LIB ${FLEDGE_DEFAULT_LIB_DIR}) + message(STATUS "Using Fledge dev package libs " ${FLEDGE_LIB}) + else() + message(STATUS "Using -DFLEDGE_LIB option " ${FLEDGE_LIB}) + endif() + # Set return variable FLEDGE_LIB_DIRS + set(FLEDGE_LIB_DIRS ${FLEDGE_LIB}) +endif() + +# Check NEEDED_FLEDGE_LIBS in libraries in FLEDGE_LIB_DIRS +# NEEDED_FLEDGE_LIBS variables comes from CMakeLists.txt +foreach(_LIB ${NEEDED_FLEDGE_LIBS}) + # Remove current value from cache + unset(_FOUND_LIB CACHE) + # Get up to date var from find_library + find_library(_FOUND_LIB NAME ${_LIB} PATHS ${FLEDGE_LIB_DIRS}) + if (_FOUND_LIB) + # Extract path form founf library file + get_filename_component(_DIR_LIB ${_FOUND_LIB} DIRECTORY) + else() + message(SEND_ERROR "Needed Fledge library ${_LIB} not found in ${FLEDGE_LIB_DIRS}") + return() + endif() + # Remove current value from cache + unset(_FOUND_LIB CACHE) +endforeach() + +# Set return variable FLEDGE_FOUND +set(FLEDGE_FOUND "true") diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dacfc3f --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Dianomic Systems Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..31da6ac --- /dev/null +++ b/Makefile @@ -0,0 +1,234 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 3.16 + +# Default target executed when no arguments are given to make. +default_target: all + +.PHONY : default_target + +# Allow only one "make -f Makefile2" at a time, but pass parallelism. +.NOTPARALLEL: + + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + + +# Remove some rules from gmake that .SUFFIXES does not remove. +SUFFIXES = + +.SUFFIXES: .hpux_make_needs_suffix_list + + +# Suppress display of executed commands. +$(VERBOSE).SILENT: + + +# A target that is always out of date. +cmake_force: + +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /usr/bin/cmake + +# The command to remove a file. +RM = /usr/bin/cmake -E remove -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /fledge-rule-delta + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /fledge-rule-delta + +#============================================================================= +# Targets provided globally by CMake. + +# Special rule for the target install/local +install/local: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..." + /usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local + +# Special rule for the target install/local +install/local/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..." + /usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local/fast + +# Special rule for the target install +install: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." + /usr/bin/cmake -P cmake_install.cmake +.PHONY : install + +# Special rule for the target install +install/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." + /usr/bin/cmake -P cmake_install.cmake +.PHONY : install/fast + +# Special rule for the target list_install_components +list_install_components: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Available install components are: \"Unspecified\"" +.PHONY : list_install_components + +# Special rule for the target list_install_components +list_install_components/fast: list_install_components + +.PHONY : list_install_components/fast + +# Special rule for the target rebuild_cache +rebuild_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." + /usr/bin/cmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : rebuild_cache + +# Special rule for the target rebuild_cache +rebuild_cache/fast: rebuild_cache + +.PHONY : rebuild_cache/fast + +# Special rule for the target edit_cache +edit_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No interactive CMake dialog available..." + /usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available. +.PHONY : edit_cache + +# Special rule for the target edit_cache +edit_cache/fast: edit_cache + +.PHONY : edit_cache/fast + +# Special rule for the target install/strip +install/strip: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..." + /usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip + +# Special rule for the target install/strip +install/strip/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..." + /usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip/fast + +# The main all target +all: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /fledge-rule-delta/CMakeFiles /fledge-rule-delta/CMakeFiles/progress.marks + $(MAKE) -f CMakeFiles/Makefile2 all + $(CMAKE_COMMAND) -E cmake_progress_start /fledge-rule-delta/CMakeFiles 0 +.PHONY : all + +# The main clean target +clean: + $(MAKE) -f CMakeFiles/Makefile2 clean +.PHONY : clean + +# The main clean target +clean/fast: clean + +.PHONY : clean/fast + +# Prepare targets for installation. +preinstall: all + $(MAKE) -f CMakeFiles/Makefile2 preinstall +.PHONY : preinstall + +# Prepare targets for installation. +preinstall/fast: + $(MAKE) -f CMakeFiles/Makefile2 preinstall +.PHONY : preinstall/fast + +# clear depends +depend: + $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 +.PHONY : depend + +#============================================================================= +# Target rules for targets named Delta + +# Build rule for target. +Delta: cmake_check_build_system + $(MAKE) -f CMakeFiles/Makefile2 Delta +.PHONY : Delta + +# fast build rule for target. +Delta/fast: + $(MAKE) -f CMakeFiles/Delta.dir/build.make CMakeFiles/Delta.dir/build +.PHONY : Delta/fast + +# target to build an object file +delta.o: + $(MAKE) -f CMakeFiles/Delta.dir/build.make CMakeFiles/Delta.dir/delta.o +.PHONY : delta.o + +# target to preprocess a source file +delta.i: + $(MAKE) -f CMakeFiles/Delta.dir/build.make CMakeFiles/Delta.dir/delta.i +.PHONY : delta.i + +# target to generate assembly for a file +delta.s: + $(MAKE) -f CMakeFiles/Delta.dir/build.make CMakeFiles/Delta.dir/delta.s +.PHONY : delta.s + +# target to build an object file +plugin.o: + $(MAKE) -f CMakeFiles/Delta.dir/build.make CMakeFiles/Delta.dir/plugin.o +.PHONY : plugin.o + +# target to preprocess a source file +plugin.i: + $(MAKE) -f CMakeFiles/Delta.dir/build.make CMakeFiles/Delta.dir/plugin.i +.PHONY : plugin.i + +# target to generate assembly for a file +plugin.s: + $(MAKE) -f CMakeFiles/Delta.dir/build.make CMakeFiles/Delta.dir/plugin.s +.PHONY : plugin.s + +# Help Target +help: + @echo "The following are some of the valid targets for this Makefile:" + @echo "... all (the default if no target is provided)" + @echo "... clean" + @echo "... depend" + @echo "... install/local" + @echo "... install" + @echo "... list_install_components" + @echo "... rebuild_cache" + @echo "... edit_cache" + @echo "... install/strip" + @echo "... Delta" + @echo "... delta.o" + @echo "... delta.i" + @echo "... delta.s" + @echo "... plugin.o" + @echo "... plugin.i" + @echo "... plugin.s" +.PHONY : help + + + +#============================================================================= +# Special targets to cleanup operation of make. + +# Special rule to run CMake to check the build system integrity. +# No rule that depends on this can have commands that come from listfiles +# because they might be regenerated. +cmake_check_build_system: + $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 +.PHONY : cmake_check_build_system + diff --git a/Package b/Package new file mode 100644 index 0000000..12cf080 --- /dev/null +++ b/Package @@ -0,0 +1,26 @@ +# A set of variables that define how we package this repository +# +plugin_name=delta +plugin_type=rule +plugin_install_dirname=Delta + +# Now build up the runtime requirements list. This has 3 components +# 1. Generic packages we depend on in all architectures and package managers +# 2. Architecture specific packages we depend on +# 3. Package manager specific packages we depend on +requirements="fledge,fledge-service-notification" + +case "$arch" in + x86_64) + ;; + armv7l) + ;; + aarch64) + ;; +esac +case "$package_manager" in + deb) + ;; + rpm) + ;; +esac diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..429d8da --- /dev/null +++ b/README.rst @@ -0,0 +1,103 @@ +======================================== +Fledge Delta notification rule plugin +======================================== + +A notification plugin that triggers if the value of a datapoint is +more than a prescribed percentage different from the currently observed +delta for that data point. + +The plugin only monitors a single asset, but will moitor all data points +within that asset. It will trigger if any of the data points within the +asset differ by more than the configured percentage, an delta is maintained +for each data point seperately. + +A configuration option also allows for control of notification triggering +based on the value being above, below are either side of the delta +value. + +The delta calculated may be either a simple moving delta or an +exponential moving delta. If an exponential moving delta is chosen +then a second configuration parameter allows the setting of the factor +used to calculate that delta. + +Exponential moving deltas give more weight to the recent values compare +to historical values. The smaller the EMA factor the more weight recent +values carry. A value of 1 for factor will only consider at the most recent +value. + +The Delta rule is not applicable to all data, only simple numeric values +are considered and those values should not deviate with an delta of +0 or close to 0 if good results are required. Data points that deviate +wildly are also not suitable for this plugin. + +Build +----- +To build Fledge "Delta" notification rule C++ plugin, +in addition to Fledge source code, the Notification server C++ +header files are required (no .cpp files or libraries needed so far) + +The path with Notification server C++ header files cab be specified only via +NOTIFICATION_SERVICE_INCLUDE_DIRS environment variable. + +Example: + +.. code-block:: console + + $ export NOTIFICATION_SERVICE_INCLUDE_DIRS=/home/ubuntu/source/fledge-service-notification/C/services/common/include + +.. code-block:: console + + $ mkdir build + $ cd build + $ cmake .. + $ make + +- By default the Fledge develop package header files and libraries + are expected to be located in /usr/include/fledge and /usr/lib/fledge +- If **FLEDGE_ROOT** env var is set and no -D options are set, + the header files and libraries paths are pulled from the ones under the + FLEDGE_ROOT directory. + Please note that you must first run 'make' in the FLEDGE_ROOT directory. + +You may also pass one or more of the following options to cmake to override +this default behaviour: + +- **FLEDGE_SRC** sets the path of a Fledge source tree +- **FLEDGE_INCLUDE** sets the path to Fledge header files +- **FLEDGE_LIB sets** the path to Fledge libraries +- **FLEDGE_INSTALL** sets the installation path of Random plugin + +NOTE: + - The **FLEDGE_INCLUDE** option should point to a location where all the Fledge + header files have been installed in a single directory. + - The **FLEDGE_LIB** option should point to a location where all the Fledge + libraries have been installed in a single directory. + - 'make install' target is defined only when **FLEDGE_INSTALL** is set + +Examples: + +- no options + + $ cmake .. + +- no options and FLEDGE_ROOT set + + $ export FLEDGE_ROOT=/some_fledge_setup + + $ cmake .. + +- set FLEDGE_SRC + + $ cmake -DFLEDGE_SRC=/home/source/develop/Fledge .. + +- set FLEDGE_INCLUDE + + $ cmake -DFLEDGE_INCLUDE=/dev-package/include .. +- set FLEDGE_LIB + + $ cmake -DFLEDGE_LIB=/home/dev/package/lib .. +- set FLEDGE_INSTALL + + $ cmake -DFLEDGE_INSTALL=/home/source/develop/Fledge .. + + $ cmake -DFLEDGE_INSTALL=/usr/local/fledge .. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..9ab8337 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.9.1 diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..f586d6d --- /dev/null +++ b/build.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Pass any cmake options this way: + +# ./build.sh -DFLEDGE_INSTALL=/some_path/Fledge +# + +os_name=`(grep -o '^NAME=.*' /etc/os-release | cut -f2 -d\" | sed 's/"//g')` +os_version=`(grep -o '^VERSION_ID=.*' /etc/os-release | cut -f2 -d\" | sed 's/"//g')` + +if [[ ( $os_name == *"Red Hat"* || $os_name == *"CentOS"* ) && $os_version == *"7"* ]]; then + if [ -f build_rhel.sh ]; then + echo "Custom build for platform is ${os_name}, Version: ${os_version}" + ./build_rhel.sh $@ + fi +elif apt --version 2>/dev/null; then + if [ -f build_deb.sh ]; then + echo "Custom build for platform is ${os_name}, Version: ${os_version}" + ./build_deb.sh $@ + fi +else + echo "Custom build script not yet implemented for platform ${os_name}, Version: ${os_version}" +fi diff --git a/build_deb.sh b/build_deb.sh new file mode 100755 index 0000000..aa6f33d --- /dev/null +++ b/build_deb.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Pass any cmake options this way: + +# ./build_deb.sh -DFLEDGE_INSTALL=/some_path/Fledge + +mkdir build +cd build/ +cmake $@ .. +make diff --git a/build_rhel.sh b/build_rhel.sh new file mode 100755 index 0000000..f5532b9 --- /dev/null +++ b/build_rhel.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Pass any cmake options this way: + +# ./build_rhel.sh -DFLEDGE_INSTALL=/some_path/Fledge + +source scl_source enable devtoolset-7 +export CC=$(scl enable devtoolset-7 "command -v gcc") +export CXX=$(scl enable devtoolset-7 "command -v g++") + +mkdir build +cd build/ +cmake $@ .. +make diff --git a/cmake_install.cmake b/cmake_install.cmake new file mode 100644 index 0000000..5a3841d --- /dev/null +++ b/cmake_install.cmake @@ -0,0 +1,105 @@ +# Install script for directory: /fledge-rule-delta + +# Set the install prefix +if(NOT DEFINED CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX "/usr/local") +endif() +string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") + +# Set the install configuration name. +if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) + if(BUILD_TYPE) + string(REGEX REPLACE "^[^A-Za-z0-9_]+" "" + CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") + else() + set(CMAKE_INSTALL_CONFIG_NAME "") + endif() + message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") +endif() + +# Set the component getting installed. +if(NOT CMAKE_INSTALL_COMPONENT) + if(COMPONENT) + message(STATUS "Install component: \"${COMPONENT}\"") + set(CMAKE_INSTALL_COMPONENT "${COMPONENT}") + else() + set(CMAKE_INSTALL_COMPONENT) + endif() +endif() + +# Install shared libraries without execute permission? +if(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE) + set(CMAKE_INSTALL_SO_NO_EXE "1") +endif() + +# Is this installation the result of a crosscompile? +if(NOT DEFINED CMAKE_CROSSCOMPILING) + set(CMAKE_CROSSCOMPILING "FALSE") +endif() + +if("x${CMAKE_INSTALL_COMPONENT}x" STREQUAL "xUnspecifiedx" OR NOT CMAKE_INSTALL_COMPONENT) + if(EXISTS "$ENV{DESTDIR}/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so.1" AND + NOT IS_SYMLINK "$ENV{DESTDIR}/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so.1") + file(RPATH_CHECK + FILE "$ENV{DESTDIR}/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so.1" + RPATH "") + endif() + list(APPEND CMAKE_ABSOLUTE_DESTINATION_FILES + "/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so.1") + if(CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION) + message(WARNING "ABSOLUTE path INSTALL DESTINATION : ${CMAKE_ABSOLUTE_DESTINATION_FILES}") + endif() + if(CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION) + message(FATAL_ERROR "ABSOLUTE path INSTALL DESTINATION forbidden (by caller): ${CMAKE_ABSOLUTE_DESTINATION_FILES}") + endif() +file(INSTALL DESTINATION "/usr/local/fledge/plugins/notificationRule/Delta" TYPE SHARED_LIBRARY FILES "/fledge-rule-delta/libDelta.so.1") + if(EXISTS "$ENV{DESTDIR}/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so.1" AND + NOT IS_SYMLINK "$ENV{DESTDIR}/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so.1") + file(RPATH_CHANGE + FILE "$ENV{DESTDIR}/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so.1" + OLD_RPATH "/fledge/cmake_build/C/lib:" + NEW_RPATH "") + if(CMAKE_INSTALL_DO_STRIP) + execute_process(COMMAND "/usr/bin/strip" "$ENV{DESTDIR}/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so.1") + endif() + endif() +endif() + +if("x${CMAKE_INSTALL_COMPONENT}x" STREQUAL "xUnspecifiedx" OR NOT CMAKE_INSTALL_COMPONENT) + if(EXISTS "$ENV{DESTDIR}/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so" AND + NOT IS_SYMLINK "$ENV{DESTDIR}/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so") + file(RPATH_CHECK + FILE "$ENV{DESTDIR}/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so" + RPATH "") + endif() + list(APPEND CMAKE_ABSOLUTE_DESTINATION_FILES + "/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so") + if(CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION) + message(WARNING "ABSOLUTE path INSTALL DESTINATION : ${CMAKE_ABSOLUTE_DESTINATION_FILES}") + endif() + if(CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION) + message(FATAL_ERROR "ABSOLUTE path INSTALL DESTINATION forbidden (by caller): ${CMAKE_ABSOLUTE_DESTINATION_FILES}") + endif() +file(INSTALL DESTINATION "/usr/local/fledge/plugins/notificationRule/Delta" TYPE SHARED_LIBRARY FILES "/fledge-rule-delta/libDelta.so") + if(EXISTS "$ENV{DESTDIR}/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so" AND + NOT IS_SYMLINK "$ENV{DESTDIR}/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so") + file(RPATH_CHANGE + FILE "$ENV{DESTDIR}/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so" + OLD_RPATH "/fledge/cmake_build/C/lib:" + NEW_RPATH "") + if(CMAKE_INSTALL_DO_STRIP) + execute_process(COMMAND "/usr/bin/strip" "$ENV{DESTDIR}/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so") + endif() + endif() +endif() + +if(CMAKE_INSTALL_COMPONENT) + set(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INSTALL_COMPONENT}.txt") +else() + set(CMAKE_INSTALL_MANIFEST "install_manifest.txt") +endif() + +string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT + "${CMAKE_INSTALL_MANIFEST_FILES}") +file(WRITE "/fledge-rule-delta/${CMAKE_INSTALL_MANIFEST}" + "${CMAKE_INSTALL_MANIFEST_CONTENT}") diff --git a/delta.cpp b/delta.cpp new file mode 100644 index 0000000..ebbead8 --- /dev/null +++ b/delta.cpp @@ -0,0 +1,301 @@ +/** + * Fledge Delta notification rule plugin + * + * Copyright (c) 2021 ACDP + * + * Released under the Apache 2.0 Licence + * + * Author: Sebastian Kropatschek, Thorsten Steuer + */ + +/*********************************************************************** +* DISCLAIMER: +* +* All sample code is provided by ACDP for illustrative purposes only. +* These examples have not been thoroughly tested under all conditions. +* ACDP provides no guarantee nor implies any reliability, +* serviceability, or function of these programs. +* ALL PROGRAMS CONTAINED HEREIN ARE PROVIDED TO YOU "AS IS" +* WITHOUT ANY WARRANTIES OF ANY KIND. ALL WARRANTIES INCLUDING +* THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY +* AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY DISCLAIMED. +************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "version.h" +#include "delta.h" +//#include +#include + +using namespace std; + +/** + * Delta rule constructor + * + * Call parent class BuiltinRule constructor + */ +DeltaRule::DeltaRule() : BuiltinRule() +{ +} + +/** + * Delta destructor + */ +DeltaRule::~DeltaRule() +{ + for(std::map::iterator itr = m_lastvalue.begin(); itr != m_lastvalue.end(); itr++) + { + delete (itr->second); + itr->second = NULL; + } + m_lastvalue.clear(); +} + +/** + * Configure the rule plugin + * + * @param config The configuration object to process + */ +void DeltaRule::configure(const ConfigCategory& config) +{ + // Remove current triggers + // Configuration change is protected by a lock + lockConfig(); + if (hasTriggers()) + { + removeTriggers(); + } + // Release lock + unlockConfig(); + + std::string assetName = config.getValue("asset"); + + addTrigger(assetName, NULL); + + datapointJsonString = config.getValue("datapoints"); + getDatapointNamesConfig(); + // for (string elem: datapointNames){ + // Logger::getLogger()->debug(elem.c_str()); + // } + +} + +bool DeltaRule::evaluate(const std::string& asset, const std::string& datapoint, const rapidjson::Value& value) +{ + std::map::iterator it; + + rapidjson::Document* newvalue = new rapidjson::Document(); + newvalue->CopyFrom(value, newvalue->GetAllocator()); + std::string newValueString = seralizeJson(newvalue); + + if ((it = m_lastvalue.find(datapoint)) == m_lastvalue.end()) + { + //Logger::getLogger()->debug("VALUE INITIALIZING"); + m_lastvalue.insert(std::pair(datapoint, newvalue)); + if(this->actionJsonObject.empty()) + { + generateJsonActionObject(asset, datapoint, newValueString); + }else{ + std::string jsonActionItem= generateJsonActionItem(asset, datapoint, newValueString); + appendJsonActionItem(jsonActionItem); + } + return true; + } + + bool rval = false; + + rapidjson::Document* lastvalue = it->second; + it->second = newvalue; + std::string lastValueString = seralizeJson(lastvalue); + + + if (lastValueString == newValueString) + { + // Logger::getLogger()->debug("Values are equal. LastValue: %s NewValue: %s", lastValueString.c_str(), newValueString.c_str()); + if(this->actionJsonObject.empty()) + { + generateJsonActionObject(asset, datapoint, newValueString, lastValueString); + }else{ + std::string jsonActionItem= generateJsonActionItem(asset, datapoint, newValueString, lastValueString); + appendJsonActionItem(jsonActionItem); + } + rval = false; + } + else + { + // Logger::getLogger()->debug("Values are NOT equal. LastValue: %s NewValue: %s", lastValueString.c_str(), newValueString.c_str()); + if(this->actionJsonObject.empty()) + { + generateJsonActionObject(asset, datapoint, newValueString, lastValueString); + }else{ + std::string jsonActionItem= generateJsonActionItem(asset, datapoint, newValueString, lastValueString); + appendJsonActionItem(jsonActionItem); + } + rval = true; + } + + if (lastvalue != NULL) + { + delete lastvalue; + lastvalue = NULL; + } + + if (rval) + { + Logger::getLogger()->info("%s.%s triggered", + asset.c_str(), + datapoint.c_str() + ); + } + return rval; +} + +const std::string DeltaRule::seralizeJson(const rapidjson::Document* doc) +{ + if(doc) + { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc->Accept(writer); + + std::string jsonString = buffer.GetString(); + return jsonString; + + }else + { + return "{}"; + } + +} + +bool DeltaRule::chosenDatapoint(const std::string& datapoint) +{ + if(datapointNames.empty()) + { + Logger::getLogger()->warn("No datapoints have been submitted all datapoints in the asset will be considered"); + return true; + } + + if(std::find(datapointNames.begin(), datapointNames.end(), datapoint) != datapointNames.end()) + { + return true; + } + else + { + return false; + } +} + + +void DeltaRule::generateJsonActionObject(const std::string& asset, const std::string& datapoint, const std::string& newValue, const std::string& lastValue) +{ + const std::string new_datapoint = getAliasNameConfig(datapoint); + if(lastValue.empty()) + { + this->actionJsonObject = "{\"" + asset + "\": {\"" + new_datapoint + "\": { \"lastValue\": " + "null" + ", \"value\": " + newValue + "}}}"; + }else{ + this->actionJsonObject = "{\"" + asset + "\": {\"" + new_datapoint + "\": { \"lastValue\": " + lastValue + ", \"value\": " + newValue + "}}}"; + } +} + +const std::string DeltaRule::generateJsonActionItem(const std::string& asset, const std::string& datapoint, const std::string& newValue, const std::string& lastValue) +{ + const std::string new_datapoint = getAliasNameConfig(datapoint); + if(lastValue.empty()) + { + std::string actionJsonItem = "\"" + new_datapoint + "\": { \"lastValue\": " + "null" + ", \"value\": " + newValue + "}}"; + return actionJsonItem; + }else{ + std::string actionJsonItem = "\"" + new_datapoint + "\": { \"lastValue\": " + lastValue + ", \"value\": " + newValue + "}}"; + return actionJsonItem; + } +} + +void DeltaRule::appendJsonActionItem(const std::string& actionJsonItem) +{ + //Logger::getLogger()->debug("Append Item %s", actionJsonItem.c_str()); + //Delete last two characters to append item to current object + this->actionJsonObject.pop_back(); + this->actionJsonObject.pop_back(); + this->actionJsonObject += ", "; + this->actionJsonObject += actionJsonItem; + this->actionJsonObject += "}"; +} + +const std::string DeltaRule::getJsonActionObject() +{ + // return escape_json(actionJsonObject); + return actionJsonObject; +} + +void DeltaRule::setJsonActionObject(const std::string& jsonString) +{ + this->actionJsonObject = jsonString; +} + +std::string DeltaRule::escape_json(const std::string &s) +{ + std::ostringstream o; + for (auto c = s.cbegin(); c != s.cend(); c++) + { + switch (*c) + { + case '"': o << "\\\""; break; + case '\\': o << "\\\\"; break; + case '\b': o << "\\b"; break; + case '\f': o << "\\f"; break; + case '\n': o << "\\n"; break; + case '\r': o << "\\r"; break; + case '\t': o << "\\t"; break; + default: + if ('\x00' <= *c && *c <= '\x1f') { + o << "\\u" << std::hex << std::setw(4) << std::setfill('0') << (int)*c; + } + else + { + o << *c; + } + } + } + return o.str(); +} + +void DeltaRule::getDatapointNamesConfig(){ + Document configDoc; + configDoc.Parse(datapointJsonString.c_str()); + + for (Value::ConstMemberIterator itr = configDoc.MemberBegin();itr != configDoc.MemberEnd(); ++itr){ + datapointNames.push_back(itr->name.GetString()); + } +} + +const std::string DeltaRule::getAliasNameConfig(const std::string& datapointName){ + std::string alias_name; + Document configDoc; + configDoc.Parse(datapointJsonString.c_str()); + + for (Value::ConstMemberIterator itr = configDoc.MemberBegin();itr != configDoc.MemberEnd(); ++itr) + { + if (datapointName==itr->name.GetString()){ + if(itr->value.IsString()){ + alias_name = itr->value.GetString(); + if(alias_name.empty() == true){ + alias_name=itr->name.GetString(); + } + }else{ + Logger::getLogger()->info("Please submit a String as alias name"); + } + } + } + return alias_name; +} diff --git a/docs/images/average_1.jpg b/docs/images/average_1.jpg new file mode 100644 index 0000000..fb9bfee Binary files /dev/null and b/docs/images/average_1.jpg differ diff --git a/docs/images/average_2.jpg b/docs/images/average_2.jpg new file mode 100644 index 0000000..c12580d Binary files /dev/null and b/docs/images/average_2.jpg differ diff --git a/docs/images/average_3.jpg b/docs/images/average_3.jpg new file mode 100644 index 0000000..7230a5b Binary files /dev/null and b/docs/images/average_3.jpg differ diff --git a/docs/images/average_4.jpg b/docs/images/average_4.jpg new file mode 100644 index 0000000..3ae9a4e Binary files /dev/null and b/docs/images/average_4.jpg differ diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..7e4d076 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,45 @@ +.. Images +.. |delta_1| image:: images/delta_1.jpg +.. |delta_2| image:: images/delta_2.jpg +.. |delta_3| image:: images/delta_3.jpg +.. |delta_4| image:: images/delta_4.jpg + +Moving Delta Rule +=================== + +The *fledge-rule-delta* plugin is a notifcation rule that is used to detect when a value moves outside of the determined delta by more than a specified percentage. The plugin only monitors a single asset, but will monitor all data points within that asset. It will trigger if any of the data points within the asset differ by more than the configured percentage, an delta is maintained for each data point separately. + +During the configuration of a notification use the screen presented to choose the delta plugin as the rule. + ++-------------+ +| |delta_1| | ++-------------+ + +The next screen you are presented with provides the configuration options for the rule. + ++-------------+ +| |delta_2| | ++-------------+ + +The *Asset* entry field is used to define the single asset that the plugin should monitor. + +The *Deviation %* defines how far away from the observed delta the current value should be in order to considered as triggering the rule. + ++-------------+ +| |delta_3| | ++-------------+ + +The *Direction* entry is used to define if the rule should trigger when the current value is above delta, below delta or in both cases. + ++-------------+ +| |delta_4| | ++-------------+ + +The *Delta* entry is used to determine what type of delta is used for the calculation. The delta calculated may be either a simple moving delta or an exponential moving delta. If an exponential moving delta is chosen then a second configuration parameter, *EMA Factor*, allows the setting of the factor used to calculate that delta. + +Exponential moving deltas give more weight to the recent values compared to historical values. The smaller the EMA factor the more weight recent values carry. A value of 1 for *EMA Factor* will only consider the most recent value. + +.. note:: + + The Delta rule is not applicable to all data, only simple numeric values are considered and those values should not deviate with an delta of 0 or close to 0 if good results are required. Data points that deviate wildly are also not suitable for this plugin. + diff --git a/fledge.version b/fledge.version new file mode 100644 index 0000000..32f64f6 --- /dev/null +++ b/fledge.version @@ -0,0 +1 @@ +fledge_version>=1.9 diff --git a/include/delta.h b/include/delta.h new file mode 100644 index 0000000..ebeb9f1 --- /dev/null +++ b/include/delta.h @@ -0,0 +1,58 @@ +#ifndef _DELTA_RULE_H +#define _DELTA_RULE_H +/* + * Fledge Delta class + * + * Copyright (c) 2019 Dianomic Systems + * + * Released under the Apache 2.0 Licence + * + * Author: Mark Riddoch + */ +#include +#include +#include +#include +#include +#include +#include + +//#include +//#include + +/** + * Delta class, derived from Notification BuiltinRule + */ +class DeltaRule: public BuiltinRule +{ + public: + DeltaRule(); + ~DeltaRule(); + + void configure(const ConfigCategory& config); + void lockConfig() { m_configMutex.lock(); }; + void unlockConfig() { m_configMutex.unlock(); }; + + + bool evaluate(const std::string& asset, const std::string& datapoint, const rapidjson::Value& value); + bool chosenDatapoint(const std::string& datapoint); + const std::string getJsonActionObject(); + void setJsonActionObject(const std::string& jsonString); + + + private: + std::mutex m_configMutex; + std::map m_lastvalue; + std::string actionJsonObject; + std::vector datapointNames; + std::string datapointJsonString; + const std::string seralizeJson(const rapidjson::Document* doc); + void generateJsonActionObject(const std::string& asset, const std::string& datapoint, const std::string& newValue, const std::string& lastValue=""); + const std::string generateJsonActionItem(const std::string& asset, const std::string& datapoint, const std::string& newValue, const std::string& lastValue=""); + void appendJsonActionItem(const std::string& actionJson); + std::string escape_json(const std::string& s); + void getDatapointNamesConfig(); + const std::string getAliasNameConfig(const std::string& datapointName); +}; + +#endif diff --git a/install_manifest.txt b/install_manifest.txt new file mode 100644 index 0000000..58b73aa --- /dev/null +++ b/install_manifest.txt @@ -0,0 +1,2 @@ +/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so.1 +/usr/local/fledge/plugins/notificationRule/Delta/libDelta.so \ No newline at end of file diff --git a/mkversion b/mkversion new file mode 100755 index 0000000..6f71d5b --- /dev/null +++ b/mkversion @@ -0,0 +1,11 @@ +#!/bin/sh +cat > version.h << END_WARNING + +/* + * WARNING: This is an automatically generated file. + * Do not edit this file. + * To change the version edit the file VERSION + */ + +END_WARNING +/bin/echo '#define VERSION "'`cat $1/VERSION`'"' >> version.h diff --git a/plugin.cpp b/plugin.cpp new file mode 100644 index 0000000..75a5001 --- /dev/null +++ b/plugin.cpp @@ -0,0 +1,294 @@ +/** + * Fledge Delta notification rule plugin + * + * Copyright (c) 2021 ACDP + * + * Released under the Apache 2.0 Licence + * + * Author: Sebastian Kropatschek, Thorsten Steuer + */ + +/*********************************************************************** +* DISCLAIMER: +* +* All sample code is provided by ACDP for illustrative purposes only. +* These examples have not been thoroughly tested under all conditions. +* ACDP provides no guarantee nor implies any reliability, +* serviceability, or function of these programs. +* ALL PROGRAMS CONTAINED HEREIN ARE PROVIDED TO YOU "AS IS" +* WITHOUT ANY WARRANTIES OF ANY KIND. ALL WARRANTIES INCLUDING +* THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY +* AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY DISCLAIMED. +************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "version.h" +#include "delta.h" + + +#include + + +#define RULE_NAME "Delta" +#define DEFAULT_TIME_INTERVAL 30 + + + +static const char *default_config = QUOTE({ + "description": { + "description": "Trigger if the current value deviates from the last one", + "type": "string", + "default": RULE_NAME, + "readonly": "true" + }, + "plugin": { + "description": "Trigger if the current value deviates from the last one", + "type": "string", + "default": RULE_NAME, + "readonly": "true" + }, + "asset": { + "description" : "Asset to monitor", + "type" : "string", + "default" : "", + "displayName" : "Asset", + "order": "1" + }, + "datapoints": { + "description" : "Add datapoints to set triggers and alias names to define the keys in the action json", + "type" : "JSON", + "default" : "{ \"datapoint_name\" : \"alias_name\",\"sinusoid\" : \"cosinus\"}", + "displayName" : "JSON Configuration", + "order" : "2" + } +}); + + +using namespace std; + +/** + * The C plugin interface + */ +extern "C" { +/** + * The C API rule information structure + */ +static PLUGIN_INFORMATION ruleInfo = { + RULE_NAME, // Name + VERSION, // Version + 0, // Flags + PLUGIN_TYPE_NOTIFICATION_RULE, // Type + "1.0.0", // Interface version + default_config // Configuration +}; + +/** + * Return the information about this plugin + */ +PLUGIN_INFORMATION *plugin_info() +{ + return &ruleInfo; +} + +/** + * Initialise rule objects based in configuration + * + * @param config The rule configuration category data. + * @return The rule handle. + */ +PLUGIN_HANDLE plugin_init(const ConfigCategory& config) +{ + + DeltaRule *handle = new DeltaRule(); + handle->configure(config); + + return (PLUGIN_HANDLE)handle; +} + +/** + * Free rule resources + */ +void plugin_shutdown(PLUGIN_HANDLE handle) +{ + DeltaRule *rule = (DeltaRule *)handle; + // Delete plugin handle + delete rule; +} + +/** + * Return triggers JSON document + * + * @return JSON string + */ +string plugin_triggers(PLUGIN_HANDLE handle) +{ + string ret; + DeltaRule *rule = (DeltaRule *)handle; + + if (!rule) + { + ret = "{\"triggers\" : []}"; + return ret; + } + + // Configuration fetch is protected by a lock + rule->lockConfig(); + + if (!rule->hasTriggers()) + { + rule->unlockConfig(); + ret = "{\"triggers\" : []}"; + return ret; + } + + ret = "{\"triggers\" : [ "; + std::map triggers = rule->getTriggers(); + for (auto it = triggers.begin(); + it != triggers.end(); + ++it) + { + ret += "{ \"asset\" : \"" + (*it).first + "\""; + ret += " }"; + + if (std::next(it, 1) != triggers.end()) + { + ret += ", "; + } + } + + ret += " ] }"; + + // Release lock + rule->unlockConfig(); + + return ret; +} + +/** + * Evaluate notification data received + * + * @param assetValues JSON string document + * with notification data. + * @return True if the rule was triggered, + * false otherwise. + */ +bool plugin_eval(PLUGIN_HANDLE handle, + const string& assetValues) +{ + rapidjson::Document doc; + doc.Parse(assetValues.c_str()); + if (doc.HasParseError()) + { + return false; + } + + bool eval = false; + DeltaRule *rule = (DeltaRule *)handle; + map& triggers = rule->getTriggers(); + + // Iterate throgh all configured assets + // If we have multiple asset the evaluation result is + // TRUE only if all assets checks returned true + for (auto t = triggers.begin(); t != triggers.end(); ++t) + { + string assetName = t->first; + string assetTimestamp = "timestamp_" + assetName; + if (doc.HasMember(assetName.c_str())) + { + // Get all datapoints for assetName + const rapidjson::Value& assetValue = doc[assetName.c_str()]; + + for (rapidjson::Value::ConstMemberIterator itr = assetValue.MemberBegin(); + itr != assetValue.MemberEnd(); ++itr) + { + if(rule->chosenDatapoint(itr->name.GetString())) + { + eval |= rule->evaluate(assetName, itr->name.GetString(), itr->value); + } + + } + + if(!eval) + { + rule->setJsonActionObject(""); + } + // Add evalution timestamp + if (doc.HasMember(assetTimestamp.c_str())) + { + const rapidjson::Value& assetTime = doc[assetTimestamp.c_str()]; + double timestamp = assetTime.GetDouble(); + rule->setEvalTimestamp(timestamp); + } + } + } + + // Set final state: true is any calls to evalaute() returned true + rule->setState(eval); + + // Logger::getLogger()->debug("assetValues %s, eval: %d", assetValues.c_str(), eval); + + return eval; +} + +/** + * Return rule trigger reason: trigger or clear the notification. + * + * @return A JSON string + */ +string plugin_reason(PLUGIN_HANDLE handle) +{ + DeltaRule* rule = (DeltaRule *)handle; + BuiltinRule::TriggerInfo info; + rule->getFullState(info); + + string ret = "{ \"reason\": \""; + ret += info.getState() == BuiltinRule::StateTriggered ? "triggered" : "cleared"; + ret += "\""; + // Original Asset info from Plugin + //ret += ", \"asset\": " + info.getAssets(); + std::string jsonActionObject = rule->getJsonActionObject(); + // Send Json object as string doesn't work why???? + //ret += ", \"asset\": \"" + jsonActionObject + "\"" ; + // Send as Json Object + ret += ", \"asset\": " + jsonActionObject; + // Create extra entry in JSON asset name is then dublicated + //ret += ", " + jsonActionObject; + if (rule->getEvalTimestamp()) + { + ret += string(", \"timestamp\": \"") + info.getUTCTimestamp() + string("\""); + } + ret += " }"; + // Set action to empty string for next trigger message + rule->setJsonActionObject(""); + return ret; +} + +/** + * Call the reconfigure method in the plugin + * + * Not implemented yet + * + * @param newConfig The new configuration for the plugin + */ +void plugin_reconfigure(PLUGIN_HANDLE handle, + const string& newConfig) +{ + + DeltaRule* rule = (DeltaRule *)handle; + ConfigCategory config("new_outofbound", newConfig); + rule->configure(config); +} + +// End of extern "C" +}; diff --git a/service_notification.version b/service_notification.version new file mode 100644 index 0000000..69793ff --- /dev/null +++ b/service_notification.version @@ -0,0 +1 @@ +service_notification_version>=1.9.1 diff --git a/version.h b/version.h new file mode 100644 index 0000000..892db85 --- /dev/null +++ b/version.h @@ -0,0 +1,8 @@ + +/* + * WARNING: This is an automatically generated file. + * Do not edit this file. + * To change the version edit the file VERSION + */ + +#define VERSION "1.9.1"