Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ ARG REPO=axisecp
ARG SDK=acap-native-sdk
# Keep in sync with `install-sdk.sh` and `on-host-workflow.yml`.
ARG UBUNTU_VERSION=24.04
# Keep in sync with `install-sdk.sh`.
# Keep in sync with `install-sdk.sh` and `AXIS_OS_VERSION` below.
ARG VERSION=12.11.0
ARG BASE_IMAGE=debian:trixie-20260223

Expand All @@ -22,6 +22,7 @@ ENV \
SYSROOT_ARMV7HF=/opt/axis/acapsdk/sysroots/armv7hf
# The above makes the below easier to read
ENV \
AXIS_OS_VERSION=12.11 \
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER="aarch64-linux-gnu-gcc" \
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-args=--sysroot=${SYSROOT_AARCH64}" \
CC_aarch64_unknown_linux_gnu="aarch64-linux-gnu-gcc" \
Expand Down
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ bbox = { path = "crates/bbox" }
bbox-sys = { path = "crates/bbox-sys" }
cargo-acap-build = { path = "crates/cargo-acap-build" }
cli-version = { path = "crates/cli-version" }
datahub-sys = { path = "crates/datahub-sys" }
larod-sys = { path = "crates/larod-sys" }
licensekey = { path = "crates/licensekey" }
licensekey-sys = { path = "crates/licensekey-sys" }
Expand Down
1 change: 1 addition & 0 deletions apps-aarch64.checksum
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ eea87093d21a9c5975146e7e901b2af719751516 target-aarch64/acap/event_subscribe_1_
b094d400939f066dd4db602469c1f4fb242d4ca2 target-aarch64/acap/hello_world_0_0_0_aarch64.eap
0fec22df9046a67af4c0bb3f05e64a9e348d0cd6 target-aarch64/acap/inspect_env_0_0_0_aarch64.eap
44d11b7400ed107e3b091e3cd9b770336679c331 target-aarch64/acap/licensekey_handler_0_0_0_aarch64.eap
8982d13060a23b8e87fdbb803c5246d8afae0228 target-aarch64/acap/object_consumer_0_0_0_aarch64.eap
4fe894e74fb465817c3333dd4f3edfc7cca61766 target-aarch64/acap/object_detection_1_0_0_aarch64.eap
04dca71df69368834ce03d6c485597b115a00146 target-aarch64/acap/reverse_proxy_0_0_0_aarch64.eap
7e453ac8f014f7c54e7cae8d025b6d34509cad7a target-aarch64/acap/send_event_1_0_0_aarch64.eap
Expand Down
1 change: 1 addition & 0 deletions apps-aarch64.filesize
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
1406 target-aarch64/acap/hello_world_0_0_0_aarch64.eap
1441 target-aarch64/acap/inspect_env_0_0_0_aarch64.eap
1430 target-aarch64/acap/licensekey_handler_0_0_0_aarch64.eap
1418 target-aarch64/acap/object_consumer_0_0_0_aarch64.eap
1408 target-aarch64/acap/object_detection_1_0_0_aarch64.eap
10315 target-aarch64/acap/reverse_proxy_0_0_0_aarch64.eap
3437 target-aarch64/acap/send_event_1_0_0_aarch64.eap
Expand Down
15 changes: 15 additions & 0 deletions apps/object_consumer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "object_consumer"
version = "0.0.0"
edition.workspace = true
publish = false

[dependencies]
libc = { workspace = true }
log = { workspace = true }

acap-logging = { workspace = true }
datahub-sys = { workspace = true }

[features]
default = ["acap-logging/default"]
22 changes: 22 additions & 0 deletions apps/object_consumer/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"schemaVersion": "2.2.0",
"acapPackageConf": {
"setup": {
"appName": "object_consumer",
"vendor": "Axis Communications",
"vendorId": "0123456789",
"runMode": "never",
"version": "0.0.0",
"compatibleOsVersions": [
{
"max": "13"
}
]
}
},
"resources": {
"deviceDataHub": {
"enabled": true
}
}
}
203 changes: 203 additions & 0 deletions apps/object_consumer/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
//! An example app that subscribes to object detection data using [`datahub_sys`].
//!
//! Re-implements the [C example for consuming Device Data Hub data].
//! Unlike the C example, which relies on globals and `atexit`, resources are owned by `main` and
//! released explicitly on every exit path.
//!
//! Note that this example does not do anything on its own, and
//! its counterpart (`object_detector`) has yet to be ported.
//!
//! [C example for consuming Device Data Hub data]: https://github.com/AxisCommunications/acap-native-sdk-examples/tree/main/device-data-hub/acap-communication/object-consumer

