opossum_logger Directory Reference
+ Directory dependency graph for opossum_logger:

Files

 ObjectSpokenNames.cpp
 
 ObjectSpokenNames.h
 
 OpossumLogger.cpp
 
 OpossumLogger.h
 
 SkillEventRenderer.cpp
 
 SkillEventRenderer.h
 

Detailed Description

Streams ARMAR-7's skill events to the OPOSSUM summary server as scene-level log lines.

OPOSSUM is the shared multi-agent log of the euROBIN demo: every robot (Rollin' Justin / DLR, TIAGo / IST, ARMAR-7 / KIT) POSTs one line per noteworthy event to a common HTTP server, which sorts the lines by robot_name and feeds them to an LLM summariser. This component is KIT's producer side.

For every skill that starts, succeeds, fails or is aborted, it emits one line:

skill:LookAtObject, object:dining table, called_by:ServeDrinks::ApproachAndAskForDrink, state:started, time_stamp:2026-07-08T16:45:40.341
skill:NavigateTo, called_by:ServeDrinks::BringObjectFromCart, state:failed, time_stamp:2026-07-08T16:46:41.902, error:SkillError 501: The main method of skill 'NavigateTo' did fail.

and POSTs it as {"robot_name": "ARMAR-7 (KIT)", "message": "<the line>"}.

The format is the one of data/ARMAR7_serve_drinks_success.txt in the opossum_euROBIN repository. Its offline counterpart there, tools/gen_armar7_logs.py, produces the same lines from an ArMem LTM export; the two are kept byte-identical (see Offline equivalence).

How it works

Skill memory (Skill/SkillEvent)
│ memory subscription (all snapshots below the core segment)
onSkillEventUpdate() drop noise skills (by entity name) & duplicates →
│ queryMemoryIDs() → FromAron →
│ ARON parameters/result → nlohmann::json
SkillEventRenderer drop intermediate states & any noise left, render one line
│ (object classes → natural-language names)
bounded queue oldest dropped on overflow; the memory callback never blocks
sender thread append to OutputFile, then POST
File Role
OpossumLogger.{h,cpp} The component: properties, memory subscription, queue, sender thread, file sink.
SkillEventRenderer.{h,cpp} Pure conversion logic — no Ice, no memory, no filesystem. A port of gen_armar7_logs.py, function names kept in sync with it.
ObjectSpokenNames.{h,cpp} The production object-name lookup, injected into the renderer.
test/SkillEventRendererTest.cpp Unit tests + the export-equivalence harness.

Notes on the design worth knowing before changing it:

The line format

Fields are comma-separated, always in this order:

Field Content
skill The skill name, verbatim.
subject At most one of text, question, object, query, location, place, part — see below.
called_by Last segment of the executor call stack: A->B::CB::C.
state Runningstarted, Succeededdone, Failedfailed, Abortedaborted. Every other status (Constructing, Initializing, Preparing) is not reported.
time_stamp Local time, Y-m-dTH:M:S.mmm, milliseconds truncated.
error Only on failed/aborted, and only if the result carries an errorMessage. Reduced to its Reason: line, whitespace-collapsed, truncated at 100 characters on a word boundary.

The subject fields are picked from the skill's ARON parameters by name; the first key that matches wins, and each field appears at most once:

Field Parameter keys Rendering
text text quoted free text
question question quoted free text
object graspObjectName, object, objectID, objectInstanceID, objectToGrasp, cartObjectId, cartObjectID, object1, instance_id ObjectID → natural-language name
query objectName, objectText, basketDetectionQuery, familiarObjectDetectionQuery, objectClass, discovery.objectClass quoted free text
location location, locationName, graspLocationName, handoverLocationName, placementLocationName, navigateToFridgeLocation, navigateToHandoverLocation, graspNextGarmentLocationName, placeGarmentInBasketLocationName, graspObjectAtLocation Kitchen/serving-cart-at-lab/on-upper-shelf:with-drinkson upper shelf of serving cart
place commonPlace as location
part objectToInteract, specificNode, objectFrame, node humanized (laundryBasketlaundry basket)

Free text is quoted because it may itself contain commas, which separate fields.

Object names are resolved through ObjectFinderObjectInfo::loadSpokenNames(), i.e. from PriorKnowledgeData: the class sprite_bottle becomes bottle of sprite. Datasets grounding_sam and OpenVocabulary are open-vocabulary — their class name is the natural-language name and is passed through. If the class is unknown, the humanized class name is used, so a missing PriorKnowledgeData degrades the lines rather than breaking them. Lookups (including misses) are cached.

Noise filtering. opossum.SkillFilter drops skills that say nothing at scene level — pure body/gaze control (LookAt, HomePose, ShapeHand, MoveJointsToPosition, …) and primitives that only implement a skill already logged above them. The default list has 25 entries; see SkillEventRenderer::DefaultNoiseSkills(). Set the property to an empty string to report everything.

Skill renaming (GraspNamedObjectWithinReach → a human phrase) is deliberately not done here — the summariser gets the skill names as they are.

Properties

All properties are prefixed with ArmarX.OpossumLogger. (or ArmarX.<InstanceName>. if the component runs under a different instance name). OpossumLogger_run --print-options prints the authoritative list.

Property Default Meaning
opossum.Enabled true Master switch. false disables the subscription entirely.
opossum.RobotName ARMAR-7 (KIT) The robot_name field. The server groups the log by it.
opossum.Host 192.168.253.48 Summary server host.
opossum.Port 8001 Summary server port.
opossum.Path message POST path, without leading slash.
opossum.TimeoutMs 1000 Connect, read and write timeout of one POST.
opossum.QueueSize 1000 Pending lines; the oldest are dropped on overflow.
opossum.OutputFile /tmp/opossum_log.txt Also append every line here. Empty disables it.
opossum.SkillFilter the 25 noise skills Comma-separated skills not to report.
opossum.ResolveObjectNames true false skips PriorKnowledgeData and only humanizes class names.
mem.SkillMemoryName Skill Memory to read.
mem.CoreSegmentName SkillEvent Core segment to subscribe to.

Setting it up at the venue

Where it runs: not on the robot

The component is a passive observer — it subscribes to the Skill memory over Ice and POSTs to an HTTP server — so it can run on any machine that can reach both. It should not run on ARMAR-7:

So run it on the operator PC, or on a team laptop that is joined to the robot WiFi and can reach the server's network. That machine needs an ArmarX workspace with RobotAPI built, and ideally a PriorKnowledgeData checkout — without one the lines are still produced, objects just appear as humanized class names instead of natural-language ones.

Everything below happens on that machine. Do it before the demo slot; steps 2 and 3 are the ones that tend to surprise.

1. Build the component

The component lives on branch feature/opossum_logger of RobotAPI.

cd "$ARMARX_WORKSPACE/armarx/RobotAPI"
git checkout feature/opossum_logger
cmake --build build --target OpossumLogger OpossumLogger_run

2. Point this machine at the robot's IceGrid

The Skill memory runs on the robot, so this machine has to join the robot's ArmarX instance rather than start its own. Either way works:

Either way, mind --if — including on a machine that was already configured, if it was configured back home on a single network. It sets Ice.Default.Host, the interface this machine advertises to the rest of ArmarX. The memory server calls back into the logger to deliver new snapshots; on a dual-homed laptop that advertises its venue-network address, those callbacks never arrive — the component starts cleanly, logs its startup line, and then silently sees no skill events. If you get exactly that symptom, this is why.

Do not run armarx start on this machine — that would start a second, local IceGrid and the component would find no memory at all.

3. Check both networks

Robot side — the Skill memory must be visible from here:

armarx list objects | grep -i SkillMemory

If nothing shows up, this machine's IceGrid points somewhere other than the robot (step 2), or the robot's memory is not up yet.

Server side — POST a line by hand, from this machine:

curl -m 2 -i -X POST http://192.168.253.48:8001/message \
-H 'Content-Type: application/json' \
-d '{"robot_name": "ARMAR-7 (KIT)", "message": "connectivity check"}'

Expect a 2xx. Otherwise:

Confirm the host, port, path and the exact robot name with the operator while you are at it. The defaults were agreed beforehand, but the server belongs to a project partner and its address is the most likely thing to have changed on site.

4. Configure the scenario

RobotAPI/scenarios/Opossum/ is exactly this deployment: one application, OpossumLogger, and nothing else — no memory, no skill provider, since those run on the robot. Edit config/OpossumLogger.cfg if anything from step 3 differs from the defaults:

ArmarX.OpossumLogger.opossum.Host = 192.168.253.48
ArmarX.OpossumLogger.opossum.Port = 8001
ArmarX.OpossumLogger.opossum.Path = message
ArmarX.OpossumLogger.opossum.RobotName = ARMAR-7 (KIT)
ArmarX.OpossumLogger.opossum.OutputFile = /tmp/opossum_log.txt

5. Start it and read the one line that confirms it

armarx scenario start Opossum -p RobotAPI

On startup the component logs exactly where it will send:

Reporting skill events from 'Skill/SkillEvent' as 'ARMAR-7 (KIT)' to http://192.168.253.48:8001/message.

If instead it stays at ‘Waiting for memory 'Skill’ ...`, it cannot see the robot's memory — back to step 2. A missing memory blocks; it does not error.

6. Verify end to end

Have the robot run any skill, and watch the file sink:

tail -f /tmp/opossum_log.txt

Lines land here whether or not the server accepts them, which separates "we are receiving skill events from the robot" from "the server is receiving them". Confirm the second half with the operator. Check that each started line is followed by a done/failed one and that called_by shows the expected nesting.

7. During the demo

Nothing to do. Expected behaviour when things go wrong:

Testing it locally

The opossum_euROBIN repository contains a summary server that serves POST /posted_logs:

python3 -m opossum.summary_server --host=localhost --port=8000

RobotAPI/scenarios/OpossumLogger/ is preconfigured against it and starts MemoryNameSystem, SkillsMemory, SkillProviderExample, OpossumLogger and RemoteGuiProviderApp. See that scenario's README for the run instructions.

There are two scenarios, on purpose:

Scenario Purpose
RobotAPI/scenarios/Opossum Deployment. Just the logger, pointed at the robot's memory and the venue server. Runs on the operator PC / a team laptop.
RobotAPI/scenarios/OpossumLogger Local test. Self-contained: brings its own memory and example skill provider, posts to a local summary server. Needs no robot.

Unit tests

cmake --build "$ARMARX_WORKSPACE/armarx/RobotAPI/build" --target OpossumSkillEventRendererTest
"$ARMARX_WORKSPACE/armarx/RobotAPI/build/bin/OpossumSkillEventRendererTest"

Offline equivalence

gen_armar7_logs.py is the reference implementation; the renderer must match it byte for byte. The test binary renders a whole LTM export when pointed at one:

# The C++ renderer.
OPOSSUM_EXPORT_DIR=<export>/Skill/SkillEvent OPOSSUM_EXPORT_OUT=/tmp/cpp.txt \
CMAKE_PREFIX_PATH="$ARMARX_WORKSPACE/h2t/PriorKnowledgeData/build:$CMAKE_PREFIX_PATH" \
TZ=UTC "$ARMARX_WORKSPACE/armarx/RobotAPI/build/bin/OpossumSkillEventRendererTest"
# The Python generator, on the same export.
cd ~/gitlab_repos/opossum_euROBIN
TZ=UTC python3 tools/gen_armar7_logs.py <export>/Skill/SkillEvent /tmp/py.txt
diff /tmp/py.txt /tmp/cpp.txt

Two things make this diff lie if you skip them:

The names also only agree if both sides read the same PriorKnowledgeData checkout — the script has its path hardcoded, the component resolves it through the ArmarX package system.