From c0390e58f6fa92933dfae74857e24d0ab4f79369 Mon Sep 17 00:00:00 2001 From: Joakim Roubert Date: Tue, 10 Sep 2024 16:53:21 +0200 Subject: [PATCH] Add functionality that adds value to dynamic string (#42) This means e.g. overlay widget gauges (in a camera with support for that) can display the digital value on screen. Along with this update, we also move to the current version of native ACAP 4 SDK for building and hence drop support for legacy versions of Axis OS. For legacy devices, the previous commit can still be successfully used, only that it (at this point) will not have the data written to the dynamic string. Signed-off-by: Joakim Roubert --- Dockerfile | 8 +- LICENSE | 2 +- Makefile | 2 +- README.md | 12 ++- include/common.hpp | 2 +- include/dynstrhandler.hpp | 45 +++++++++++ include/gauge.hpp | 2 +- include/imgprovider.hpp | 8 +- include/opcuaserver.hpp | 2 +- manifest.json | 40 +++++----- src/dynstrhandler.cpp | 164 ++++++++++++++++++++++++++++++++++++++ src/gauge.cpp | 2 +- src/imgprovider.cpp | 7 +- src/opcuagaugereader.cpp | 26 +++++- src/opcuaserver.cpp | 2 +- 15 files changed, 286 insertions(+), 38 deletions(-) create mode 100644 include/dynstrhandler.hpp create mode 100644 src/dynstrhandler.cpp diff --git a/Dockerfile b/Dockerfile index b8ca203..6fa2e80 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,13 @@ ARG ARCH=aarch64 -ARG ACAP_SDK_VERSION=3.5 -ARG SDK_IMAGE=axisecp/acap-sdk +ARG SDK_VERSION=1.15 +ARG SDK_IMAGE=axisecp/acap-native-sdk ARG DEBUG_WRITE ARG BUILD_DIR=/opt/build ARG ACAP_BUILD_DIR="$BUILD_DIR"/app ARG OPEN62541_VERSION=1.2.9 ARG OPENCV_VERSION=4.5.5 -FROM $SDK_IMAGE:$ACAP_SDK_VERSION-$ARCH-ubuntu20.04 AS builder +FROM $SDK_IMAGE:$SDK_VERSION-$ARCH AS builder # Set general arguments ARG ARCH @@ -105,7 +105,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN curl -L https://github.com/open62541/open62541/archive/refs/tags/v$OPEN62541_VERSION.tar.gz | tar xz WORKDIR "$OPEN62541_BUILD_DIR" RUN . /opt/axis/acapsdk/environment-setup* && \ - cmake -j \ + cmake \ -DCMAKE_INSTALL_PREFIX="$SDKTARGETSYSROOT"/usr \ -DBUILD_BUILD_EXAMPLES=OFF \ -DBUILD_SHARED_LIBS=ON \ diff --git a/LICENSE b/LICENSE index 338d683..eb2e6d2 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [2022] [Axis Communications AB] + Copyright [2024] [Axis Communications AB] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Makefile b/Makefile index 9704331..71decb0 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ TARGET = opcuagaugereader OBJECTS = $(wildcard $(CURDIR)/src/*.cpp) RM ?= rm -f -PKGS = gio-2.0 gio-unix-2.0 vdostream open62541 axparameter +PKGS = gio-2.0 gio-unix-2.0 vdostream open62541 libcurl axparameter CXXFLAGS += -Os -pipe -std=c++11 -Wall -Werror -Wextra CXXFLAGS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --cflags-only-I $(PKGS)) diff --git a/README.md b/README.md index 5125be2..ddd239f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -*Copyright (C) 2022, Axis Communications AB, Lund, Sweden. All Rights Reserved.* +*Copyright (C) 2024, Axis Communications AB, Lund, Sweden. All Rights Reserved.* # OPC UA Gauge Reader ACAP @@ -162,6 +162,7 @@ curl -k --anyauth -u root: \ will list the current settings: ```sh +root.Opcuagaugereader.DynamicStringNumber=1 root.Opcuagaugereader.centerX=479 root.Opcuagaugereader.centerY=355 root.Opcuagaugereader.clockwise=1 @@ -187,6 +188,15 @@ to read the value (and its timestamp) from the application's OPC UA server. > [!NOTE] > The application will also log the gauge value in the camera's syslog. +### Bonus + +In addition to the above, the application will write the extracted gauge +reading as a +[dynamic text overlay](https://www.axis.com/vapix-library/subjects/t10175981/section/t10007638/display?section=t10007638-t10003585) +string. It can then be displayed as camera text overlay—or used by graph +widgets—with the modifier **#D***N*, where *N* is set by the application +parameter `DynamicStringNumber`. + ## License [Apache 2.0](LICENSE) diff --git a/include/common.hpp b/include/common.hpp index 0e7e156..f0311bb 100644 --- a/include/common.hpp +++ b/include/common.hpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022, Axis Communications AB, Lund, Sweden + * Copyright (C) 2024, Axis Communications AB, Lund, Sweden * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/include/dynstrhandler.hpp b/include/dynstrhandler.hpp new file mode 100644 index 0000000..5d8153b --- /dev/null +++ b/include/dynstrhandler.hpp @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2024, Axis Communications AB, Lund, Sweden + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +/** + * brief A type for handling setting dynamic text overlay string via VAPIX. + * + * This is not needed for OPC UA, but enables the camera to use the extracted + * gauge reading in overlays, which can add value to the live view. + */ +class DynStrHandler +{ + public: + DynStrHandler(const guint8 nbr); + ~DynStrHandler(); + void SetStrNumber(const guint8 newnbr); + void UpdateStr(const double value); + + private: + std::string RetrieveVapixCredentials(const char &username) const; + gboolean VapixGet(const std::string &url); + + CURL *curl; + guint8 nbr; + std::chrono::time_point lastupdate; +}; diff --git a/include/gauge.hpp b/include/gauge.hpp index 358f855..5c29c89 100644 --- a/include/gauge.hpp +++ b/include/gauge.hpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022, Axis Communications AB, Lund, Sweden + * Copyright (C) 2024, Axis Communications AB, Lund, Sweden * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/include/imgprovider.hpp b/include/imgprovider.hpp index c431ac6..d263d76 100644 --- a/include/imgprovider.hpp +++ b/include/imgprovider.hpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022, Axis Communications AB, Lund, Sweden + * Copyright (C) 2024, Axis Communications AB, Lund, Sweden * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,10 @@ #include #include -#include "vdo-stream.h" -#include "vdo-types.h" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include +#include +#pragma GCC diagnostic pop #define _Atomic(X) std::atomic #define NUM_VDO_BUFFERS (8) diff --git a/include/opcuaserver.hpp b/include/opcuaserver.hpp index a4e23fb..3a71f75 100644 --- a/include/opcuaserver.hpp +++ b/include/opcuaserver.hpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022, Axis Communications AB, Lund, Sweden + * Copyright (C) 2024, Axis Communications AB, Lund, Sweden * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/manifest.json b/manifest.json index ad4e136..b4cd8b6 100644 --- a/manifest.json +++ b/manifest.json @@ -1,31 +1,33 @@ { - "schemaVersion": "1.3", + "schemaVersion": "1.7.0", "acapPackageConf": { "setup": { "friendlyName": "OPC UA Gauge Reader", "appName": "opcuagaugereader", "vendor": "Axis Communications AB", "embeddedSdkVersion": "3.0", - "user": { - "group": "sdk", - "username": "sdk" - }, "vendorUrl": "https://www.axis.com/", "runMode": "respawn", - "version": "1.2.1" + "version": "2.0.0" }, - "configuration": { - "settingPage": "settings.html", - "paramConfig": [ - {"name": "clockwise", "type": "bool:0,1", "default": "1"}, - {"name": "maxX", "type": "int:min=0,max=639", "default": "150"}, - {"name": "maxY", "type": "int:min=0,max=359", "default": "150"}, - {"name": "centerX", "type": "int:min=0,max=639", "default": "100"}, - {"name": "centerY", "type": "int:min=0,max=359", "default": "170"}, - {"name": "minX", "type": "int:min=0,max=639", "default": "50"}, - {"name": "minY", "type": "int:min=0,max=359", "default": "150"}, - {"name": "port", "type": "int:min=1,max=65535", "default": "4840"} - ] - } + "configuration": { + "settingPage": "settings.html", + "paramConfig": [ + {"name": "DynamicStringNumber", "type": "int:min=1,max=16", "default": "1"}, + {"name": "clockwise", "type": "bool:0,1", "default": "1"}, + {"name": "maxX", "type": "int:min=0,max=639", "default": "150"}, + {"name": "maxY", "type": "int:min=0,max=359", "default": "150"}, + {"name": "centerX", "type": "int:min=0,max=639", "default": "100"}, + {"name": "centerY", "type": "int:min=0,max=359", "default": "170"}, + {"name": "minX", "type": "int:min=0,max=639", "default": "50"}, + {"name": "minY", "type": "int:min=0,max=359", "default": "150"}, + {"name": "port", "type": "int:min=1,max=65535", "default": "4840"} + ] + } + }, + "resources": { + "dbus": { + "requiredMethods": ["com.axis.HTTPConf1.VAPIXServiceAccounts1.GetCredentials"] + } } } diff --git a/src/dynstrhandler.cpp b/src/dynstrhandler.cpp new file mode 100644 index 0000000..146dc1c --- /dev/null +++ b/src/dynstrhandler.cpp @@ -0,0 +1,164 @@ +/** + * Copyright (C) 2024, Axis Communications AB, Lund, Sweden + * + * 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. + */ + +#include +#include +#include + +#include "common.hpp" +#include "dynstrhandler.hpp" + +using namespace std; +using namespace std::chrono; + +static size_t append_to_string_callback(char *ptr, size_t size, size_t nmemb, string *response) +{ + assert(nullptr != response); + auto totalsize = size * nmemb; + response->append(ptr, totalsize); + + return totalsize; +} + +DynStrHandler::DynStrHandler(const guint8 nbr) : curl(nullptr), nbr(nbr), lastupdate(steady_clock::now()) +{ + assert(1 <= nbr); + assert(16 >= nbr); + + curl_global_init(CURL_GLOBAL_DEFAULT); + curl = curl_easy_init(); + assert(nullptr != curl); + + const gchar *user = "example-vapix-user"; + auto credentials = RetrieveVapixCredentials(*user); + + auto curl_init = + (CURLE_OK == curl_easy_setopt(curl, CURLOPT_HTTPAUTH, (long)(CURLAUTH_DIGEST | CURLAUTH_BASIC)) && + CURLE_OK == curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 2L) && + CURLE_OK == curl_easy_setopt(curl, CURLOPT_USERPWD, credentials.c_str()) && + CURLE_OK == curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L) && + CURLE_OK == curl_easy_setopt(curl, CURLOPT_TIMEOUT, 1) && + CURLE_OK == curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, append_to_string_callback)); + + assert(curl_init); + LOG_I("%s/%s: Dynamic string handler constructor is done!", __FILE__, __FUNCTION__); +} + +DynStrHandler::~DynStrHandler() +{ + assert(nullptr != curl); + curl_easy_cleanup(curl); + curl_global_cleanup(); +} + +void DynStrHandler::SetStrNumber(const guint8 newnbr) +{ + nbr = newnbr; + LOG_I("Now using dynamic string number %u", newnbr); +} + +void DynStrHandler::UpdateStr(const double value) +{ + // We don't need to update too frequently + auto nowtime = steady_clock::now(); + if (1 > duration_cast(nowtime - lastupdate).count()) + { + return; + } + + auto url = "http://127.0.0.12/axis-cgi/dynamicoverlay.cgi?action=settext&text_index=" + to_string(nbr) + + "&text=" + to_string(value); + if (!VapixGet(url)) + { + LOG_E("%s/%s: Failed to update dynamic string", __FILE__, __FUNCTION__); + } + lastupdate = nowtime; +} + +string DynStrHandler::RetrieveVapixCredentials(const char &username) const +{ + GError *error = nullptr; + auto connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, &error); + if (nullptr == connection) + { + LOG_E("Error connecting to D-Bus: %s", error->message); + g_error_free(error); + return nullptr; + } + + const char *bus_name = "com.axis.HTTPConf1"; + const char *object_path = "/com/axis/HTTPConf1/VAPIXServiceAccounts1"; + const char *interface_name = "com.axis.HTTPConf1.VAPIXServiceAccounts1"; + const char *method_name = "GetCredentials"; + + auto result = g_dbus_connection_call_sync( + connection, + bus_name, + object_path, + interface_name, + method_name, + g_variant_new("(s)", &username), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (nullptr == result) + { + LOG_E("Error invoking D-Bus method: %s", error->message); + g_error_free(error); + return ""; + } + + // Extract credentials string + const char *credentials_string = nullptr; + g_variant_get(result, "(&s)", &credentials_string); + string credentials(credentials_string); + g_variant_unref(result); + + return credentials; +} + +gboolean DynStrHandler::VapixGet(const string &url) +{ + assert(nullptr != curl); + + string response; + + if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_URL, url.c_str()) || + CURLE_OK != curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response)) + { + LOG_E("%s/%s: Failed to set up cURL options", __FILE__, __FUNCTION__); + return FALSE; + } + + auto res = curl_easy_perform(curl); + if (res != CURLE_OK) + { + LOG_E("%s/%s: curl fail %d '%s''", __FILE__, __FUNCTION__, res, curl_easy_strerror(res)); + return FALSE; + } + + long response_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + if (200 != response_code) + { + LOG_E("Got response code %ld with response '%s'", response_code, response.c_str()); + return FALSE; + } + + return TRUE; +} diff --git a/src/gauge.cpp b/src/gauge.cpp index c57bc19..5ede047 100644 --- a/src/gauge.cpp +++ b/src/gauge.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022, Axis Communications AB, Lund, Sweden + * Copyright (C) 2024, Axis Communications AB, Lund, Sweden * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/imgprovider.cpp b/src/imgprovider.cpp index 482937f..2cc80ea 100644 --- a/src/imgprovider.cpp +++ b/src/imgprovider.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022, Axis Communications AB, Lund, Sweden + * Copyright (C) 2024, Axis Communications AB, Lund, Sweden * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,14 @@ #include #include + +#pragma GCC diagnostic ignored "-Wunused-parameter" #include +#include +#pragma GCC diagnostic pop #include "common.hpp" #include "imgprovider.hpp" -#include "vdo-map.h" #define VDO_CHANNEL (1) diff --git a/src/opcuagaugereader.cpp b/src/opcuagaugereader.cpp index b94bd20..862ba7a 100644 --- a/src/opcuagaugereader.cpp +++ b/src/opcuagaugereader.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022, Axis Communications AB, Lund, Sweden + * Copyright (C) 2024, Axis Communications AB, Lund, Sweden * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ #include #include "common.hpp" +#include "dynstrhandler.hpp" #include "gauge.hpp" #include "imgprovider.hpp" #include "opcuaserver.hpp" @@ -46,6 +47,9 @@ static ImgProvider *provider = nullptr; static Mat nv12_mat; static Mat gray_mat; +static DynStrHandler *dynstrhandler = nullptr; +static guint8 dynstrnbr = 0; + static gchar *get_param(AXParameter &axparameter, const gchar &name) { GError *error = nullptr; @@ -92,6 +96,15 @@ static void update_local_param(const gchar &name, const uint32_t val) } return; } + else if (0 == strncmp("DynamicStringNumber", &name, 19)) + { + dynstrnbr = val; + if (nullptr != dynstrhandler) + { + dynstrhandler->SetStrNumber(dynstrnbr); + } + return; + } // The following parameters trigger recalibration of the gauge if (0 == strncmp("cloc", &name, 4)) @@ -228,6 +241,10 @@ static gboolean imageanalysis(gpointer data) { LOG_I("%s/%s: Value was %f", __FILE__, __FUNCTION__, value); opcuaserver.UpdateGaugeValue(value); + if (nullptr != dynstrhandler) + { + dynstrhandler->UpdateStr(value); + } } // Release the VDO frame buffer @@ -349,7 +366,8 @@ int main(int argc, char *argv[]) } LOG_I("%s/%s: ax_parameter_new success", __FILE__, __FUNCTION__); // clang-format off - if (!setup_param(*axparameter, "centerX", param_callback) || + if (!setup_param(*axparameter, "DynamicStringNumber", param_callback) || + !setup_param(*axparameter, "centerX", param_callback) || !setup_param(*axparameter, "centerY", param_callback) || !setup_param(*axparameter, "clockwise", param_callback) || !setup_param(*axparameter, "maxX", param_callback) || @@ -368,6 +386,9 @@ int main(int argc, char *argv[]) LOG_I("%s/%s: min: (%u, %u)", __FILE__, __FUNCTION__, min_point.x, min_point.y); LOG_I("%s/%s: max: (%u, %u)", __FILE__, __FUNCTION__, max_point.x, max_point.y); + // Init dynamic string handling + dynstrhandler = new DynStrHandler(dynstrnbr); + // Initialize image analysis if (!initimageanalysis()) { @@ -399,6 +420,7 @@ int main(int argc, char *argv[]) opcuaserver.ShutDownServer(); exit_param: + delete dynstrhandler; ax_parameter_free(axparameter); exit: diff --git a/src/opcuaserver.cpp b/src/opcuaserver.cpp index 6260f2c..5db2d2f 100644 --- a/src/opcuaserver.cpp +++ b/src/opcuaserver.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022, Axis Communications AB, Lund, Sweden + * Copyright (C) 2024, Axis Communications AB, Lund, Sweden * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.