From 924e73e5af0d557b9e82f106fcd3e2a189c81598 Mon Sep 17 00:00:00 2001 From: Artem Shein Date: Mon, 9 Feb 2026 12:03:22 +0100 Subject: [PATCH] mini-adas: mw com api support Change-Id: I94d1b476ab7aeeb74f04ccff24561d68529551b2 --- .bazelrc | 6 + MODULE.bazel | 41 ++ examples/rust/mini-adas/BUILD.bazel | 64 +-- examples/rust/mini-adas/build.rs | 13 +- examples/rust/mini-adas/etc/logging.json | 8 + .../rust/mini-adas/etc/mw_com_config.json | 261 +++++++++++ .../rust/mini-adas/mini-adas-gen/BUILD.bazel | 41 ++ .../mini-adas/mini-adas-gen/mini_adas_gen.cpp | 41 ++ .../mini-adas/mini-adas-gen/mini_adas_gen.h | 139 ++++++ .../mini-adas/mini-adas-gen/mini_adas_gen.rs | 438 ++++++++++++++++++ .../mini-adas/src/activities/components.rs | 368 +++++++++++---- .../rust/mini-adas/src/activities/messages.rs | 126 ----- examples/rust/mini-adas/src/activities/mod.rs | 1 - .../mini-adas/src/bin/adas_deserializer.rs | 89 ---- .../rust/mini-adas/src/bin/adas_primary.rs | 13 +- .../rust/mini-adas/src/bin/adas_recorder.rs | 179 ------- .../rust/mini-adas/src/bin/adas_secondary.rs | 33 +- examples/rust/mini-adas/src/config.rs | 31 +- examples/rust/mini-adas/src/ffi.rs | 20 - examples/rust/mini-adas/src/lib.rs | 1 - src/feo/src/agent/direct/primary.rs | 10 +- src/feo/src/agent/direct/primary_mpsc.rs | 10 +- src/feo/src/agent/mod.rs | 17 + src/feo/src/agent/relayed/primary.rs | 10 +- src/feo/src/scheduler.rs | 4 +- 25 files changed, 1316 insertions(+), 648 deletions(-) create mode 100644 examples/rust/mini-adas/etc/logging.json create mode 100644 examples/rust/mini-adas/etc/mw_com_config.json create mode 100644 examples/rust/mini-adas/mini-adas-gen/BUILD.bazel create mode 100644 examples/rust/mini-adas/mini-adas-gen/mini_adas_gen.cpp create mode 100644 examples/rust/mini-adas/mini-adas-gen/mini_adas_gen.h create mode 100644 examples/rust/mini-adas/mini-adas-gen/mini_adas_gen.rs delete mode 100644 examples/rust/mini-adas/src/activities/messages.rs delete mode 100644 examples/rust/mini-adas/src/bin/adas_deserializer.rs delete mode 100644 examples/rust/mini-adas/src/bin/adas_recorder.rs delete mode 100644 examples/rust/mini-adas/src/ffi.rs diff --git a/.bazelrc b/.bazelrc index 3f297f5..4d23464 100644 --- a/.bazelrc +++ b/.bazelrc @@ -39,3 +39,9 @@ build:lint --@aspect_rules_lint//lint:fail_on_violation=true build:lint-rust --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect build:lint-rust --output_groups=+clippy_checks build:lint-rust --@rules_rust//:clippy.toml=//:clippy.toml + +# Communication integration +common --@score_logging//score/mw/log/flags:KRemote_Logging=False +common --@score_baselibs//score/json:base_library=nlohmann +common --@score_baselibs//score/memory/shared/flags:use_typedshmd=False +common --@score_communication//score/mw/com/flags:tracing_library=@score_baselibs//score/analysis/tracing/generic_trace_library/stub_implementation diff --git a/MODULE.bazel b/MODULE.bazel index 061ec41..97fb963 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -35,6 +35,47 @@ git_override( remote = "https://github.com/eclipse-score/baselibs_rust.git", ) +# SCORE communication integration +bazel_dep(name = "score_communication", version = "0.1.2") +#bazel_dep(name = "rules_doxygen", version = "2.6.1", dev_dependency = True) + +git_override( + module_name = "score_communication", + commit = "dfc9b0f6fd2ae43cecdf11f4684ee2aa628faaf5", + remote = "https://github.com/eclipse-score/communication.git", +) + +bazel_dep(name = "score_baselibs", version = "0.2.2") +git_override( + module_name = "score_baselibs", + commit = "6951cd2a3045fb7a053acf203833a17704c22ccf", + remote = "https://github.com/eclipse-score/baselibs.git", +) + +## TRLC dependency for requirements traceability +bazel_dep(name = "trlc", version = "0.0.0", dev_dependency = True) +git_override( + module_name = "trlc", + commit = "a4f7e95210d8093ba208b91cbc5b681eae8d502b", # trlc-2.0.3 release + remote = "https://github.com/bmw-software-engineering/trlc.git", +) + +bazel_dep(name = "score_logging", version = "0.0.5") + +single_version_override( + module_name = "score_docs_as_code", + version = "3.0.0", +) + +bazel_dep(name = "rules_boost", repo_name = "com_github_nelhage_rules_boost") +archive_override( + module_name = "rules_boost", + strip_prefix = "rules_boost-master", + urls = ["https://github.com/nelhage/rules_boost/archive/refs/heads/master.tar.gz"], +) + +bazel_dep(name = "boost.program_options", version = "1.87.0") + # Rust rules for Bazel bazel_dep(name = "rules_rust", version = "0.68.1-score") bazel_dep(name = "rules_rust_prost", version = "0.67.0") diff --git a/examples/rust/mini-adas/BUILD.bazel b/examples/rust/mini-adas/BUILD.bazel index ecfd3ae..4758eda 100644 --- a/examples/rust/mini-adas/BUILD.bazel +++ b/examples/rust/mini-adas/BUILD.bazel @@ -17,10 +17,8 @@ rust_library( name = "libmini_adas_rust", srcs = [ "src/activities/components.rs", - "src/activities/messages.rs", "src/activities/mod.rs", "src/config.rs", - "src/ffi.rs", "src/lib.rs", ], crate_features = [ @@ -33,13 +31,14 @@ rust_library( ], visibility = ["//visibility:public"], deps = [ - ":cpp_activities", + "//examples/rust/mini-adas/mini-adas-gen:mini_adas_gen_rs", "//src/feo:libfeo_rust", "//src/feo-com:libfeo_com_rust", "//src/feo-cpp-build:libfeo_cpp_build_rust", "//src/feo-time:libfeo_time_rust", "//src/feo-tracing:libfeo_tracing_rust", "@score_baselibs_rust//src/log/score_log", + "@score_communication//score/mw/com/impl/rust/com-api/com-api", "@score_crates//:tracing", ], ) @@ -48,10 +47,8 @@ rust_library( name = "libmini_adas_recording_rust", srcs = [ "src/activities/components.rs", - "src/activities/messages.rs", "src/activities/mod.rs", "src/config.rs", - "src/ffi.rs", "src/lib.rs", ], crate_features = [ @@ -65,13 +62,14 @@ rust_library( ], visibility = ["//visibility:public"], deps = [ - ":cpp_activities", + "//examples/rust/mini-adas/mini-adas-gen:mini_adas_gen_rs", "//src/feo:libfeo_recording_rust", "//src/feo-com:libfeo_com_rust", "//src/feo-cpp-build:libfeo_cpp_build_rust", "//src/feo-time:libfeo_time_rust", "//src/feo-tracing:libfeo_tracing_rust", "@score_baselibs_rust//src/log/score_log", + "@score_communication//score/mw/com/impl/rust/com-api/com-api", "@score_crates//:postcard", "@score_crates//:serde", "@score_crates//:tracing", @@ -84,6 +82,13 @@ rust_binary( "src/bin/adas_primary.rs", ], crate_features = ["signalling_relayed_tcp"], + data = [ + "etc/logging.json", + "etc/mw_com_config.json", + ], + env = { + "MW_LOG_CONFIG_FILE": "examples/rust/mini-adas/etc/logging.json", + }, rustc_flags = [ "-Clink-arg=-lstdc++", "-Clink-arg=-lm", @@ -97,6 +102,7 @@ rust_binary( "//src/feo-tracing:libfeo_tracing_rust", "@score_baselibs_rust//src/log/score_log", "@score_baselibs_rust//src/log/stdout_logger", + "@score_communication//score/mw/com/impl/rust/com-api/com-api", ], ) @@ -106,6 +112,13 @@ rust_binary( "src/bin/adas_secondary.rs", ], crate_features = ["signalling_relayed_tcp"], + data = [ + "etc/logging.json", + "etc/mw_com_config.json", + ], + env = { + "MW_LOG_CONFIG_FILE": "examples/rust/mini-adas/etc/logging.json", + }, rustc_flags = [ "-Clink-arg=-lstdc++", "-Clink-arg=-lm", @@ -119,44 +132,7 @@ rust_binary( "//src/feo-tracing:libfeo_tracing_rust", "@score_baselibs_rust//src/log/score_log", "@score_baselibs_rust//src/log/stdout_logger", - ], -) - -rust_binary( - name = "adas_recorder", - srcs = [ - "src/bin/adas_recorder.rs", - ], - crate_features = [ - "recording", - "signalling_direct_tcp", - ], - visibility = ["//visibility:public"], - deps = [ - ":libmini_adas_recording_rust", - "//src/feo:libfeo_recording_rust", - "//src/feo-time:libfeo_time_rust", - "//src/feo-tracing:libfeo_tracing_rust", - "@score_baselibs_rust//src/log/score_log", - "@score_baselibs_rust//src/log/stdout_logger", - ], -) - -rust_binary( - name = "adas_deserializer", - srcs = [ - "src/bin/adas_deserializer.rs", - ], - crate_features = ["recording"], - visibility = ["//visibility:public"], - deps = [ - ":libmini_adas_recording_rust", - "//src/feo:libfeo_recording_rust", - "//src/feo-tracing:libfeo_tracing_rust", - "@score_baselibs_rust//src/log/score_log", - "@score_baselibs_rust//src/log/stdout_logger", - "@score_crates//:postcard", - "@score_crates//:serde", + "@score_communication//score/mw/com/impl/rust/com-api/com-api", ], ) diff --git a/examples/rust/mini-adas/build.rs b/examples/rust/mini-adas/build.rs index 13c773d..8b31257 100644 --- a/examples/rust/mini-adas/build.rs +++ b/examples/rust/mini-adas/build.rs @@ -17,15 +17,4 @@ use std::path::PathBuf; // Relative path to the feo repository root directory static PATH_TO_REPO_ROOT: &str = "../../../"; -fn main() { - let sources = ["src/cpp/lane_assist.cpp", "src/cpp/trajec_vis.cpp"]; - let header_dirs = ["src/include/"]; - - println!("cargo::rerun-if-changed=build.rs"); - - let local_dir: PathBuf = env::var("CARGO_MANIFEST_DIR").unwrap().into(); - let repo_root_dir = local_dir.join(PATH_TO_REPO_ROOT); - - // Build given components into the given library - feo_cpp_build::activity_lib(&sources, &header_dirs, "cpp_activities", repo_root_dir); -} +fn main() {} diff --git a/examples/rust/mini-adas/etc/logging.json b/examples/rust/mini-adas/etc/logging.json new file mode 100644 index 0000000..735f3ee --- /dev/null +++ b/examples/rust/mini-adas/etc/logging.json @@ -0,0 +1,8 @@ +{ + "ecuId": "Rust", + "appId": "miniadas", + "appDesc": "mini-adas", + "logLevel": "kDebug", + "logMode": "kFile", + "logFilePath": "/tmp" +} diff --git a/examples/rust/mini-adas/etc/mw_com_config.json b/examples/rust/mini-adas/etc/mw_com_config.json new file mode 100644 index 0000000..b521e9a --- /dev/null +++ b/examples/rust/mini-adas/etc/mw_com_config.json @@ -0,0 +1,261 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/feo/com/CameraInterface", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 6432, + "events": [ + { + "eventName": "image", + "eventId": 1 + } + ] + } + ] + }, + { + "serviceTypeName": "/feo/com/RadarInterface", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 6433, + "events": [ + { + "eventName": "scan", + "eventId": 2 + } + ] + } + ] + }, + { + "serviceTypeName": "/feo/com/NeuralNetInterface", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 6434, + "events": [ + { + "eventName": "scene", + "eventId": 3 + } + ] + } + ] + }, + { + "serviceTypeName": "/feo/com/BrakeControllerInterface", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 6435, + "events": [ + { + "eventName": "brake_instruction", + "eventId": 4 + } + ] + } + ] + }, + { + "serviceTypeName": "/feo/com/SteeringControllerInterface", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 6436, + "events": [ + { + "eventName": "steering", + "eventId": 5 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "/feo/com/MiniAdasCamera", + "serviceTypeName": "/feo/com/CameraInterface", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 1, + "allowedConsumer": { + "QM": [ + 0 + ] + }, + "allowedProvider": { + "QM": [ + 0 + ] + }, + "asil-level": "QM", + "binding": "SHM", + "events": [ + { + "eventName": "image", + "numberOfSampleSlots": 10, + "maxSubscribers": 3 + } + ] + } + ] + }, + { + "instanceSpecifier": "/feo/com/MiniAdasRadar", + "serviceTypeName": "/feo/com/RadarInterface", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 2, + "allowedConsumer": { + "QM": [ + 0 + ] + }, + "allowedProvider": { + "QM": [ + 0 + ] + }, + "asil-level": "QM", + "binding": "SHM", + "events": [ + { + "eventName": "scan", + "numberOfSampleSlots": 10, + "maxSubscribers": 3 + } + ] + } + ] + }, + { + "instanceSpecifier": "/feo/com/MiniAdasNeuralNet", + "serviceTypeName": "/feo/com/NeuralNetInterface", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 3, + "allowedConsumer": { + "QM": [ + 0 + ] + }, + "allowedProvider": { + "QM": [ + 0 + ] + }, + "asil-level": "QM", + "binding": "SHM", + "events": [ + { + "eventName": "scene", + "numberOfSampleSlots": 10, + "maxSubscribers": 3 + } + ] + } + ] + }, + { + "instanceSpecifier": "/feo/com/MiniAdasBrakeController", + "serviceTypeName": "/feo/com/BrakeControllerInterface", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 4, + "allowedConsumer": { + "QM": [ + 0 + ] + }, + "allowedProvider": { + "QM": [ + 0 + ] + }, + "asil-level": "QM", + "binding": "SHM", + "events": [ + { + "eventName": "brake_instruction", + "numberOfSampleSlots": 10, + "maxSubscribers": 3 + } + ] + } + ] + }, + { + "instanceSpecifier": "/feo/com/MiniAdasSteeringController", + "serviceTypeName": "/feo/com/SteeringControllerInterface", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 5, + "allowedConsumer": { + "QM": [ + 0 + ] + }, + "allowedProvider": { + "QM": [ + 0 + ] + }, + "asil-level": "QM", + "binding": "SHM", + "events": [ + { + "eventName": "steering", + "numberOfSampleSlots": 10, + "maxSubscribers": 3 + } + ] + } + ] + } + ] +} diff --git a/examples/rust/mini-adas/mini-adas-gen/BUILD.bazel b/examples/rust/mini-adas/mini-adas-gen/BUILD.bazel new file mode 100644 index 0000000..1715a9f --- /dev/null +++ b/examples/rust/mini-adas/mini-adas-gen/BUILD.bazel @@ -0,0 +1,41 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +load("@rules_rust//rust:defs.bzl", "rust_library") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +rust_library( + name = "mini_adas_gen_rs", + srcs = ["mini_adas_gen.rs"], + crate_name = "mini_adas_gen", + features = ["link_std_cpp_lib"], + visibility = [ + "//examples/rust/mini-adas:__subpackages__", + ], + deps = [ + ":mini_adas_gen_cpp", + "@score_baselibs_rust//src/log/score_log", + "@score_communication//score/mw/com/impl/rust/com-api/com-api", + "@score_crates//:libc", + ], +) + +cc_library( + name = "mini_adas_gen_cpp", + srcs = ["mini_adas_gen.cpp"], + hdrs = ["mini_adas_gen.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + "@score_communication//score/mw/com/impl/rust/com-api/com-api-ffi-lola:registry_bridge_macro_cpp", + ], + alwayslink = True, +) diff --git a/examples/rust/mini-adas/mini-adas-gen/mini_adas_gen.cpp b/examples/rust/mini-adas/mini-adas-gen/mini_adas_gen.cpp new file mode 100644 index 0000000..caf2d95 --- /dev/null +++ b/examples/rust/mini-adas/mini-adas-gen/mini_adas_gen.cpp @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "mini_adas_gen.h" +#include "score/mw/com/impl/rust/com-api/com-api-ffi-lola/registry_bridge_macro.h" + +BEGIN_EXPORT_MW_COM_INTERFACE(CameraInterface, ::score::feo::com::CameraProxy, ::score::feo::com::CameraSkeleton) +EXPORT_MW_COM_EVENT(::score::feo::com::CameraImage, image) +END_EXPORT_MW_COM_INTERFACE() + +EXPORT_MW_COM_TYPE(CameraImage, ::score::feo::com::CameraImage) + +BEGIN_EXPORT_MW_COM_INTERFACE(RadarInterface, ::score::feo::com::RadarProxy, ::score::feo::com::RadarSkeleton) +EXPORT_MW_COM_EVENT(::score::feo::com::RadarScan, scan) +END_EXPORT_MW_COM_INTERFACE() +EXPORT_MW_COM_TYPE(RadarScan, ::score::feo::com::RadarScan) + +BEGIN_EXPORT_MW_COM_INTERFACE(NeuralNetInterface, ::score::feo::com::NeuralNetProxy, ::score::feo::com::NeuralNetSkeleton) +EXPORT_MW_COM_EVENT(::score::feo::com::Scene, scene) +END_EXPORT_MW_COM_INTERFACE() +EXPORT_MW_COM_TYPE(Scene, ::score::feo::com::Scene) + +BEGIN_EXPORT_MW_COM_INTERFACE(BrakeControllerInterface, ::score::feo::com::BrakeControllerProxy, ::score::feo::com::BrakeControllerSkeleton) +EXPORT_MW_COM_EVENT(::score::feo::com::BrakeInstruction, brake_instruction) +END_EXPORT_MW_COM_INTERFACE() +EXPORT_MW_COM_TYPE(BrakeInstruction, ::score::feo::com::BrakeInstruction) + +BEGIN_EXPORT_MW_COM_INTERFACE(SteeringControllerInterface, ::score::feo::com::SteeringControllerProxy, ::score::feo::com::SteeringControllerSkeleton) +EXPORT_MW_COM_EVENT(::score::feo::com::Steering, steering) +END_EXPORT_MW_COM_INTERFACE() +EXPORT_MW_COM_TYPE(Steering, ::score::feo::com::Steering) diff --git a/examples/rust/mini-adas/mini-adas-gen/mini_adas_gen.h b/examples/rust/mini-adas/mini-adas-gen/mini_adas_gen.h new file mode 100644 index 0000000..81a602d --- /dev/null +++ b/examples/rust/mini-adas/mini-adas-gen/mini_adas_gen.h @@ -0,0 +1,139 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_MINI_ADAS_DATATYPE_H +#define SCORE_MINI_ADAS_DATATYPE_H + +#include "score/mw/com/types.h" + +namespace score::feo::com +{ + +struct CameraImage +{ + CameraImage() = default; + CameraImage(CameraImage&&) = default; + CameraImage(const CameraImage&) = default; + CameraImage& operator=(CameraImage&&) = default; + CameraImage& operator=(const CameraImage&) = default; + std::size_t num_people; + std::size_t num_cars; + std::double_t distance_obstacle; +}; + +template +class CameraInterface : public Trait::Base +{ + public: + using Trait::Base::Base; + typename Trait::template Event image{*this, "image"}; +}; + +using CameraProxy = mw::com::AsProxy; +using CameraSkeleton = mw::com::AsSkeleton; + +struct RadarScan +{ + RadarScan() = default; + RadarScan(RadarScan&&) = default; + RadarScan(const RadarScan&) = default; + RadarScan& operator=(RadarScan&&) = default; + RadarScan& operator=(const RadarScan&) = default; + + std::double_t distance_obstacle; + std::double_t error_margin; +}; + +template +class RadarInterface : public Trait::Base +{ + public: + using Trait::Base::Base; + typename Trait::template Event scan{*this, "scan"}; +}; + +using RadarProxy = mw::com::AsProxy; +using RadarSkeleton = mw::com::AsSkeleton; + +struct Scene +{ + Scene() = default; + Scene(Scene&&) = default; + Scene(const Scene&) = default; + Scene& operator=(Scene&&) = default; + Scene& operator=(const Scene&) = default; + + std::size_t num_people; + std::size_t num_cars; + std::double_t distance_obstacle; + std::double_t distance_left_lane; + std::double_t distance_right_lane; +}; + +template +class NeuralNetInterface : public Trait::Base +{ + public: + using Trait::Base::Base; + typename Trait::template Event scene{*this, "scene"}; +}; + +using NeuralNetProxy = mw::com::AsProxy; +using NeuralNetSkeleton = mw::com::AsSkeleton; + +struct BrakeInstruction +{ + BrakeInstruction() = default; + BrakeInstruction(BrakeInstruction&&) = default; + BrakeInstruction(const BrakeInstruction&) = default; + BrakeInstruction& operator=(BrakeInstruction&&) = default; + BrakeInstruction& operator=(const BrakeInstruction&) = default; + + bool active; + std::double_t level; +}; + +template +class BrakeControllerInterface : public Trait::Base +{ + public: + using Trait::Base::Base; + typename Trait::template Event brake_instruction{*this, "brake_instruction"}; +}; + +using BrakeControllerProxy = mw::com::AsProxy; +using BrakeControllerSkeleton = mw::com::AsSkeleton; + +struct Steering +{ + Steering() = default; + Steering(Steering&&) = default; + Steering(const Steering&) = default; + Steering& operator=(Steering&&) = default; + Steering& operator=(const Steering&) = default; + + std::double_t angle; +}; + +template +class SteeringControllerInterface : public Trait::Base +{ + public: + using Trait::Base::Base; + typename Trait::template Event steering{*this, "steering"}; +}; + +using SteeringControllerProxy = mw::com::AsProxy; +using SteeringControllerSkeleton = mw::com::AsSkeleton; + +} // namespace score::feo::com +#endif // SCORE_MINI_ADAS_DATATYPE_H diff --git a/examples/rust/mini-adas/mini-adas-gen/mini_adas_gen.rs b/examples/rust/mini-adas/mini-adas-gen/mini_adas_gen.rs new file mode 100644 index 0000000..a8cd474 --- /dev/null +++ b/examples/rust/mini-adas/mini-adas-gen/mini_adas_gen.rs @@ -0,0 +1,438 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +use com_api::{ + CommData, Consumer, Interface, OfferedProducer, Producer, ProviderInfo, Publisher, Reloc, Runtime, Subscriber, +}; +use score_log::ScoreDebug; + +/// Camera image +/// +/// A neural network could detect the number of people, +/// number of cars and the distance to the closest obstacle. +/// Given that we do not have a real neural network, +/// we already include information to be dummy inferred. +#[derive(Debug, Default, Reloc, ScoreDebug)] +#[repr(C)] +pub struct CameraImage { + pub num_people: libc::size_t, + pub num_cars: libc::size_t, + pub distance_obstacle: f64, +} + +impl CommData for CameraImage { + const ID: &'static str = "CameraImage"; +} + +pub struct CameraInterface; + +impl Interface for CameraInterface { + const INTERFACE_ID: &'static str = "CameraInterface"; + type Consumer = CameraConsumer; + type Producer = CameraProducer; +} + +pub struct CameraConsumer { + pub image: R::Subscriber, +} + +impl Consumer for CameraConsumer { + fn new(instance_info: R::ConsumerInfo) -> Self { + CameraConsumer { + image: R::Subscriber::new("image", instance_info.clone()).expect("Failed to create subscriber"), + } + } +} + +pub struct CameraProducer { + _runtime: core::marker::PhantomData, + instance_info: R::ProviderInfo, +} + +impl Producer for CameraProducer { + type Interface = CameraInterface; + type OfferedProducer = CameraOfferedProducer; + + fn offer(self) -> com_api::Result { + let offered_producer = CameraOfferedProducer { + image: R::Publisher::new("image", self.instance_info.clone()).expect("Failed to create publisher"), + instance_info: self.instance_info.clone(), + }; + // Offer the service instance to make it discoverable + // this is called after skeleton created using producer_builder API + self.instance_info.offer_service()?; + Ok(offered_producer) + } + + fn new(instance_info: R::ProviderInfo) -> com_api::Result { + Ok(CameraProducer { + _runtime: core::marker::PhantomData, + instance_info, + }) + } +} + +pub struct CameraOfferedProducer { + pub image: R::Publisher, + instance_info: R::ProviderInfo, +} + +impl OfferedProducer for CameraOfferedProducer { + type Interface = CameraInterface; + type Producer = CameraProducer; + + fn unoffer(self) -> com_api::Result { + let producer = CameraProducer { + _runtime: std::marker::PhantomData, + instance_info: self.instance_info.clone(), + }; + // Stop offering the service instance to withdraw it from system availability + self.instance_info.stop_offer_service()?; + Ok(producer) + } +} + +/// Radar scan +/// +/// With post-processing, we could detect the closest object +/// from a real radar scan. In this example, +/// the message type already carries the information to be dummy extracted. +#[derive(Debug, Default, Reloc, ScoreDebug)] +#[repr(C)] +pub struct RadarScan { + pub distance_obstacle: f64, + pub error_margin: f64, +} + +impl CommData for RadarScan { + const ID: &'static str = "RadarScan"; +} + +pub struct RadarInterface; + +impl Interface for RadarInterface { + const INTERFACE_ID: &'static str = "RadarInterface"; + type Consumer = RadarConsumer; + type Producer = RadarProducer; +} + +pub struct RadarConsumer { + pub scan: R::Subscriber, +} + +impl Consumer for RadarConsumer { + fn new(instance_info: R::ConsumerInfo) -> Self { + RadarConsumer { + scan: R::Subscriber::new("scan", instance_info.clone()).expect("Failed to create subscriber"), + } + } +} + +pub struct RadarProducer { + _runtime: core::marker::PhantomData, + instance_info: R::ProviderInfo, +} + +impl Producer for RadarProducer { + type Interface = RadarInterface; + type OfferedProducer = RadarOfferedProducer; + + fn offer(self) -> com_api::Result { + let offered_producer = RadarOfferedProducer { + scan: R::Publisher::new("scan", self.instance_info.clone()).expect("Failed to create publisher"), + instance_info: self.instance_info.clone(), + }; + // Offer the service instance to make it discoverable + // this is called after skeleton created using producer_builder API + self.instance_info.offer_service()?; + Ok(offered_producer) + } + + fn new(instance_info: R::ProviderInfo) -> com_api::Result { + Ok(RadarProducer { + _runtime: core::marker::PhantomData, + instance_info, + }) + } +} + +pub struct RadarOfferedProducer { + pub scan: R::Publisher, + instance_info: R::ProviderInfo, +} + +impl OfferedProducer for RadarOfferedProducer { + type Interface = RadarInterface; + type Producer = RadarProducer; + + fn unoffer(self) -> com_api::Result { + let producer = RadarProducer { + _runtime: std::marker::PhantomData, + instance_info: self.instance_info.clone(), + }; + // Stop offering the service instance to withdraw it from system availability + self.instance_info.stop_offer_service()?; + Ok(producer) + } +} + +/// Scene +/// +/// The scene is the result of fusing the camera image and the radar scan +/// with a neural network. In our example, we just extract the information. +#[derive(Debug, Default, Reloc, ScoreDebug)] +#[repr(C)] +pub struct Scene { + pub num_people: usize, + pub num_cars: usize, + pub distance_obstacle: f64, + pub distance_left_lane: f64, + pub distance_right_lane: f64, +} + +impl CommData for Scene { + const ID: &'static str = "Scene"; +} + +pub struct NeuralNetInterface; + +impl Interface for NeuralNetInterface { + const INTERFACE_ID: &'static str = "NeuralNetInterface"; + type Consumer = NeuralNetConsumer; + type Producer = NeuralNetProducer; +} + +pub struct NeuralNetConsumer { + pub scene: R::Subscriber, +} + +impl Consumer for NeuralNetConsumer { + fn new(instance_info: R::ConsumerInfo) -> Self { + NeuralNetConsumer { + scene: R::Subscriber::new("scene", instance_info.clone()).expect("Failed to create subscriber"), + } + } +} + +pub struct NeuralNetProducer { + _runtime: core::marker::PhantomData, + instance_info: R::ProviderInfo, +} + +impl Producer for NeuralNetProducer { + type Interface = NeuralNetInterface; + type OfferedProducer = NeuralNetOfferedProducer; + + fn offer(self) -> com_api::Result { + let offered_producer = NeuralNetOfferedProducer { + scene: R::Publisher::new("scene", self.instance_info.clone()).expect("Failed to create publisher"), + instance_info: self.instance_info.clone(), + }; + // Offer the service instance to make it discoverable + // this is called after skeleton created using producer_builder API + self.instance_info.offer_service()?; + Ok(offered_producer) + } + + fn new(instance_info: R::ProviderInfo) -> com_api::Result { + Ok(NeuralNetProducer { + _runtime: core::marker::PhantomData, + instance_info, + }) + } +} + +pub struct NeuralNetOfferedProducer { + pub scene: R::Publisher, + instance_info: R::ProviderInfo, +} + +impl OfferedProducer for NeuralNetOfferedProducer { + type Interface = NeuralNetInterface; + type Producer = NeuralNetProducer; + + fn unoffer(self) -> com_api::Result { + let producer = NeuralNetProducer { + _runtime: std::marker::PhantomData, + instance_info: self.instance_info.clone(), + }; + // Stop offering the service instance to withdraw it from system availability + self.instance_info.stop_offer_service()?; + Ok(producer) + } +} + +/// Brake instruction +/// +/// This is an instruction whether to engage the brakes and at which level. +#[derive(Debug, Default, Reloc, ScoreDebug)] +#[repr(C)] +pub struct BrakeInstruction { + pub active: bool, + pub level: f64, +} + +impl CommData for BrakeInstruction { + const ID: &'static str = "BrakeInstruction"; +} + +pub struct BrakeControllerInterface; + +impl Interface for BrakeControllerInterface { + const INTERFACE_ID: &'static str = "BrakeControllerInterface"; + type Consumer = BrakeControllerConsumer; + type Producer = BrakeControllerProducer; +} + +pub struct BrakeControllerConsumer { + pub brake_instruction: R::Subscriber, +} + +impl Consumer for BrakeControllerConsumer { + fn new(instance_info: R::ConsumerInfo) -> Self { + BrakeControllerConsumer { + brake_instruction: R::Subscriber::new("brake_instruction", instance_info.clone()) + .expect("Failed to create subscriber"), + } + } +} + +pub struct BrakeControllerProducer { + _runtime: core::marker::PhantomData, + instance_info: R::ProviderInfo, +} + +impl Producer for BrakeControllerProducer { + type Interface = BrakeControllerInterface; + type OfferedProducer = BrakeControllerOfferedProducer; + + fn offer(self) -> com_api::Result { + let offered_producer = BrakeControllerOfferedProducer { + brake_instruction: R::Publisher::new("brake_instruction", self.instance_info.clone()) + .expect("Failed to create publisher"), + instance_info: self.instance_info.clone(), + }; + // Offer the service instance to make it discoverable + // this is called after skeleton created using producer_builder API + self.instance_info.offer_service()?; + Ok(offered_producer) + } + + fn new(instance_info: R::ProviderInfo) -> com_api::Result { + Ok(BrakeControllerProducer { + _runtime: core::marker::PhantomData, + instance_info, + }) + } +} + +pub struct BrakeControllerOfferedProducer { + pub brake_instruction: R::Publisher, + instance_info: R::ProviderInfo, +} + +impl OfferedProducer for BrakeControllerOfferedProducer { + type Interface = BrakeControllerInterface; + type Producer = BrakeControllerProducer; + + fn unoffer(self) -> com_api::Result { + let producer = BrakeControllerProducer { + _runtime: std::marker::PhantomData, + instance_info: self.instance_info.clone(), + }; + // Stop offering the service instance to withdraw it from system availability + self.instance_info.stop_offer_service()?; + Ok(producer) + } +} + +/// Steering +/// +/// This carries the angle of steering. +#[derive(Debug, Default, Reloc, ScoreDebug)] +#[repr(C)] +pub struct Steering { + pub angle: f64, +} + +impl CommData for Steering { + const ID: &'static str = "Steering"; +} + +pub struct SteeringControllerInterface; + +impl Interface for SteeringControllerInterface { + const INTERFACE_ID: &'static str = "SteeringControllerInterface"; + type Consumer = SteeringControllerConsumer; + type Producer = SteeringControllerProducer; +} + +pub struct SteeringControllerConsumer { + pub steering: R::Subscriber, +} + +impl Consumer for SteeringControllerConsumer { + fn new(instance_info: R::ConsumerInfo) -> Self { + SteeringControllerConsumer { + steering: R::Subscriber::new("steering", instance_info.clone()).expect("Failed to create subscriber"), + } + } +} + +pub struct SteeringControllerProducer { + _runtime: core::marker::PhantomData, + instance_info: R::ProviderInfo, +} + +impl Producer for SteeringControllerProducer { + type Interface = SteeringControllerInterface; + type OfferedProducer = SteeringControllerOfferedProducer; + + fn offer(self) -> com_api::Result { + let offered_producer = SteeringControllerOfferedProducer { + steering: R::Publisher::new("steering", self.instance_info.clone()).expect("Failed to create publisher"), + instance_info: self.instance_info.clone(), + }; + // Offer the service instance to make it discoverable + // this is called after skeleton created using producer_builder API + self.instance_info.offer_service()?; + Ok(offered_producer) + } + + fn new(instance_info: R::ProviderInfo) -> com_api::Result { + Ok(SteeringControllerProducer { + _runtime: core::marker::PhantomData, + instance_info, + }) + } +} + +pub struct SteeringControllerOfferedProducer { + pub steering: R::Publisher, + instance_info: R::ProviderInfo, +} + +impl OfferedProducer for SteeringControllerOfferedProducer { + type Interface = SteeringControllerInterface; + type Producer = SteeringControllerProducer; + + fn unoffer(self) -> com_api::Result { + let producer = SteeringControllerProducer { + _runtime: std::marker::PhantomData, + instance_info: self.instance_info.clone(), + }; + // Stop offering the service instance to withdraw it from system availability + self.instance_info.stop_offer_service()?; + Ok(producer) + } +} diff --git a/examples/rust/mini-adas/src/activities/components.rs b/examples/rust/mini-adas/src/activities/components.rs index 175d378..394dcbe 100644 --- a/examples/rust/mini-adas/src/activities/components.rs +++ b/examples/rust/mini-adas/src/activities/components.rs @@ -11,8 +11,11 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -use crate::activities::messages::{BrakeInstruction, CameraImage, RadarScan, Scene, Steering}; -use core::fmt; +use crate::config::mw_com_runtime; +use com_api::{ + Builder, CommData, FindServiceSpecifier, InstanceSpecifier, Interface, LolaRuntimeImpl, Producer, Publisher, + Runtime, SampleContainer, SampleMaybeUninit, SampleMut, ServiceDiscovery, Subscriber, Subscription, +}; use core::hash::{BuildHasher as _, Hasher as _}; use core::mem::MaybeUninit; use core::ops::{Deref, DerefMut, Range}; @@ -20,18 +23,46 @@ use core::time::Duration; use feo::activity::Activity; use feo::error::ActivityError; use feo::ids::ActivityId; -use feo_com::interface::{ActivityInput, ActivityOutput}; -#[cfg(feature = "com_iox2")] -use feo_com::iox2::{Iox2Input, Iox2Output}; -#[cfg(feature = "com_linux_shm")] -use feo_com::linux_shm::{LinuxShmInput, LinuxShmOutput}; use feo_tracing::instrument; +use mini_adas_gen::{ + BrakeControllerConsumer, BrakeControllerInterface, CameraConsumer, CameraInterface, NeuralNetConsumer, + NeuralNetInterface, RadarConsumer, RadarInterface, SteeringControllerConsumer, SteeringControllerInterface, +}; +use mini_adas_gen::{BrakeInstruction, CameraImage, RadarScan, Scene, Steering}; use score_log::debug; -use score_log::fmt::ScoreDebug; use std::hash::RandomState; use std::thread; +use std::thread::sleep; + const SLEEP_RANGE: Range = 10..45; +pub struct DebugWrapper(pub T); + +impl core::fmt::Debug for DebugWrapper { + fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + Ok(()) + } +} + +impl core::ops::Deref for DebugWrapper { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for DebugWrapper { + fn deref_mut(&mut self) -> &mut ::Target { + &mut self.0 + } +} + +type MwComPublisher = ::Publisher; +type MwComSubscription = + <::Subscriber as Subscriber>::Subscription; +type MwComSample<'a, T> = as Subscription>::Sample<'a>; + /// Camera activity /// /// This activity emulates a camera generating a [CameraImage]. @@ -40,7 +71,7 @@ pub struct Camera { /// ID of the activity activity_id: ActivityId, /// Image output - output_image: Box>, + output_image: DebugWrapper>, // Local state for pseudo-random output generation num_people: usize, @@ -52,7 +83,7 @@ impl Camera { pub fn build(activity_id: ActivityId, image_topic: &str) -> Box { Box::new(Self { activity_id, - output_image: activity_output(image_topic), + output_image: DebugWrapper(create_producer::(image_topic).image), num_people: 4, num_cars: 10, distance_obstacle: 40.0, @@ -92,12 +123,9 @@ impl Activity for Camera { debug!("Stepping Camera"); sleep_random(); - if let Ok(camera) = self.output_image.write_uninit() { - let image = self.get_image(); - debug!("Sending image: {:?}", image); - let camera = camera.write_payload(image); - camera.send().unwrap(); - } + let image = self.get_image(); + debug!("Sending image: {:?}", image); + self.output_image.send(image).unwrap(); Ok(()) } @@ -116,7 +144,7 @@ pub struct Radar { /// ID of the activity activity_id: ActivityId, /// Radar scan output - output_scan: Box>, + output_scan: DebugWrapper<::Publisher>, // Local state for pseudo-random output generation distance_obstacle: f64, @@ -126,7 +154,7 @@ impl Radar { pub fn build(activity_id: ActivityId, radar_topic: &str) -> Box { Box::new(Self { activity_id, - output_scan: activity_output(radar_topic), + output_scan: DebugWrapper(create_producer::(radar_topic).scan), distance_obstacle: 40.0, }) } @@ -161,12 +189,9 @@ impl Activity for Radar { debug!("Stepping Radar"); sleep_random(); - if let Ok(radar) = self.output_scan.write_uninit() { - let scan = self.get_scan(); - debug!("Sending scan: {}", scan); - let radar = radar.write_payload(scan); - radar.send().unwrap(); - } + let scan = self.get_scan(); + debug!("Sending scan: {:?}", scan); + self.output_scan.send(scan).unwrap(); Ok(()) } @@ -177,6 +202,48 @@ impl Activity for Radar { } } +struct MwComInput { + count: usize, + /// Subscription + subscription: MwComSubscription, + /// Sample container + sample_container: SampleContainer>, +} + +impl core::fmt::Debug for MwComInput { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "MwComInput") + } +} + +impl MwComInput { + fn new = C>>( + topic: &str, + f: impl FnOnce(C) -> ::Subscriber, + ) -> Self { + Self { + count: 0, + subscription: f(create_consumer::(topic)).subscribe(1).unwrap(), + sample_container: SampleContainer::new(1), + } + } + + fn read(&mut self) -> MwComSample<'_, T> { + debug!("Starting to read {}", self.count); + self.count += 1; + assert_eq!( + 1, + self.subscription + .try_receive(&mut self.sample_container, 1,) + .expect("receive failed"), + "mw com read failed" + ); + let result = self.sample_container.pop_front().expect("pop_front failed"); + debug!("Reading done"); + result + } +} + /// Neural network activity /// /// This component emulates a neural network @@ -187,20 +254,21 @@ pub struct NeuralNet { /// ID of the activity activity_id: ActivityId, /// Image input - input_image: Box>, + input_image: MwComInput, /// Radar scan input - input_scan: Box>, + input_scan: MwComInput, /// Scene output - output_scene: Box>, + output_scene: DebugWrapper>, } impl NeuralNet { pub fn build(activity_id: ActivityId, image_topic: &str, scan_topic: &str, scene_topic: &str) -> Box { + let output_scene = DebugWrapper(create_producer::(scene_topic).scene); Box::new(Self { activity_id, - input_image: activity_input(image_topic), - input_scan: activity_input(scan_topic), - output_scene: activity_output(scene_topic), + input_image: MwComInput::new::<_, CameraInterface>(image_topic, |i: CameraConsumer<_>| i.image), + input_scan: MwComInput::new::<_, RadarInterface>(scan_topic, |r: RadarConsumer<_>| r.scan), + output_scene, }) } @@ -247,17 +315,15 @@ impl Activity for NeuralNet { let camera = self.input_image.read(); let radar = self.input_scan.read(); - let scene = self.output_scene.write_uninit(); - if let (Ok(camera), Ok(radar), Ok(mut scene)) = (camera, radar, scene) { - debug!("Inferring scene with neural network"); + debug!("Inferring scene with neural network"); - Self::infer(camera.deref(), radar.deref(), scene.deref_mut()); - // Safety: `Scene` has `repr(C)` and was fully initialized by `Self::infer` above. - let scene = unsafe { scene.assume_init() }; - debug!("Sending Scene {}", scene.deref()); - scene.send().unwrap(); - } + let mut scene = self.output_scene.allocate().unwrap(); + Self::infer(&camera, &radar, scene.as_mut()); + // Safety: `Scene` has `repr(C)` and was fully initialized by `Self::infer` above. + let scene = unsafe { scene.assume_init() }; + debug!("Sending Scene {:?}", scene.deref()); + scene.send().unwrap(); Ok(()) } @@ -279,17 +345,19 @@ pub struct EmergencyBraking { /// ID of the activity activity_id: ActivityId, /// Scene input - input_scene: Box>, + input_scene: MwComInput, /// Brake instruction output - output_brake_instruction: Box>, + output_brake_instruction: DebugWrapper<::Publisher>, } impl EmergencyBraking { pub fn build(activity_id: ActivityId, scene_topic: &str, brake_instruction_topic: &str) -> Box { + let output_brake_instruction = + DebugWrapper(create_producer::(brake_instruction_topic).brake_instruction); Box::new(Self { activity_id, - input_scene: activity_input(scene_topic), - output_brake_instruction: activity_output(brake_instruction_topic), + input_scene: MwComInput::new::<_, NeuralNetInterface>(scene_topic, |n: NeuralNetConsumer<_>| n.scene), + output_brake_instruction, }) } } @@ -310,28 +378,26 @@ impl Activity for EmergencyBraking { sleep_random(); let scene = self.input_scene.read(); - let brake_instruction = self.output_brake_instruction.write_uninit(); - - if let (Ok(scene), Ok(brake_instruction)) = (scene, brake_instruction) { - const ENGAGE_DISTANCE: f64 = 30.0; - const MAX_BRAKE_DISTANCE: f64 = 15.0; - - if scene.distance_obstacle < ENGAGE_DISTANCE { - // Map distances ENGAGE_DISTANCE..MAX_BRAKE_DISTANCE to intensities 0.0..1.0 - let level = f64::min( - 1.0, - (ENGAGE_DISTANCE - scene.distance_obstacle) / (ENGAGE_DISTANCE - MAX_BRAKE_DISTANCE), - ); - - let brake_instruction = brake_instruction.write_payload(BrakeInstruction { active: true, level }); - brake_instruction.send().unwrap(); - } else { - let brake_instruction = brake_instruction.write_payload(BrakeInstruction { - active: false, - level: 0.0, - }); - brake_instruction.send().unwrap(); - } + let brake_instruction = self.output_brake_instruction.allocate().unwrap(); + + const ENGAGE_DISTANCE: f64 = 30.0; + const MAX_BRAKE_DISTANCE: f64 = 15.0; + + if scene.distance_obstacle < ENGAGE_DISTANCE { + // Map distances ENGAGE_DISTANCE..MAX_BRAKE_DISTANCE to intensities 0.0..1.0 + let level = f64::min( + 1.0, + (ENGAGE_DISTANCE - scene.distance_obstacle) / (ENGAGE_DISTANCE - MAX_BRAKE_DISTANCE), + ); + + let brake_instruction = brake_instruction.write(BrakeInstruction { active: true, level }); + brake_instruction.send().unwrap(); + } else { + let brake_instruction = brake_instruction.write(BrakeInstruction { + active: false, + level: 0.0, + }); + brake_instruction.send().unwrap(); } Ok(()) } @@ -354,14 +420,17 @@ pub struct BrakeController { /// ID of the activity activity_id: ActivityId, /// Brake instruction input - input_brake_instruction: Box>, + input_brake_instruction: MwComInput, } impl BrakeController { pub fn build(activity_id: ActivityId, brake_instruction_topic: &str) -> Box { Box::new(Self { activity_id, - input_brake_instruction: activity_input(brake_instruction_topic), + input_brake_instruction: MwComInput::new::<_, BrakeControllerInterface>( + brake_instruction_topic, + |b: BrakeControllerConsumer<_>| b.brake_instruction, + ), }) } } @@ -381,13 +450,12 @@ impl Activity for BrakeController { debug!("Stepping BrakeController"); sleep_random(); - if let Ok(brake_instruction) = self.input_brake_instruction.read() { - if brake_instruction.active { - debug!( - "BrakeController activating brakes with level {:.3}", - brake_instruction.level - ) - } + let brake_instruction = self.input_brake_instruction.read(); + if brake_instruction.active { + debug!( + "BrakeController activating brakes with level {:.3}", + brake_instruction.level + ) } Ok(()) } @@ -409,14 +477,14 @@ pub struct EnvironmentRenderer { /// ID of the activity activity_id: ActivityId, /// Scene input - input_scene: Box>, + input_scene: MwComInput, } impl EnvironmentRenderer { pub fn build(activity_id: ActivityId, scene_topic: &str) -> Box { Box::new(Self { activity_id, - input_scene: activity_input(scene_topic), + input_scene: MwComInput::new::<_, NeuralNetInterface>(scene_topic, |n: NeuralNetConsumer<_>| n.scene), }) } } @@ -436,9 +504,8 @@ impl Activity for EnvironmentRenderer { debug!("Stepping EnvironmentRenderer"); sleep_random(); - if let Ok(_scene) = self.input_scene.read() { - debug!("Rendering scene"); - } + let _scene = self.input_scene.read(); + debug!("Rendering scene"); Ok(()) } @@ -460,14 +527,17 @@ pub struct SteeringController { /// ID of the activity activity_id: ActivityId, /// Steering input - input_steering: Box>, + input_steering: MwComInput, } impl SteeringController { pub fn build(activity_id: ActivityId, steering_topic: &str) -> Box { Box::new(Self { activity_id, - input_steering: activity_input(steering_topic), + input_steering: MwComInput::new::<_, SteeringControllerInterface>( + steering_topic, + |s: SteeringControllerConsumer<_>| s.steering, + ), }) } } @@ -487,9 +557,8 @@ impl Activity for SteeringController { debug!("Stepping SteeringController"); sleep_random(); - if let Ok(steering) = self.input_steering.read() { - debug!("SteeringController adjusting angle to {:.3}", steering.angle) - } + let steering = self.input_steering.read(); + debug!("SteeringController adjusting angle to {:.3}", steering.angle); Ok(()) } @@ -500,26 +569,84 @@ impl Activity for SteeringController { } } -/// Create an activity input. -fn activity_input(topic: &str) -> Box> -where - T: fmt::Debug + ScoreDebug + 'static, -{ - #[cfg(feature = "com_iox2")] - return Box::new(Iox2Input::new(topic)); - #[cfg(feature = "com_linux_shm")] - return Box::new(LinuxShmInput::new(topic)); +/// LaneAssist stub activity +/// +#[derive(Debug)] +pub struct LaneAssist { + /// ID of the activity + activity_id: ActivityId, + /// Brake instruction output + steering_controller: DebugWrapper<::Publisher>, +} + +impl LaneAssist { + pub fn build(activity_id: ActivityId, steering_topic: &str) -> Box { + Box::new(Self { + activity_id, + steering_controller: DebugWrapper(create_producer::(steering_topic).steering), + }) + } +} + +impl Activity for LaneAssist { + fn id(&self) -> ActivityId { + self.activity_id + } + + #[instrument(name = "LaneAssist startup")] + fn startup(&mut self) -> Result<(), ActivityError> { + Ok(()) + } + + #[instrument(name = "LaneAssist")] + fn step(&mut self) -> Result<(), ActivityError> { + debug!("Stepping LaneAssist"); + sleep_random(); + self.steering_controller.send(Steering { angle: 2.34 }).unwrap(); + Ok(()) + } + + #[instrument(name = "LaneAssist shutdown")] + fn shutdown(&mut self) -> Result<(), ActivityError> { + Ok(()) + } +} + +/// TrajectoryVisualizer stub activity +/// +#[derive(Debug)] +pub struct TrajectoryVisualizer { + /// ID of the activity + activity_id: ActivityId, +} + +impl TrajectoryVisualizer { + pub fn build(activity_id: ActivityId) -> Box { + Box::new(Self { activity_id }) + } } -/// Create an activity output. -fn activity_output(topic: &str) -> Box> -where - T: fmt::Debug + ScoreDebug + 'static, -{ - #[cfg(feature = "com_iox2")] - return Box::new(Iox2Output::new(topic)); - #[cfg(feature = "com_linux_shm")] - return Box::new(LinuxShmOutput::new(topic)); +impl Activity for TrajectoryVisualizer { + fn id(&self) -> ActivityId { + self.activity_id + } + + #[instrument(name = "TrajectoryVisualizer startup")] + fn startup(&mut self) -> Result<(), ActivityError> { + Ok(()) + } + + #[instrument(name = "TrajectoryVisualizer")] + fn step(&mut self) -> Result<(), ActivityError> { + debug!("Stepping TrajectoryVisualizer"); + sleep_random(); + Ok(()) + } + + #[instrument(name = "TrajectoryVisualizer shutdown")] + fn shutdown(&mut self) -> Result<(), ActivityError> { + Ok(()) + } } /// Generate a pseudo-random number in the specified range. @@ -561,3 +688,40 @@ fn random_walk_integer(previous: usize, change_prop: f64, max_delta: usize) -> u fn sleep_random() { thread::sleep(Duration::from_millis(gen_random_in_range(SLEEP_RANGE) as u64)); } + +fn create_producer( + topic: &str, +) -> <::Producer as Producer>::OfferedProducer { + debug!("Creating MW COM Producer for {}...", topic); + let service_id = InstanceSpecifier::new(topic).expect("Failed to create InstanceSpecifier"); + let producer_builder = mw_com_runtime().producer_builder::(service_id); + let producer = producer_builder.build().unwrap(); + producer.offer().expect("can't offer a producer") +} + +fn create_consumer(topic: &str) -> ::Consumer { + debug!("Creating MW COM Consumer for {}...", topic); + let mut tries = 0; + loop { + let service_id = InstanceSpecifier::new(topic).expect("Failed to create InstanceSpecifier"); + let consumer_discovery = mw_com_runtime().find_service::(FindServiceSpecifier::Specific(service_id)); + let available_service_instances = consumer_discovery.get_available_instances().unwrap(); + + if available_service_instances.is_empty() { + debug!("No producer yet available for {}, waiting...", topic); + sleep(Duration::from_millis(500)); + tries += 1; + if tries > 100 { + break; + } + continue; + } + + // Select service instance at specific handle_index + let handle_index = 0; // or any index you need from vector of instances + let consumer_builder = available_service_instances.into_iter().nth(handle_index).unwrap(); + + return consumer_builder.build().unwrap(); + } + panic!("can't create consumer for {topic}") +} diff --git a/examples/rust/mini-adas/src/activities/messages.rs b/examples/rust/mini-adas/src/activities/messages.rs deleted file mode 100644 index d96d848..0000000 --- a/examples/rust/mini-adas/src/activities/messages.rs +++ /dev/null @@ -1,126 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2025 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -//! Messages -//! -//! This module contains the definition of messages -//! to be used within this example. - -#[cfg(feature = "recording")] -use feo::{recording::registry::TypeRegistry, register_type, register_types}; -#[cfg(feature = "recording")] -use postcard::experimental::max_size::MaxSize; -#[cfg(feature = "recording")] -use score_log::fmt::ScoreDebug; -use score_log::ScoreDebug; -#[cfg(feature = "recording")] -use serde::{Deserialize, Serialize}; - -/// Camera image -/// -/// A neural network could detect the number of people, -/// number of cars and the distance to the closest obstacle. -/// Given that we do not have a real neural network, -/// we already include information to be dummy inferred. -#[cfg_attr(feature = "recording", derive(Serialize, Deserialize, MaxSize))] -#[derive(Debug, Default, ScoreDebug)] -#[repr(C)] -pub struct CameraImage { - pub num_people: usize, - pub num_cars: usize, - pub distance_obstacle: f64, -} - -/// Radar scan -/// -/// With post-processing, we could detect the closest object -/// from a real radar scan. In this example, -/// the message type already carries the information to be dummy extracted. -#[cfg_attr(feature = "recording", derive(Serialize, Deserialize, MaxSize))] -#[derive(Debug, Default, ScoreDebug)] -#[repr(C)] -pub struct RadarScan { - pub distance_obstacle: f64, - pub error_margin: f64, -} - -/// Scene -/// -/// The scene is the result of fusing the camera image and the radar scan -/// with a neural network. In our example, we just extract the information. -#[cfg_attr(feature = "recording", derive(Serialize, Deserialize, MaxSize))] -#[derive(Debug, Default, ScoreDebug)] -#[repr(C)] -pub struct Scene { - pub num_people: usize, - pub num_cars: usize, - pub distance_obstacle: f64, - pub distance_left_lane: f64, - pub distance_right_lane: f64, -} - -/// Brake instruction -/// -/// This is an instruction whether to engage the brakes and at which level. -#[cfg_attr(feature = "recording", derive(Serialize, Deserialize, MaxSize))] -#[derive(Debug, Default, ScoreDebug)] -#[repr(C)] -pub struct BrakeInstruction { - pub active: bool, - pub level: f64, -} - -/// Steering -/// -/// This carries the angle of steering. -#[cfg_attr(feature = "recording", derive(Serialize, Deserialize, MaxSize))] -#[derive(Debug, Default, ScoreDebug)] -#[repr(C)] -pub struct Steering { - pub angle: f64, -} - -/// Return a type registry containing the types defined in this file -#[cfg(feature = "recording")] -pub fn type_registry() -> TypeRegistry { - use core::fmt; - use feo_com::interface::ActivityInput; - - #[cfg(feature = "com_iox2")] - use feo_com::iox2::Iox2Input; - - #[cfg(feature = "com_linux_shm")] - use feo_com::linux_shm::LinuxShmInput; - - fn activity_input(topic: &str) -> Box> - where - T: fmt::Debug + ScoreDebug + 'static, - { - #[cfg(feature = "com_iox2")] - return Box::new(Iox2Input::new(topic)); - - #[cfg(feature = "com_linux_shm")] - Box::new(LinuxShmInput::new(topic)) - } - - let mut registry = TypeRegistry::default(); - register_types!( - registry; - CameraImage, |topic: &str| activity_input(topic); - RadarScan, |topic: &str| activity_input(topic); - Scene, |topic: &str| activity_input(topic); - BrakeInstruction, |topic: &str| activity_input(topic); - Steering, |topic: &str| activity_input(topic) - ); - registry -} diff --git a/examples/rust/mini-adas/src/activities/mod.rs b/examples/rust/mini-adas/src/activities/mod.rs index cd5d06b..6e2518a 100644 --- a/examples/rust/mini-adas/src/activities/mod.rs +++ b/examples/rust/mini-adas/src/activities/mod.rs @@ -12,4 +12,3 @@ ********************************************************************************/ pub mod components; -pub mod messages; diff --git a/examples/rust/mini-adas/src/bin/adas_deserializer.rs b/examples/rust/mini-adas/src/bin/adas_deserializer.rs deleted file mode 100644 index 17e5c8a..0000000 --- a/examples/rust/mini-adas/src/bin/adas_deserializer.rs +++ /dev/null @@ -1,89 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2025 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -use feo::recording::recorder::{DataDescriptionRecord, Record}; -use mini_adas::activities::messages; -use score_log::{info, LevelFilter}; -use serde::Deserialize; -use std::io::Read; -use stdout_logger::StdoutLoggerBuilder; - -fn main() { - StdoutLoggerBuilder::new() - .context("adas-deserializer") - .show_module(true) - .show_file(true) - .show_line(true) - .log_level(LevelFilter::Trace) - .set_as_default_logger(); - - let mut serialized_data = Vec::new(); - std::fs::File::open("rec.bin") - .expect("failed to open recording") - // Reading to end for now, just for this simple tool - .read_to_end(&mut serialized_data) - .expect("failed to read recording"); - - let serialized_data_len = serialized_data.len(); - info!("Read file with {} bytes", serialized_data_len); - let mut remaining_bytes = serialized_data.as_slice(); - while !remaining_bytes.is_empty() { - let (record, remaining) = postcard::take_from_bytes(remaining_bytes).expect("deserializing failed"); - remaining_bytes = remaining; - - println!("{record:#?}"); - if let Record::DataDescription(data_record) = record { - if let Some((image, remaining)) = - try_deserialization_as_a::(data_record, remaining_bytes) - { - remaining_bytes = remaining; - println!("{:#?}", image); - } else if let Some((radar, remaining)) = - try_deserialization_as_a::(data_record, remaining_bytes) - { - remaining_bytes = remaining; - println!("{:#?}", radar); - } else if let Some((scene, remaining)) = - try_deserialization_as_a::(data_record, remaining_bytes) - { - remaining_bytes = remaining; - println!("{:#?}", scene); - } else if let Some((brake, remaining)) = - try_deserialization_as_a::(data_record, remaining_bytes) - { - remaining_bytes = remaining; - println!("{:#?}", brake); - } else if let Some((steering, remaining)) = - try_deserialization_as_a::(data_record, remaining_bytes) - { - remaining_bytes = remaining; - println!("{:#?}", steering); - } else { - // Skip data record - info!("Skipping deserialization of {}", data_record.type_name); - remaining_bytes = &remaining_bytes[data_record.data_size..]; - } - } - } -} - -fn try_deserialization_as_a<'a, T: Deserialize<'a>>( - header: DataDescriptionRecord, - bytes: &'a [u8], -) -> Option<(T, &'a [u8])> { - if header.type_name == std::any::type_name::() { - Some(postcard::take_from_bytes(bytes).expect("failed to deserialize CameraImage")) - } else { - None - } -} diff --git a/examples/rust/mini-adas/src/bin/adas_primary.rs b/examples/rust/mini-adas/src/bin/adas_primary.rs index 3eef811..dcb6f36 100644 --- a/examples/rust/mini-adas/src/bin/adas_primary.rs +++ b/examples/rust/mini-adas/src/bin/adas_primary.rs @@ -11,10 +11,9 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -use feo::agent::com_init::initialize_com_primary; use feo::ids::AgentId; use feo_time::Duration; -use mini_adas::config::{agent_assignments_ids, topic_dependencies, COM_BACKEND, MAX_ADDITIONAL_SUBSCRIBERS}; +use mini_adas::config::mw_com_runtime; use score_log::{error, info, LevelFilter}; use std::collections::HashSet; use stdout_logger::StdoutLoggerBuilder; @@ -37,14 +36,8 @@ fn main() { let config = cfg::make_config(params); - // Initialize topics. Do not drop. - let _topic_guards = initialize_com_primary( - COM_BACKEND, - AGENT_ID, - topic_dependencies(), - &agent_assignments_ids(), - MAX_ADDITIONAL_SUBSCRIBERS, - ); + // Initialize MW COM + mw_com_runtime(); // Setup and run primary cfg::Primary::new(config) diff --git a/examples/rust/mini-adas/src/bin/adas_recorder.rs b/examples/rust/mini-adas/src/bin/adas_recorder.rs deleted file mode 100644 index 8cc6cc0..0000000 --- a/examples/rust/mini-adas/src/bin/adas_recorder.rs +++ /dev/null @@ -1,179 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2025 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -use feo::ids::AgentId; -use feo::recording::recorder::RecordingRules; -use mini_adas::activities::messages::{self, BrakeInstruction, CameraImage, RadarScan, Scene, Steering}; -use score_log::{debug, LevelFilter}; - -use feo::agent::com_init::initialize_com_recorder; -use feo::topicspec::TopicSpecification; -use mini_adas::config::{ - topic_dependencies, COM_BACKEND, TOPIC_CAMERA_FRONT, TOPIC_CONTROL_BRAKES, TOPIC_CONTROL_STEERING, - TOPIC_INFERRED_SCENE, TOPIC_RADAR_FRONT, -}; -use std::collections::HashMap; -use stdout_logger::StdoutLoggerBuilder; - -fn main() { - StdoutLoggerBuilder::new() - .context("adas-recorder") - .show_module(true) - .show_file(true) - .show_line(true) - .log_level(LevelFilter::Trace) - .set_as_default_logger(); - - let params = Params::from_args(); - - let registry = &messages::type_registry(); - let rules: RecordingRules = HashMap::from([ - (TOPIC_CAMERA_FRONT, core::any::type_name::()), - (TOPIC_CONTROL_BRAKES, core::any::type_name::()), - (TOPIC_CONTROL_STEERING, core::any::type_name::()), - (TOPIC_INFERRED_SCENE, core::any::type_name::()), - (TOPIC_RADAR_FRONT, core::any::type_name::()), - ]); - - let config = cfg::make_config(params.agent_id, rules.clone(), registry); - - // initialize reading based on topic specs corresponding to recording rules - let topic_specs: Vec = topic_dependencies() - .into_iter() - .filter(|s| rules.contains_key(s.topic)) - .collect(); - - // Initialize topics. Do not drop. - let _topic_guards = initialize_com_recorder(COM_BACKEND, topic_specs); - - debug!("Creating recorder with agent id {}", params.agent_id); - let mut recorder = cfg::Recorder::new(config); - - debug!("Starting to record"); - recorder.run() -} - -/// Parameters of the primary -struct Params { - /// Agent id of the recorder - agent_id: AgentId, -} - -impl Params { - fn from_args() -> Self { - let args: Vec = std::env::args().collect(); - - // First argument is the agent ID of the recorder - let agent_id = args - .get(1) - .and_then(|x| x.parse::().ok()) - .map(AgentId::new) - .expect("missing or invalid agent id"); - - Self { agent_id } - } -} - -#[cfg(feature = "signalling_direct_tcp")] -mod cfg { - use feo::agent::NodeAddress; - use feo::ids::AgentId; - use feo::recording::recorder::RecordingRules; - use feo::recording::registry::TypeRegistry; - use feo_time::Duration; - use mini_adas::config::BIND_ADDR; - - pub(super) use feo::agent::direct::recorder::{Recorder, RecorderConfig}; - - pub(super) fn make_config(agent_id: AgentId, rules: RecordingRules, registry: &TypeRegistry) -> RecorderConfig<'_> { - RecorderConfig { - id: agent_id, - record_file: "./rec.bin", - rules, - registry, - receive_timeout: Duration::from_secs(10), - endpoint: NodeAddress::Tcp(BIND_ADDR), - } - } -} - -#[cfg(feature = "signalling_direct_unix")] -mod cfg { - use feo::agent::NodeAddress; - use feo::ids::AgentId; - use feo::recording::recorder::RecordingRules; - use feo::recording::registry::TypeRegistry; - use feo_time::Duration; - use mini_adas::config::socket_paths; - - pub(super) use feo::agent::direct::recorder::{Recorder, RecorderConfig}; - - pub(super) fn make_config(agent_id: AgentId, rules: RecordingRules, registry: &TypeRegistry) -> RecorderConfig { - RecorderConfig { - id: agent_id, - record_file: "./rec.bin", - rules, - registry, - receive_timeout: Duration::from_secs(10), - endpoint: NodeAddress::UnixSocket(socket_paths().0), - } - } -} - -#[cfg(feature = "signalling_relayed_tcp")] -mod cfg { - use feo::agent::NodeAddress; - use feo::ids::AgentId; - use feo::recording::recorder::RecordingRules; - use feo::recording::registry::TypeRegistry; - use feo_time::Duration; - use mini_adas::config::{BIND_ADDR, BIND_ADDR2}; - - pub(super) use feo::agent::relayed::recorder::{Recorder, RecorderConfig}; - - pub(super) fn make_config(agent_id: AgentId, rules: RecordingRules, registry: &TypeRegistry) -> RecorderConfig { - RecorderConfig { - id: agent_id, - record_file: "./rec.bin", - rules, - registry, - receive_timeout: Duration::from_secs(10), - bind_address_senders: NodeAddress::Tcp(BIND_ADDR), - bind_address_receivers: NodeAddress::Tcp(BIND_ADDR2), - } - } -} - -#[cfg(feature = "signalling_relayed_unix")] -mod cfg { - use feo::agent::NodeAddress; - use feo::ids::AgentId; - use feo::recording::recorder::RecordingRules; - use feo::recording::registry::TypeRegistry; - use feo_time::Duration; - use mini_adas::config::socket_paths; - - pub(super) use feo::agent::relayed::recorder::{Recorder, RecorderConfig}; - - pub(super) fn make_config(agent_id: AgentId, rules: RecordingRules, registry: &TypeRegistry) -> RecorderConfig { - RecorderConfig { - id: agent_id, - record_file: "./rec.bin", - rules, - registry, - receive_timeout: Duration::from_secs(10), - bind_address_senders: NodeAddress::UnixSocket(socket_paths().0), - bind_address_receivers: NodeAddress::UnixSocket(socket_paths().1), - } - } -} diff --git a/examples/rust/mini-adas/src/bin/adas_secondary.rs b/examples/rust/mini-adas/src/bin/adas_secondary.rs index e5cf324..245c734 100644 --- a/examples/rust/mini-adas/src/bin/adas_secondary.rs +++ b/examples/rust/mini-adas/src/bin/adas_secondary.rs @@ -56,25 +56,18 @@ fn main() { .copied() .collect(); - // Initialize topics. Do not drop. - let _topic_guards = initialize_com_secondary(COM_BACKEND, topic_dependencies(), &local_activities); - let secondary = Secondary::new(config); secondary.run(); } #[cfg(feature = "signalling_relayed_tcp")] fn main() { - use feo::agent::com_init::initialize_com_secondary; use feo::agent::relayed::secondary::{Secondary, SecondaryConfig}; use feo::agent::NodeAddress; - use feo::ids::ActivityId; use feo_time::Duration; - use mini_adas::config::{agent_assignments, topic_dependencies}; - use mini_adas::config::{agent_assignments_ids, COM_BACKEND}; + use mini_adas::config::agent_assignments; use mini_adas::config::{BIND_ADDR, BIND_ADDR2}; use params::Params; - use std::collections::HashSet; init_logging(); @@ -91,18 +84,6 @@ fn main() { bind_address_receivers: NodeAddress::Tcp(BIND_ADDR2), }; - // determine set of activity ids belonging to this agent - let local_activities: HashSet = agent_assignments_ids() - .remove(¶ms.agent_id) - .unwrap() - .iter() - .flat_map(|(_, acts)| acts.iter()) - .copied() - .collect(); - - // Initialize topics. Do not drop. - let _topic_guards = initialize_com_secondary(COM_BACKEND, topic_dependencies(), &local_activities); - let secondary = Secondary::new(config); secondary.run(); } @@ -134,18 +115,6 @@ fn main() { bind_address_receivers: NodeAddress::UnixSocket(socket_paths().1), }; - // determine set of activity ids belonging to this agent - let local_activities: HashSet = agent_assignments_ids() - .remove(¶ms.agent_id) - .unwrap() - .iter() - .flat_map(|(_, acts)| acts.iter()) - .copied() - .collect(); - - // Initialize topics. Do not drop. - let _topic_guards = initialize_com_secondary(COM_BACKEND, topic_dependencies(), &local_activities); - let secondary = Secondary::new(config); secondary.run(); } diff --git a/examples/rust/mini-adas/src/config.rs b/examples/rust/mini-adas/src/config.rs index 33683b1..fe0e57a 100644 --- a/examples/rust/mini-adas/src/config.rs +++ b/examples/rust/mini-adas/src/config.rs @@ -12,17 +12,19 @@ ********************************************************************************/ use crate::activities::components::{ - BrakeController, Camera, EmergencyBraking, EnvironmentRenderer, NeuralNet, Radar, SteeringController, + BrakeController, Camera, EmergencyBraking, EnvironmentRenderer, LaneAssist, NeuralNet, Radar, SteeringController, + TrajectoryVisualizer, }; -use crate::activities::messages::{BrakeInstruction, CameraImage, RadarScan, Scene, Steering}; -use crate::ffi::{lane_assist, trajectory_visualizer}; +use com_api::{Builder, LolaRuntimeBuilderImpl, LolaRuntimeImpl, RuntimeBuilder}; use core::net::{IpAddr, Ipv4Addr, SocketAddr}; use feo::activity::{ActivityBuilder, ActivityIdAndBuilder}; use feo::ids::{ActivityId, AgentId, WorkerId}; use feo::topicspec::{Direction, TopicSpecification}; use feo_com::interface::ComBackend; +use mini_adas_gen::{BrakeInstruction, CameraImage, RadarScan, Scene, Steering}; use std::collections::HashMap; use std::path::{Path, PathBuf}; +use std::sync::LazyLock; pub type WorkerAssignment = (WorkerId, Vec<(ActivityId, Box)>); @@ -37,15 +39,24 @@ pub const COM_BACKEND: ComBackend = ComBackend::LinuxShm; pub const BIND_ADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8081); pub const BIND_ADDR2: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8082); -pub const TOPIC_INFERRED_SCENE: &str = "feo/com/vehicle/inferred/scene"; -pub const TOPIC_CONTROL_BRAKES: &str = "feo/com/vehicle/control/brakes"; -pub const TOPIC_CONTROL_STEERING: &str = "feo/com/vehicle/control/steering"; -pub const TOPIC_CAMERA_FRONT: &str = "feo/com/vehicle/camera/front"; -pub const TOPIC_RADAR_FRONT: &str = "feo/com/vehicle/radar/front"; +pub const TOPIC_INFERRED_SCENE: &str = "/feo/com/MiniAdasNeuralNet"; +pub const TOPIC_CONTROL_BRAKES: &str = "/feo/com/MiniAdasBrakeController"; +pub const TOPIC_CONTROL_STEERING: &str = "/feo/com/MiniAdasSteeringController"; +pub const TOPIC_CAMERA_FRONT: &str = "/feo/com/MiniAdasCamera"; +pub const TOPIC_RADAR_FRONT: &str = "/feo/com/MiniAdasRadar"; /// Allow up to two recorder processes (that potentially need to subscribe to every topic) pub const MAX_ADDITIONAL_SUBSCRIBERS: usize = 2; +pub fn mw_com_runtime() -> &'static LolaRuntimeImpl { + static RUNTIME: LazyLock = LazyLock::new(|| { + let mut lola_runtime_builder = LolaRuntimeBuilderImpl::new(); + lola_runtime_builder.load_config(&PathBuf::from("./examples/rust/mini-adas/etc/mw_com_config.json")); + lola_runtime_builder.build().unwrap() + }); + &RUNTIME +} + pub fn socket_paths() -> (PathBuf, PathBuf) { ( Path::new("/tmp/feo_listener1.socket").to_owned(), @@ -94,12 +105,12 @@ pub fn agent_assignments() -> HashMap) { + ctrlc::set_handler(move || { + if shutdown.load(Ordering::Relaxed) { + info!("Terminate triggered, exiting..."); + std::process::exit(1); + } else { + info!("Ctrl-C detected. Requesting graceful shutdown..."); + shutdown.store(true, core::sync::atomic::Ordering::Relaxed); + } + }) + .expect("Error setting Ctrl-C handler") +} diff --git a/src/feo/src/agent/relayed/primary.rs b/src/feo/src/agent/relayed/primary.rs index f526d5d..62b1bdc 100644 --- a/src/feo/src/agent/relayed/primary.rs +++ b/src/feo/src/agent/relayed/primary.rs @@ -14,6 +14,7 @@ //! Implementation of the primary agent for mixed signalling using sockets and mpsc channels use crate::activity::ActivityIdAndBuilder; +use crate::agent::register_sigterm_handler; use crate::agent::NodeAddress; use crate::error::Error; use crate::ids::{ActivityId, AgentId, WorkerId}; @@ -27,7 +28,7 @@ use alloc::sync::Arc; use alloc::vec::Vec; use core::sync::atomic::AtomicBool; use feo_time::Duration; -use score_log::{debug, info}; +use score_log::debug; use std::collections::HashMap; use std::thread::{self, JoinHandle}; @@ -144,12 +145,7 @@ impl Primary { // Create a shared flag to signal shutdown from an OS signal (e.g., Ctrl-C). let shutdown_requested = Arc::new(AtomicBool::new(false)); - let shutdown_clone = shutdown_requested.clone(); - ctrlc::set_handler(move || { - info!("Ctrl-C detected. Requesting graceful shutdown..."); - shutdown_clone.store(true, core::sync::atomic::Ordering::Relaxed); - }) - .expect("Error setting Ctrl-C handler"); + register_sigterm_handler(shutdown_requested.clone()); let scheduler = Scheduler::new( id, diff --git a/src/feo/src/scheduler.rs b/src/feo/src/scheduler.rs index 0fd4ffd..a80d8f8 100644 --- a/src/feo/src/scheduler.rs +++ b/src/feo/src/scheduler.rs @@ -24,6 +24,7 @@ use alloc::vec::Vec; use alloc::{boxed::Box, collections::BTreeSet}; use core::sync::atomic::{AtomicBool, Ordering}; use feo_time::Instant; +use score_log::ScoreDebug; use score_log::{debug, error, info, trace}; use std::collections::HashMap; use std::thread; @@ -163,7 +164,7 @@ impl Scheduler { // Wait until a new ready signal has been received. // If we receive an error (i.e., an ActivityFailed signal), proceed to graceful shutdown. if let Err(e) = self.wait_next_ready() { - error!("A failure occurred during step execution: {:?}", e); + error!("A failure occurred during step execution: {:?}, while waiting for activities ready signal: {:?}", e, &self.activity_states); self.shutdown_gracefully("A failure occurred during step execution."); return; } @@ -492,6 +493,7 @@ impl Scheduler { } /// Current state of an activity +#[derive(Debug, ScoreDebug)] struct ActivityState { /// Whether the activity has been triggered for an action triggered: bool,