31#include <SimoxUtility/algorithm/string.h>
40 const std::string Whitespace =
" \t\n\r\f\v";
43 isBlank(
const std::string& text)
45 return text.find_first_not_of(Whitespace) == std::string::npos;
50 collapseWhitespace(
const std::string& text)
52 return simox::alg::join(simox::alg::split(text, Whitespace,
true,
true),
" ");
57 before(
const std::string& text,
const std::string& separator)
59 const std::size_t pos = text.find(separator);
60 return pos == std::string::npos ? text : text.substr(0, pos);
65 afterLast(
const std::string& text,
const std::string& separator)
67 const std::size_t pos = text.rfind(separator);
68 return pos == std::string::npos ? text : text.substr(pos + separator.size());
72 stringOr(
const nlohmann::json& value,
const std::string& fallback =
"")
74 return value.is_string() ?
value.get<std::string>() : fallback;
79 const std::set<std::string>&
80 openVocabularyDatasets()
82 static const std::set<std::string> datasets{
"grounding_sam",
"OpenVocabulary"};
102 std::vector<const char*> keys;
111 const std::vector<Subject>&
116 static const std::vector<Subject> subjects{
117 {
"text", {
"text"}, Kind::Quoted},
118 {
"question", {
"question"}, Kind::Quoted},
133 "basketDetectionQuery",
134 "familiarObjectDetectionQuery",
136 "discovery.objectClass"},
142 "handoverLocationName",
143 "placementLocationName",
144 "navigateToFridgeLocation",
145 "navigateToHandoverLocation",
146 "graspNextGarmentLocationName",
147 "placeGarmentInBasketLocationName",
148 "graspObjectAtLocation"},
150 {
"place", {
"commonPlace"}, Kind::Location},
151 {
"part", {
"objectToInteract",
"specificNode",
"objectFrame",
"node"}, Kind::Humanize},
162 flat(
const nlohmann::json& node)
166 nlohmann::json out = nlohmann::json::array();
167 for (
const auto& element : node)
169 out.push_back(
flat(element));
173 if (not node.is_object())
178 if (
const auto value = node.find(
"_ARON_VALUE"); value != node.end())
184 if (
const auto elements = node.find(
"_ARON_ELEMENTS"); elements != node.end())
186 if (elements->is_array())
188 nlohmann::json out = nlohmann::json::array();
189 for (
const auto& element : *elements)
191 out.push_back(
flat(element));
195 nlohmann::json out = nlohmann::json::object();
196 for (
auto it = elements->begin(); it != elements->end(); ++it)
198 out[it.key()] =
flat(it.value());
203 return nlohmann::json();
213 split.reserve(text.size() + 8);
214 for (std::size_t i = 0; i < text.size(); ++i)
216 const unsigned char c =
static_cast<unsigned char>(text[i]);
217 const unsigned char previous =
218 i > 0 ?
static_cast<unsigned char>(text[i - 1]) :
'\0';
219 if (std::isupper(
c) and (std::islower(previous) or std::isdigit(previous)))
222 split +=
static_cast<char>(std::tolower(
c));
231 std::string separated;
232 separated.reserve(
split.size());
233 bool inSeparator =
false;
234 for (
const char c :
split)
236 if (
c ==
'-' or
c ==
'_')
253 return simox::alg::trim_copy(separated);
259 return value.is_object() and value.contains(
"className") and value.contains(
"dataset") and
260 value.contains(
"instanceName");
263 const nlohmann::json*
264 lookup(
const nlohmann::json& parameters,
const std::string& key)
266 if (not parameters.is_object())
270 if (
const std::size_t
dot = key.find(
'.');
dot != std::string::npos)
272 const auto outer = parameters.find(key.substr(0,
dot));
273 return outer == parameters.end() ? nullptr :
lookup(*outer, key.substr(
dot + 1));
275 const auto value = parameters.find(key);
276 return value == parameters.end() ? nullptr : &(*value);
287 static const std::string reasonMarker =
"Reason:";
289 if (
const std::size_t reasonStart = message.find(reasonMarker);
290 reasonStart != std::string::npos)
294 const std::string reason = simox::alg::trim_copy(
295 before(message.substr(reasonStart + reasonMarker.size()),
"\nBacktrace"));
296 text = collapseWhitespace(afterLast(reason,
": "));
302 text = collapseWhitespace(before(before(message,
"The error was:"),
"\n"));
305 if (text.size() > 100)
307 const std::string head = text.substr(0, 100);
308 const std::size_t
space = head.rfind(
' ');
309 text = (
space == std::string::npos ? head : head.substr(0,
space)) +
"...";
318 escaped.reserve(text.size() + 2);
319 for (
const char c : text)
321 escaped += (
c ==
'"') ?
'\'' : (
c ==
'\n' ?
' ' :
c);
323 return "\"" + escaped +
"\"";
326 std::optional<std::string>
329 static const std::map<std::string, std::string> states{{
"Running",
"started"},
330 {
"Succeeded",
"done"},
331 {
"Failed",
"failed"},
332 {
"Aborted",
"aborted"}};
333 const auto state = states.find(
status);
334 return state == states.end() ? std::nullopt : std::optional{state->second};
340 return afterLast(afterLast(executorName,
"->"),
"/");
349 const std::time_t seconds =
static_cast<std::time_t
>(microSeconds / 1'000'000);
350 const int milliSeconds =
static_cast<int>((microSeconds % 1'000'000) / 1'000);
353 localtime_r(&seconds, &local);
356 if (std::strftime(buffer,
sizeof(buffer),
"%Y-%m-%dT%H:%M:%S", &local) == 0)
362 std::snprintf(result,
sizeof(result),
"%s.%03d", buffer, milliSeconds);
374 noiseSkills(
std::move(noiseSkills)), spokenNameLookup(
std::move(spokenNameLookup))
378 const std::set<std::string>&
384 static const std::set<std::string> noise{
388 "MoveJointsToPosition",
389 "MoveJointsToNamedConfiguration",
390 "MoveJointsWithVelocity",
391 "SetKneeHipPosition",
394 "MoveArmsToDefaultHighPosition",
395 "MoveArmsAboveBelowTable",
396 "SwitchCoordinationMode",
399 "SetCustomGazeTarget",
401 "LookAlongTrajectory",
408 "Placing::AnyObject::AtPose",
409 "grasping_failure_execute_encoder_calibration",
418 return simox::alg::join(std::vector<std::string>(noise.begin(), noise.end()),
",");
421 std::set<std::string>
424 const std::vector<std::string> names = simox::alg::split(commaSeparated,
",",
true,
true);
425 return std::set<std::string>(names.begin(), names.end());
429 SkillEventRenderer::objectName(
const nlohmann::json& objectID)
const
435 const std::string className = stringOr(objectID.at(
"className"));
436 if (className.empty())
441 const std::string dataset = stringOr(objectID.at(
"dataset"));
442 if (openVocabularyDatasets().count(dataset) > 0)
447 std::optional<std::string> spokenName;
448 if (spokenNameLookup)
450 spokenName = spokenNameLookup(dataset, className);
458 SkillEventRenderer::locationName(
const std::string& value)
const
460 const std::string text = before(value,
":");
461 const std::vector<std::string> parts = simox::alg::split(text,
"/",
false,
false);
462 if (parts.size() < 3)
467 const std::string spot =
468 simox::alg::join(std::vector<std::string>(parts.begin() + 2, parts.end()),
"/");
469 const nlohmann::json objectID = {
470 {
"dataset", parts[0]}, {
"className", parts[1]}, {
"instanceName",
""}};
474 std::vector<std::string>
475 SkillEventRenderer::subjectFields(
const nlohmann::json& parameters)
const
477 std::vector<std::string> fields;
478 for (
const Subject& subject : subjects())
480 for (
const char* key : subject.keys)
483 if (value ==
nullptr)
488 const std::string text = stringOr(*value);
489 std::string rendered;
490 switch (subject.kind)
493 if (
value->is_string() and not isBlank(text))
501 rendered = objectName(*value);
503 else if (
value->is_string() and not isBlank(text))
510 if (
value->is_string() and not isBlank(text))
512 rendered = locationName(text);
516 if (
value->is_string() and not isBlank(text))
523 if (not rendered.empty())
525 fields.push_back(std::string(subject.field) +
":" + rendered);
536 return noiseSkills.count(skillName) > 0;
539 std::optional<std::string>
551 std::vector<std::string> fields{
"skill:" +
event.skillName};
552 for (std::string& field : subjectFields(parameters))
554 fields.push_back(std::move(field));
558 fields.push_back(
"state:" + *state);
561 if (*state ==
"failed" or *state ==
"aborted")
563 const nlohmann::json* errorMessage =
564 result.is_object() ?
detail::lookup(result,
"errorMessage") :
nullptr;
565 const std::string reason =
567 if (not reason.empty())
569 fields.push_back(
"error:" + reason);
573 return simox::alg::join(fields,
", ");
Represents a point in time.
std::int64_t toMicroSecondsSinceEpoch() const
static std::string DefaultNoiseSkillsString()
DefaultNoiseSkills() as a comma-separated string (for property defaults).
bool isNoiseSkill(const std::string &skillName) const
Whether render() would drop this skill as noise.
static const std::set< std::string > & DefaultNoiseSkills()
Pure body/gaze control and primitives that only implement a skill already logged above them.
static std::set< std::string > ParseSkillList(const std::string &commaSeparated)
Split a comma-separated list into a set, trimming whitespace.
std::optional< std::string > render(const SkillEvent &event) const
Render one event.
std::string timestamp(const armarx::core::time::DateTime &time)
Local time as 2026-07-08T16:45:40.341 (milliseconds, truncated).
std::string errorText(const std::string &message)
Pull the human-readable reason out of an ArmarX error message.
nlohmann::json flat(const nlohmann::json &node)
Decode a self-describing Aron JSON tree into plain JSON.
const nlohmann::json * lookup(const nlohmann::json ¶meters, const std::string &key)
Fetch a parameter by name, supporting outer.inner nesting.
std::string quoted(const std::string &text)
Free text needs quoting: it may contain commas, which separate fields.
std::string calledBy(const std::string &executorName)
The last segment of the executor call stack, i.e. the calling skill.
bool isObjectID(const nlohmann::json &value)
Whether value is an ObjectID (has className, dataset and instanceName).
std::optional< std::string > stateOf(const std::string &status)
Map a SkillStatus name to the reported state, if it is reportable.
std::string humanize(const std::string &text)
serving-cart-at-lab -> serving cart at lab; laundryBasket -> laundry basket.
std::function< std::optional< std::string >( const std::string &dataset, const std::string &className)> SpokenNameLookup
Resolves an object class to its natural-language ("spoken") name.
std::vector< std::string > split(const std::string &source, const std::string &splitBy, bool trimElements=false, bool removeEmptyElements=false)
std::shared_ptr< Value > value()
double dot(const Point &x, const Point &y)
One skill lifecycle event, as stored in the Skill memory's SkillEvent core segment.
std::string status
One of the armarx::skills::SkillStatus names, e.g. "Running", "Succeeded".
nlohmann::json parameters
armarx::core::time::DateTime timestamp
std::string executorName
The call stack of the execution, e.g. "ServeDrinks->ServeDrinks::BringObjectFromCart".