36#include <RobotAPI/libraries/armem_skills/aron/Skill.aron.generated.h>
52 assistanceSessionTimeoutSeconds =
67 ensureMemoryConnection();
71 InterventionObserver::ensureMemoryConnection()
73 if (memoryConnected.load())
80 std::unique_lock lock(memoryConnectionMutex, std::try_to_lock);
81 if (!lock.owns_lock())
86 if (memoryConnected.load())
95 const auto memoryId = armem::MemoryID().withMemoryName(
"TaskOutcome");
105 const auto skillMemoryId = armem::MemoryID().withMemoryName(
"Skill");
108 memoryConnected.store(
true);
109 ARMARX_INFO <<
"InterventionObserver: Memory connection established.";
114 <<
"InterventionObserver: TaskOutcome memory not available yet. "
115 "Will retry on next event.";
118 return memoryConnected.load();
124 memoryConnected =
false;
149 const std::string& name,
150 const GamepadData&
data,
152 const Ice::Current&
c)
156 const bool backRisingEdge =
data.backButton && !prevBackButton;
157 prevBackButton =
data.backButton;
159 const bool startRisingEdge =
data.startButton && !prevStartButton;
160 prevStartButton =
data.startButton;
162 if (!ensureMemoryConnection())
170 ARMARX_INFO <<
"Back button pressed — committing SUSPENDED TaskOutcome";
172 const std::string suspendedSkillName = queryLatestSkillName();
173 ARMARX_INFO <<
"Skill suspended by gamepad back button: " << suspendedSkillName;
176 outcome.
taskName = suspendedSkillName;
179 outcome.
agent = agentName;
189 {
"source",
"gamepad"},
192 {
"leftStickX", std::to_string(
data.leftStickX)},
193 {
"leftStickY", std::to_string(
data.leftStickY)},
194 {
"rightStickX", std::to_string(
data.rightStickX)},
195 {
"rightStickY", std::to_string(
data.rightStickY)},
196 {
"dPadX", std::to_string(
data.dPadX)},
197 {
"dPadY", std::to_string(
data.dPadY)},
198 {
"leftTrigger", std::to_string(
data.leftTrigger)},
199 {
"rightTrigger", std::to_string(
data.rightTrigger)},
200 {
"leftButton", std::to_string(
data.leftButton)},
201 {
"rightButton", std::to_string(
data.rightButton)},
202 {
"backButton", std::to_string(
data.backButton)},
203 {
"startButton", std::to_string(
data.startButton)},
204 {
"xButton", std::to_string(
data.xButton)},
205 {
"yButton", std::to_string(
data.yButton)},
206 {
"aButton", std::to_string(
data.aButton)},
207 {
"bButton", std::to_string(
data.bButton)},
208 {
"theMiddleButton", std::to_string(
data.theMiddleButton)},
209 {
"leftStickButton", std::to_string(
data.leftStickButton)},
210 {
"rightStickButton", std::to_string(
data.rightStickButton)},
218 ARMARX_INFO <<
"Successfully committed SUSPENDED TaskOutcome for gamepad intervention.";
222 ARMARX_WARNING <<
"Failed to commit SUSPENDED TaskOutcome for gamepad intervention.";
229 ARMARX_INFO <<
"Start button pressed — checking for SUSPENDED entry to mark as recovered";
232 .providerName = providerName,
233 .taskTypeFilter = std::nullopt,
238 const auto result = reader.query(
q);
240 if (!result || result.outcomes.empty())
242 ARMARX_WARNING <<
"Start button pressed but no SUSPENDED TaskOutcome found in memory. Skipping recovery.";
248 for (
const auto& o : result.outcomes)
250 auto it = o.context.additional.find(
"source");
251 if (it == o.context.additional.end() || it->second !=
"gamepad")
263 ARMARX_WARNING <<
"Start button pressed but no gamepad-sourced SUSPENDED entry found. Skipping recovery.";
274 .recoveryMeasure =
"resume by gamepad triggered"};
281 ARMARX_INFO <<
"Successfully committed recovery for task '"
296 lastGamepadDevice = device;
297 lastGamepadName = name;
299 constexpr float deadzone = 0.1f;
301 std::set<std::string> activeActions;
303 if (std::abs(
data.leftTrigger) > deadzone || std::abs(
data.rightTrigger) > deadzone)
305 activeActions.insert(
"manual gripper control");
307 if (std::abs(
data.leftStickX) > deadzone || std::abs(
data.leftStickY) > deadzone)
309 activeActions.insert(
"manual platform navigation");
311 if (std::abs(
data.rightStickX) > deadzone || std::abs(
data.rightStickY) > deadzone)
313 activeActions.insert(
"rightStick");
315 if (std::abs(
data.dPadX) > deadzone || std::abs(
data.dPadY) > deadzone)
317 activeActions.insert(
"dPad");
321 activeActions.insert(
"manual action button");
325 activeActions.insert(
"manual action button");
329 activeActions.insert(
"manual action button");
333 activeActions.insert(
"manual action button");
337 activeActions.insert(
"manual bumper action");
339 if (
data.rightButton)
341 activeActions.insert(
"manual bumper action");
343 if (
data.theMiddleButton)
345 activeActions.insert(
"theMiddleButton");
347 if (
data.leftStickButton)
349 activeActions.insert(
"leftStickButton");
351 if (
data.rightStickButton)
353 activeActions.insert(
"rightStickButton");
357 const bool hasInput = !activeActions.empty();
361 if (!assistanceSessionActive)
364 assistanceSessionActive =
true;
365 assistanceSessionStart = now;
366 assistanceActionsInSession = activeActions;
372 assistanceActionsInSession.insert(activeActions.begin(), activeActions.end());
374 assistanceLastActivityTime = now;
376 else if (assistanceSessionActive)
378 const auto elapsed = now - assistanceLastActivityTime;
382 std::ostringstream actionStr;
384 for (
const auto& a : assistanceActionsInSession)
394 const auto durationMs =
395 (assistanceLastActivityTime - assistanceSessionStart).toMilliSeconds();
398 outcome.
taskName =
"GamepadIntervention";
401 outcome.
agent = agentName;
402 outcome.
startTime = assistanceSessionStart;
403 outcome.
endTime = assistanceLastActivityTime;
406 {
"source",
"gamepad_assistance"},
407 {
"actions", actionStr.str()},
408 {
"duration_ms", std::to_string(durationMs)},
409 {
"device", lastGamepadDevice},
410 {
"name", lastGamepadName},
418 ARMARX_INFO <<
"Committed assistance session: actions=[" << actionStr.str()
419 <<
"], duration=" << durationMs <<
"ms";
423 ARMARX_WARNING <<
"Failed to commit assistance session TaskOutcome.";
427 assistanceSessionActive =
false;
428 assistanceActionsInSession.clear();
434 InterventionObserver::queryLatestSkillName()
436 if (!skillMemoryReader)
456 const auto result = skillMemoryReader.
query(qb);
459 ARMARX_WARNING <<
"Failed to query Skill memory for latest skill event.";
472 std::string latestSkillName =
"unknown";
476 [&](
const armem::wm::Entity& entity)
481 skills::arondto::SkillStatusUpdate::FromAron(instance->data());
483 aronUpdate.executionStartedTimestamp > latestTime)
485 latestTime = aronUpdate.executionStartedTimestamp;
486 latestSkillName = aronUpdate.skillId.skillName;
491 return latestSkillName;
493 catch (
const std::exception& e)
495 ARMARX_WARNING <<
"Exception querying Skill memory: " << e.what();
506 if (!ensureMemoryConnection())
510 prevEmergencyStopState = state;
515 const bool becameActive =
516 (state == EmergencyStopState::eEmergencyStopActive &&
517 prevEmergencyStopState == EmergencyStopState::eEmergencyStopInactive);
518 const bool becameInactive =
519 (state == EmergencyStopState::eEmergencyStopInactive &&
520 prevEmergencyStopState == EmergencyStopState::eEmergencyStopActive);
521 prevEmergencyStopState = state;
525 ARMARX_INFO <<
"Emergency stop activated — committing SUSPENDED TaskOutcome";
527 const std::string suspendedSkillName = queryLatestSkillName();
528 ARMARX_INFO <<
"Skill suspended by emergency stop: " << suspendedSkillName;
531 outcome.
taskName = suspendedSkillName;
534 outcome.
agent = agentName;
544 {
"source",
"emergency_stop"},
545 {
"emergencyStopState",
"active"}};
552 ARMARX_INFO <<
"Successfully committed SUSPENDED TaskOutcome for emergency stop.";
556 ARMARX_WARNING <<
"Failed to commit SUSPENDED TaskOutcome for emergency stop.";
562 ARMARX_INFO <<
"Emergency stop released — checking for SUSPENDED entry to mark as recovered";
565 .providerName = providerName,
566 .taskTypeFilter = std::nullopt,
571 const auto result = reader.query(
q);
573 if (!result || result.outcomes.empty())
575 ARMARX_WARNING <<
"Emergency stop released but no SUSPENDED TaskOutcome found in memory. Skipping recovery.";
581 for (
const auto& o : result.outcomes)
583 auto it = o.context.additional.find(
"source");
584 if (it == o.context.additional.end() || it->second !=
"emergency_stop")
596 ARMARX_WARNING <<
"Emergency stop released but no emergency-stop-sourced SUSPENDED entry found. Skipping recovery.";
604 .recoveryMeasure =
"resume by emergency stop release"};
611 ARMARX_INFO <<
"Successfully committed recovery for emergency stop intervention.";
615 ARMARX_WARNING <<
"Failed to commit recovery TaskOutcome for emergency stop.";
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Property< PropertyType > getProperty(const std::string &name)
static Duration Hours(std::int64_t hours)
Constructs a duration in hours.
static Duration SecondsDouble(double seconds)
Constructs a duration in seconds.
Observes human interventions during autonomous robot execution and records them as TaskOutcome entrie...
void onInitComponent() override
void onDisconnectComponent() override
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
void reportEmergencyStopState(EmergencyStopState state, const Ice::Current &) override
void onConnectComponent() override
void reportGamepadState(const std::string &device, const std::string &name, const GamepadData &data, const TimestampBasePtr ×tamp, const Ice::Current &c) override
void onExitComponent() override
SpamFilterDataPtr deactivateSpam(float deactivationDurationSec=10.0f, const std::string &identifier="", bool deactivate=true) const
disables the logging for the current line for the given amount of seconds.
void usingTopic(const std::string &name, bool orderedPublishing=false)
Registers a proxy for subscription after initialization.
auto * findLatestInstance(int instanceIndex=0)
CoreSegmentT * findCoreSegment(const std::string &name)
Reader getReader(const MemoryID &memoryID)
Get a reader to the given memory name.
Reader useReader(const MemoryID &memoryID)
Use a memory server and get a reader for it.
Writer getWriter(const MemoryID &memoryID)
Get a writer to the given memory name.
QueryResult query(const QueryInput &input) const
Perform a query on the WM.
MemoryNameSystem & memoryNameSystem()
The query::Builder class provides a fluent-style specification of hierarchical queries.
CoreSegmentSelector & coreSegments()
Start specifying core segments.
CoreSegmentSelector & withName(const std::string &name) override
ProviderSegmentSelector & providerSegments()
Start specifying provider segments.
EntitySelector & all() override
SnapshotSelector & snapshots()
Start specifying entity snapshots.
EntitySelector & entities()
Start specifying entities.
ProviderSegmentSelector & all() override
SnapshotSelector & latest()
static DateTime Invalid()
#define ARMARX_INFO
The normal logging level.
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
This file offers overloads of toIce() and fromIce() functions for STL container types.
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
wm::Memory memory
The slice of the memory that matched the query.
InterruptionFailures interruption
std::map< std::string, std::string > additional
armarx::core::time::DateTime startTime
std::optional< RecoveryInfo > recoveryInfo
armarx::core::time::DateTime endTime
std::optional< bool > couldRecover
TaskOutcomeContext context
TaskOutcomeType outcomeType
std::optional< FailureInfo > failureInfo