|
|
Directory dependency graph for opossum_logger:Files | |
| ObjectSpokenNames.cpp | |
| ObjectSpokenNames.h | |
| OpossumLogger.cpp | |
| OpossumLogger.h | |
| SkillEventRenderer.cpp | |
| SkillEventRenderer.h | |
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:
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).
| 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:
SkillEventListener Ice topic. Subscription matching is prefix-based, so subscribing to the core segment Skill/SkillEvent catches every provider and skill below it.SkillEventSegment commits under entityName = skillName, so the callback can filter on the announced MemoryID alone. This matters: LookAt and ShapeHand fire far more often than everything else together, and one commit per status (7 of them per execution) means fetching them would dominate the callback.Reader, not MemoryNameSystem::resolveEntityInstances(). The latter calls getReader() per invocation, which re-resolves the MNS and ice_ping()s every proxy of every registered memory server — dozens of blocking round trips per skill event, on the Ice callback thread, from a machine that is off-robot. The callback then falls behind, IceStorm queues, and by the time a snapshot is queried the working memory (maxHistorySize 300 per entity) has evicted it — which is what MemoryNameSystem::resolveEntityInstances: ... No entity snapshot with name ... (with size 0) in the log means.executionStartedTimestamp — the latter is constant across an execution's lifecycle events and would collapse started and done onto one instant.Client neither ignores it nor passes MSG_NOSIGNAL, so a write to a socket the server already closed would otherwise kill the robot process).examples/post_mockup_logs_from_file.py in the opossum repository.std::function, which is what keeps it testable without PriorKnowledgeData or a memory server.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::C → B::C. |
state | Running → started, Succeeded → done, Failed → failed, Aborted → aborted. 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-drinks → on upper shelf of serving cart |
place | commonPlace | as location |
part | objectToInteract, specificNode, objectFrame, node | humanized (laundryBasket → laundry basket) |
Free text is quoted because it may itself contain commas, which separate fields.
Object names are resolved through ObjectFinder → ObjectInfo::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.
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. |
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.
The component lives on branch feature/opossum_logger of RobotAPI.
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:
Or configure it yourself, so this machine can reach the robot over Ice:
This rewrites Ice.Default.Locator in $ARMARX_USER_CONFIG_DIR/default.cfg, which is a global setting for the workspace: everything else you run from it afterwards also talks to the robot. Note the previous values if that machine is otherwise used for local work.
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.
Robot side — the Skill memory must be visible from here:
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:
Expect a 2xx. Otherwise:
opossum.Path.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.
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:
On startup the component logs exactly where it will send:
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.
Have the robot run any skill, and watch the file sink:
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.
Nothing to do. Expected behaviour when things go wrong:
OutputFile, and the robot is unaffected either way. When the server returns, ... is reachable again is logged once and posting resumes. Lines produced while it was down are not re-sent; replay them from OutputFile with examples/post_mockup_logs_from_file.py if the summary needs them.opossum.Path.QueueSize pending lines the oldest are dropped and a warning names the total.The opossum_euROBIN repository contains a summary server that serves POST /posted_logs:
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. |
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:
Two things make this diff lie if you skip them:
TZ=UTC on both sides. Timestamps are local time, and the test pins TZ to UTC internally.CMAKE_PREFIX_PATH must contain PriorKnowledgeData. ObjectFinder resolves the package through cmake --find-package, which reads that variable; outside a scenario run it is not set, every class falls back to its humanized name, and only the object names differ. A failed lookup is cached as a zero-byte file at /tmp/ArmarXCMakeCache_$USER/PriorKnowledgeData and reused, so delete that file before retrying or the fix appears not to work.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.