use std::{
ffi::{c_char, c_int, c_void, CStr},
process::ExitCode,
ptr,
sync::atomic::{AtomicBool, Ordering},
};

use datahub_sys::{
dh_client_connect, dh_client_create, dh_client_create_subscriber, dh_client_destroy,
dh_client_disconnect, dh_client_set_logging, dh_error_destroy, dh_error_to_string,
dh_filter_add_topic_name, dh_filter_create, dh_filter_destroy, dh_subscribe_options_add_filter,
dh_subscribe_options_create, dh_subscribe_options_destroy,
dh_subscribe_options_set_enable_data_updates, dh_subscriber_destroy,
dh_subscriber_set_data_callback, dh_subscriber_subscribe, dh_topic_data_get_json_data,
dh_topic_sample_get_data, DHClient, DHError, DHSubscriber, DHTopicSample, DH_LOG_INFO,
DH_LOG_TARGET_CONSOLE,
};
use libc::{SIGINT, SIGTERM};
use log::{error, info};

const TOPIC_NAME: &CStr = c"com.example.objectdetector";
const USER_DATA: &CStr = c"object_consumer_data";

static KEEP_RUNNING: AtomicBool = AtomicBool::new(true);

/// Logs and destroys `error`, returning `true`, if it is set; returns `false` otherwise.
unsafe fn handle_client_error(error: *mut DHError, context: &str) -> bool {
if error.is_null() {
return false;
}
let message = CStr::from_ptr(dh_error_to_string(error)).to_string_lossy();
error!("Error in {context}: {message}");
let () = dh_error_destroy(error);
true
}

/// Creates a client and connects it to the Device Data Hub.
unsafe fn initialize_client() -> Option<*mut DHClient> {
let mut error: *mut DHError = ptr::null_mut();
let client = dh_client_create(c"Client for object_consumer".as_ptr(), &mut error);
if client.is_null() {
let _ = handle_client_error(error, "create client");
return None;
}

let mut error: *mut DHError = ptr::null_mut();
if !dh_client_set_logging(client, DH_LOG_INFO, DH_LOG_TARGET_CONSOLE, &mut error) {
let _ = handle_client_error(error, "set logging");
}

let mut error: *mut DHError = ptr::null_mut();
let _ = dh_client_connect(client, &mut error);
if handle_client_error(error, "client connect") {
let () = dh_client_destroy(client);
return None;
}

Some(client)
}

/// Logs the data of each received sample.
unsafe extern "C" fn on_data_received(sample: *const DHTopicSample, user_data: *mut c_void) {
debug_assert!(!sample.is_null());
debug_assert!(!user_data.is_null());
let user_data = CStr::from_ptr(user_data.cast::<c_char>()).to_string_lossy();
info!("User data: {user_data}");
let topic_data = dh_topic_sample_get_data(sample);
let data = dh_topic_data_get_json_data(topic_data);
if !data.is_null() {
let data = CStr::from_ptr(data).to_string_lossy();
info!("Received Object Detection data: {data}");
}
}

