Skip to content

Commit

Permalink
Move up from legacy ACAP SDK to ACAP 4 Native SDK
Browse files Browse the repository at this point in the history
- Use FastCGI instead of legacy axhttp
- Move to C++20, enables making code simpler

Change-Id: I5d5c14c314b8296786f3a929f4f47952adfc819b
Signed-off-by: Joakim Roubert <[email protected]>
  • Loading branch information
joakimr-axis committed Feb 21, 2025
1 parent e376719 commit 6f64fe2
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 108 deletions.
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
ARG ARCH=aarch64
ARG SDK_VERSION=3.5
ARG SDK_IMAGE=axisecp/acap-sdk
ARG SDK_VERSION=12.2.0
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.4.4
ARG OPENCV_VERSION=4.5.5
ARG OPENCV_VERSION=4.11.0

FROM $SDK_IMAGE:$SDK_VERSION-$ARCH AS builder

Expand Down
22 changes: 12 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@ TARGET = opcuacolorchecker
OBJECTS = $(wildcard $(CURDIR)/src/*.cpp)
RM ?= rm -f

PKGS = gio-2.0 gio-unix-2.0 vdostream open62541 axevent axhttp axparameter
SDK_PKGS = axevent axparameter fcgi gio-2.0 gio-unix-2.0 open62541 vdostream
OWN_PKGS = opencv4 open62541

CXXFLAGS += -Os -pipe -std=c++11 -Wall -Werror -Wextra
CXXFLAGS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --cflags-only-I $(PKGS))
LDLIBS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --libs $(PKGS))
CXXFLAGS += -Os -pipe -std=c++20 -Wall -Werror -Wextra
CXXFLAGS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --cflags-only-I $(SDK_PKGS))
CXXFLAGS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) PKG_CONFIG_SYSROOT_DIR= pkg-config --cflags-only-I $(OWN_PKGS))
LDLIBS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) pkg-config --libs $(SDK_PKGS))
LDLIBS += $(shell PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) PKG_CONFIG_SYSROOT_DIR= pkg-config --libs $(SDK_PKGS) $(OWN_PKGS))

CXXFLAGS += -I$(SDKTARGETSYSROOT)/usr/include/opencv4 -I$(CURDIR)/include
CXXFLAGS += -I$(CURDIR)/include
LDFLAGS = -L./lib -Wl,--no-as-needed,-rpath,'$$ORIGIN/lib' -flto=auto
LDLIBS += -lm -lopencv_core -lopencv_imgproc -lopencv_video -lpthread
#LDLIBS += -lm -lpthread

# Set DEBUG_WRITE to write debug images to storage
ifneq ($(DEBUG_WRITE),)
CXXFLAGS += -DDEBUG_WRITE
LDLIBS += -lopencv_imgcodecs
DOCKER_ARGS += --build-arg DEBUG_WRITE=$(DEBUG_WRITE)
endif

Expand All @@ -24,14 +26,14 @@ endif
all: $(TARGET)

$(TARGET): $(OBJECTS)
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(LDLIBS) $^ -o $@ && \
$(CXX) $(CXXFLAGS) $^ -o $@ $(LDFLAGS) $(LDLIBS) && \
$(STRIP) --strip-unneeded $@

# docker build container targets
# Docker build container targets
%.eap:
DOCKER_BUILDKIT=1 docker build $(DOCKER_ARGS) --build-arg ARCH=$(*F) -o type=local,dest=. "$(CURDIR)"

dockerbuild: armv7hf.eap aarch64.eap
dockerbuild: aarch64.eap armv7hf.eap

clean:
$(RM) $(TARGET) *.eap* *_LICENSE.txt pa*.conf
27 changes: 14 additions & 13 deletions include/CgiHandler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,32 @@

#pragma once

#include <axhttp.h>
#include <fcgi_stdio.h>
#include <glib.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
#include <opencv2/core/core.hpp>
#pragma GCC diagnostic pop
#include <thread>

class CgiHandler
{
public:
CgiHandler(cv::Scalar (*GetColor)(), gboolean (*GetColorAreaValue)(), gboolean (*PickCurrentCallback)());
~CgiHandler();
static void RequestHandler(
const gchar *path,
const gchar *method,
const gchar *query,
GHashTable *params,
GOutputStream *output_stream,
gpointer user_data);

private:
static void
WriteErrorResponse(GDataOutputStream &dos, const guint32 statuscode, const gchar *statusname, const gchar *msg);
static void WriteBadRequest(GDataOutputStream &dos, const gchar *msg);
static void WriteInternalError(GDataOutputStream &dos, const gchar *msg);
void Run();
gboolean HandleFcgiRequest();

static void WriteResponse(FCGX_Stream &stream, const guint32 status_code, const gchar *mimetype, const gchar *msg);

cv::Scalar (*GetColor_)();
gboolean (*GetColorAreaValue_)();
gboolean (*PickCurrentCallback_)();

AXHttpHandler *http_handler_;
FCGX_Request request_;
bool running_;
int sock_;
std::jthread worker_;
};
12 changes: 4 additions & 8 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
{
"schemaVersion": "1.3",
"schemaVersion": "1.7.3",
"acapPackageConf": {
"setup": {
"friendlyName": "OPC UA Color Checker",
"appName": "opcuacolorchecker",
"vendor": "Axis Communications AB",
"embeddedSdkVersion": "3.0",
"user": {
"username": "sdk",
"group": "sdk"
},
"vendorUrl": "https://www.axis.com/",
"runMode": "respawn",
"version": "1.1.3"
"version": "2.0.0"
},
"configuration": {
"settingPage": "settings.html",
"httpConfig": [
{"type": "transferCgi", "name": "getstatus.cgi", "access": "viewer"},
{"type": "transferCgi", "name": "pickcurrent.cgi", "access": "admin"}
{"type": "fastCgi", "name": "getstatus.cgi", "access": "viewer"},
{"type": "fastCgi", "name": "pickcurrent.cgi", "access": "admin"}
],
"paramConfig": [
{"name": "CenterX", "type": "int:min=0,max=959", "default": "100"},
Expand Down
170 changes: 97 additions & 73 deletions src/CgiHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,109 +15,133 @@
*/

#include <assert.h>
#include <ostream>
#include <sstream>
#include <filesystem>
#include <format>
#include <string>
#include <sys/stat.h>

#include "CgiHandler.hpp"
#include "ColorArea.hpp"
#include "common.hpp"

using namespace std;
#define FCGI_SOCKET_NAME "FCGI_SOCKET_NAME"

#define MIME_JSON "application/json"
#define MIME_TEXT "text/plain"

CgiHandler::CgiHandler(cv::Scalar (*GetColor)(), gboolean (*GetColorAreaValue)(), gboolean (*PickCurrentCallback)())
: GetColor_(GetColor), GetColorAreaValue_(GetColorAreaValue), PickCurrentCallback_(PickCurrentCallback),
http_handler_(ax_http_handler_new(this->RequestHandler, this))
running_(false)
{
assert(nullptr != GetColor_);
assert(nullptr != GetColorAreaValue_);
assert(nullptr != PickCurrentCallback_);
assert(nullptr != http_handler_);
}

CgiHandler::~CgiHandler()
{
LOG_I("%s/%s: Free http handler ...", __FILE__, __FUNCTION__);
assert(nullptr != http_handler_);
ax_http_handler_free(http_handler_);
}
LOG_I("Setting up FastCGI ...");
auto socket_path = getenv(FCGI_SOCKET_NAME);
if (nullptr == socket_path)
{
LOG_E("Failed to get environment variable FCGI_SOCKET_NAME");
assert(false);
}

void CgiHandler::WriteErrorResponse(
GDataOutputStream &dos,
const guint32 statuscode,
const gchar *statusname,
const gchar *msg)
{
ostringstream ss;
ss << "Status: " << statuscode << " " << statusname << "\r\n"
<< "Content-Type: text/html\r\n"
<< "\r\n"
<< "<HTML><HEAD><TITLE>" << statuscode << " " << statusname << "</TITLE></HEAD>\n"
<< "<BODY><H1>" << statuscode << " " << statusname << "</H1>\n"
<< msg << "\n"
<< "</BODY></HTML>\n";

g_data_output_stream_put_string(&dos, ss.str().c_str(), nullptr, nullptr);
LOG_I("Using socket path %s", socket_path);
if (0 != FCGX_Init())
{
LOG_E("FCGX_Init failed");
assert(false);
}

sock_ = FCGX_OpenSocket(socket_path, 5);
chmod(socket_path, S_IRWXU | S_IRWXG | S_IRWXO);
if (0 != FCGX_InitRequest(&request_, sock_, 0))
{
LOG_E("FCGX_InitRequest failed");
assert(false);
}

LOG_I("Set up FastCGI for %s, start handling incoming CGI requests ...", socket_path);
worker_ = std::jthread(&CgiHandler::Run, this);
}

void CgiHandler::WriteBadRequest(GDataOutputStream &dos, const gchar *msg)
CgiHandler::~CgiHandler()
{
WriteErrorResponse(dos, 400, "Bad Request", msg);
// std::jthread automatically joins, so we only need to set running_ to false
LOG_I("Stop CGI handling ...");
running_ = false;
LOG_I("Shutting down FastCGI ...");
close(sock_);
}

void CgiHandler::WriteInternalError(GDataOutputStream &dos, const gchar *msg)
void CgiHandler::Run()
{
WriteErrorResponse(dos, 500, "Internal Server Error", msg);
running_ = true;
while (running_)
{
HandleFcgiRequest();
}
}

void CgiHandler::RequestHandler(
const gchar *path,
const gchar *method,
const gchar *query,
GHashTable *params,
GOutputStream *output_stream,
gpointer user_data)
gboolean CgiHandler::HandleFcgiRequest()
{
(void)method;
(void)query;
(void)params;
if (0 == FCGX_Accept_r(&request_))
{
LOG_I("FCGX_Accept_r OK");

auto dos = g_data_output_stream_new(output_stream);
assert(nullptr != dos);
assert(nullptr != user_data);
auto cgi_handler = static_cast<CgiHandler *>(user_data);
// Extract the CGI call only from the request
const auto command = std::filesystem::path(FCGX_GetParam("SCRIPT_NAME", request_.envp)).filename().string();

const auto func = basename(const_cast<char *>(path));
if (0 == strcmp("getstatus.cgi", func))
{
assert(nullptr != cgi_handler->GetColorAreaValue_);
const auto status = cgi_handler->GetColorAreaValue_();
g_data_output_stream_put_string(dos, "Status: 200 OK\r\n", nullptr, nullptr);
g_data_output_stream_put_string(dos, "Content-Type: application/json\r\n\r\n", nullptr, nullptr);
ostringstream ss;
ss << "{\"status\": " << (status ? "true" : "false") << "}" << endl;
g_data_output_stream_put_string(dos, ss.str().c_str(), nullptr, nullptr);
}
else if (0 == strcmp("pickcurrent.cgi", func))
{
assert(nullptr != cgi_handler->PickCurrentCallback_);
if (!cgi_handler->PickCurrentCallback_())
if ("getstatus.cgi" == command)
{
WriteInternalError(*dos, "Failed to pick current color");
goto http_exit;
assert(nullptr != GetColorAreaValue_);
const auto status_json = std::format("{{\"status\": {:b}}}", GetColorAreaValue_());
WriteResponse(*request_.out, 200, MIME_JSON, status_json.c_str());
}
else if ("pickcurrent.cgi" == command)
{
assert(nullptr != PickCurrentCallback_);
if (!PickCurrentCallback_())
{
WriteResponse(*request_.out, 500, MIME_TEXT, "Failed to pick current color");
}
else
{
const auto color = GetColor_();
const auto color_json = std::format(R"({{"R": {}, "G": {}, "B": {}}})", color[R], color[G], color[B]);
WriteResponse(*request_.out, 200, MIME_JSON, color_json.c_str());
}
}
else
{
assert(nullptr != request_.out);
WriteResponse(*request_.out, 400, MIME_TEXT, std::format("Unknown command '{}'", command).c_str());
}

const auto color = cgi_handler->GetColor_();
g_data_output_stream_put_string(dos, "Status: 200 OK\r\n", nullptr, nullptr);
g_data_output_stream_put_string(dos, "Content-Type: application/json\r\n\r\n", nullptr, nullptr);
ostringstream ss;
ss << "{\"R\":" << color[R] << ", \"G\":" << color[G] << ", \"B\":" << color[B] << "}" << endl;
g_data_output_stream_put_string(dos, ss.str().c_str(), nullptr, nullptr);
FCGX_Finish_r(&request_);
}
else
return G_SOURCE_CONTINUE;
}

void CgiHandler::WriteResponse(FCGX_Stream &stream, const guint32 status_code, const gchar *mimetype, const gchar *msg)
{
std::string descr;
switch (status_code)
{
WriteBadRequest(*dos, "Unknown action");
case 200:
descr = "OK";
break;
case 400:
descr = "Bad Request";
break;
case 500:
descr = "Internal Server Error";
break;
default:
LOG_E("%s/%s: Error code %u not yet implemented", __FILE__, __FUNCTION__, status_code);
assert(false);
break;
}
http_exit:
g_object_unref(dos);
const std::string response_text =
std::format("Status: {} {}\r\nContent-Type: {}\r\n\r\n{}", status_code, descr.c_str(), mimetype, msg);
FCGX_FPrintF(&stream, response_text.c_str());
}
2 changes: 1 addition & 1 deletion src/opcuacolorchecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ int main(int argc, char *argv[])
goto exit_param;
}

LOG_I("Start main loop ...");
LOG_I("Create and start main loop ...");
assert(nullptr == loop);
loop = g_main_loop_new(nullptr, FALSE);
g_main_loop_run(loop);
Expand Down

0 comments on commit 6f64fe2

Please sign in to comment.