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.