/// Creates a subscriber and subscribes to data updates for `topics`.
unsafe fn setup_subscription(client: *mut DHClient, topics: &[&CStr]) -> Option<*mut DHSubscriber> {
debug_assert!(!client.is_null());
let mut error: *mut DHError = ptr::null_mut();
let subscriber = dh_client_create_subscriber(
client,
c"Data subscriber for object-consumer".as_ptr(),
&mut error,
);
if handle_client_error(error, "create subscriber") {
return None;
}

let mut error: *mut DHError = ptr::null_mut();
let _ = dh_subscriber_set_data_callback(
subscriber,
Some(on_data_received),
USER_DATA.as_ptr().cast_mut().cast(),
&mut error,
);
if handle_client_error(error, "set data callback") {
let () = dh_subscriber_destroy(subscriber);
return None;
}

let filter = dh_filter_create();
if filter.is_null() {
error!("Failed to create filter");
let () = dh_subscriber_destroy(subscriber);
return None;
}

for topic in topics {
let mut error: *mut DHError = ptr::null_mut();
let _ = dh_filter_add_topic_name(filter, topic.as_ptr(), &mut error);
if handle_client_error(error, "add topic name to filter") {
let () = dh_filter_destroy(filter);
let () = dh_subscriber_destroy(subscriber);
return None;
}
}

let options = dh_subscribe_options_create();
if options.is_null() {
error!("Failed to create subscription options");
let () = dh_filter_destroy(filter);
let () = dh_subscriber_destroy(subscriber);
return None;
}

let mut error: *mut DHError = ptr::null_mut();
let _ = dh_subscribe_options_add_filter(options, filter, &mut error);
let () = dh_filter_destroy(filter);
if handle_client_error(error, "add filter to options") {
let () = dh_subscribe_options_destroy(options);
let () = dh_subscriber_destroy(subscriber);
return None;
}

let () = dh_subscribe_options_set_enable_data_updates(options, true);

let mut error: *mut DHError = ptr::null_mut();
let _ = dh_subscriber_subscribe(subscriber, options, &mut error);
let () = dh_subscribe_options_destroy(options);
if handle_client_error(error, "subscribe to topic") {
let () = dh_subscriber_destroy(subscriber);
return None;
}

Some(subscriber)
}

/// Destroys `subscriber`, if any, then disconnects and destroys `client`.
unsafe fn cleanup_resources(client: *mut DHClient, subscriber: *mut DHSubscriber) {
debug_assert!(!client.is_null());
if !subscriber.is_null() {
let () = dh_subscriber_destroy(subscriber);
}

let mut error: *mut DHError = ptr::null_mut();
let _ = dh_client_disconnect(client, &mut error);
let _ = handle_client_error(error, "client disconnect");
let () = dh_client_destroy(client);
}

extern "C" fn signal_handler(sig: c_int) {
if sig == SIGINT || sig == SIGTERM {
KEEP_RUNNING.store(false, Ordering::Relaxed);
}
}

fn main() -> ExitCode {
acap_logging::init_logger();
info!("Application started");

unsafe {
let _ = libc::signal(SIGINT, signal_handler as libc::sighandler_t);
let _ = libc::signal(SIGTERM, signal_handler as libc::sighandler_t);
}

let topics = [TOPIC_NAME];

let Some(client) = (unsafe { initialize_client() }) else {
return ExitCode::FAILURE;
};
let Some(subscriber) = (unsafe { setup_subscription(client, &topics) }) else {
let () = unsafe { cleanup_resources(client, ptr::null_mut()) };
return ExitCode::FAILURE;
};

while KEEP_RUNNING.load(Ordering::Relaxed) {
let _ = unsafe { libc::pause() };
}

info!("Application terminated");
let () = unsafe { cleanup_resources(client, subscriber) };
ExitCode::SUCCESS
}
13 changes: 13 additions & 0 deletions crates/datahub-sys/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
build = "build.rs"
name = "datahub-sys"
version = "0.0.0"
edition.workspace = true
license = "MIT"

[build-dependencies]
bindgen = { workspace = true }
pkg-config = { workspace = true }

[dependencies]
libc = { workspace = true }
29 changes: 29 additions & 0 deletions crates/datahub-sys/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use std::{env, path};

fn populated_bindings(dst: &path::PathBuf) {
let library = pkg_config::Config::new()
.probe("device-data-hub-client-c")
.unwrap();
let mut bindings = bindgen::Builder::default()
.header("wrapper.h")
.generate_comments(false)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.allowlist_function("^(dh_.*)$")
.allowlist_type("^(DH.*)$")
.allowlist_var("^(DH_.*)$")
.allowlist_recursively(false)
.layout_tests(false);
for path in library.include_paths {
bindings = bindings.clang_args(&["-I", path.to_str().unwrap()]);
}
bindings.generate().unwrap().write_to_file(dst).unwrap();
}

fn main() {
let dst = path::PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
if env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default() != "x86_64"
&& env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() != "macos"
{
populated_bindings(&dst);
}
}
Loading
Loading