iCub-main
Loading...
Searching...
No Matches
testServiceParserPOS.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Istituto Italiano di Tecnologia (IIT)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#include "serviceParser.h"
6#include "fakeEthResource.h"
7
8#include <yarp/os/Property.h>
9
10#include <cstddef>
11#include <cstring>
12#include <iostream>
13#include <stdexcept>
14#include <string>
15
16// Keep the host packet compatible with the shared firmware ABI, including fillers.
17static_assert(sizeof(eoas_pos_SensorConfig_t) == 4);
18static_assert(sizeof(eoas_pos_BoardConfig_t) == 24);
19static_assert(sizeof(eOmn_serv_config_data_as_pos_t) == 24);
20static_assert(sizeof(eOmn_serv_config_data_mc_mc4plusfaps_t) == 340);
21static_assert(sizeof(eOmn_serv_configuration_t) == 344);
22static_assert(sizeof(eOmn_serv_parameter_t) == 344);
23static_assert(sizeof(eOmn_service_cmmnds_command_t) == 348);
24static_assert(offsetof(eoas_pos_BoardConfig_t, sensors) == 8);
25static_assert(offsetof(eOmn_serv_config_data_mc_mc4plusfaps_t, pos) == 0);
26static_assert(offsetof(eOmn_serv_config_data_mc_mc4plusfaps_t, arrayofjomodescriptors) == 24);
27static_assert(offsetof(eOmn_serv_config_data_mc_mc4plusfaps_t, jomocoupling) == 48);
28static_assert(offsetof(eOmn_serv_configuration_t, data) == 4);
29static_assert(offsetof(eOmn_service_cmmnds_command_t, parameter) == 4);
30
31namespace {
32
33const std::string canboards = R"(
34 (CANBOARDS (type mtb4c)
35 (PROTOCOL (major 2) (minor 0))
36 (FIRMWARE (major 0) (minor 0) (build 0)))
37)";
38
39yarp::os::Property motionConfig(const std::string& pos,
40 const std::string& boards = canboards,
41 const std::string& type = "eomn_serv_MC_mc4plusfaps")
42{
43 yarp::os::Property config;
44 config.fromString("(SERVICE (type " + type + ") (PROPERTIES "
45 "(ETHBOARD (type mc4plus)) " + boards + " " + pos + R"(
46 (JOINTMAPPING
47 (ACTUATOR (type pwm pwm pwm pwm) (port CONN:P5 CONN:P4 CONN:P3 CONN:P2))
48 (ENCODER1 (type pos pos pos pos)
49 (port POS:hand_thumb_oc POS:hand_index_oc POS:hand_middle_oc POS:hand_ring_pinky_oc)
50 (position atjoint atjoint atjoint atjoint)
51 (resolution 65535 65535 65535 65535) (tolerance 5 5 5 5))
52 (ENCODER2 (type qenc qenc qenc qenc) (port CONN:P5 CONN:P4 CONN:P3 CONN:P2)
53 (position atmotor atmotor atmotor atmotor)
54 (resolution 1600 1600 1600 1600) (tolerance 0 0 0 0)))))
55 )");
56 return config;
57}
58
59void check(bool condition, const char* description)
60{
61 if (!condition) { throw std::runtime_error(description); }
62 std::cout << "PASS: " << description << '\n';
63}
64
65bool parse(ServiceParser& parser, const std::string& pos, servConfigMC_t& result)
66{
67 auto config = motionConfig(pos);
68 return parser.parseService(config, result);
69}
70
71} // namespace
72
73int main(int argc, char* argv[])
74{
75 try
76 {
77 ServiceParser parser;
78 servConfigMC_t omitted {};
79
80 // Poison POS data to detect fields left uninitialized by the parser.
81 auto& dependency = omitted.ethservice.configuration.data.mc.mc4plusfaps.pos;
82 std::memset(&dependency, 0xa5, sizeof(dependency));
83 check(parse(parser, "", omitted), "Entire MC POS group can be omitted");
84 const auto& omittedData = omitted.ethservice.configuration.data.mc.mc4plusfaps;
85 eOmn_serv_config_data_as_pos_t expected {};
86 expected.config.boardconfig[0].boardinfo.type = eobrd_cantype_none;
87 check(std::memcmp(&omittedData.pos, &expected, sizeof(expected)) == 0,
88 "Absent POS dependency is explicitly marked and otherwise zero-initialized");
89 check(parser.mc_service.properties.numofjoints == 4 &&
90 parser.getEncoderAtJoint(0)->resolution == 65535 &&
91 parser.getEncoderAtJoint(0)->tolerance == 5 &&
92 parser.getEncoderAtMotor(0)->resolution == 1600,
93 "Encoder placement, resolution and tolerance remain available");
94 // POS belongs to the standalone service, including an empty group.
95 servConfigMC_t ignored {};
96 for (const std::string pos : {"(POS)", "(POS (location CAN1:1))",
97 "(POS (location invalid))", "(POS (location CAN1:1 CAN1:2))",
98 "(POS (SENSORS (connector ignored)) (SETTINGS (acquisitionRate invalid)))"})
99 {
100 check(!parse(parser, pos, ignored), "MC POS group is rejected");
101 }
102 auto basicMC = motionConfig("", "", "eomn_serv_MC_mc4plus");
103 check(parser.parseService(basicMC, ignored), "Plain mc4plus service remains supported");
104 auto basicMCWithPOS = motionConfig("(POS)", "", "eomn_serv_MC_mc4plus");
105 check(!parser.parseService(basicMCWithPOS, ignored), "Plain mc4plus also rejects a POS group");
106 auto genericMCWithPOS = motionConfig("(POS)", "", "eomn_serv_MC_generic");
107 check(!parser.parseService(genericMCWithPOS, ignored), "Generic MC cannot bypass POS group rejection");
108 auto noBoards = motionConfig("", "");
109 check(!parser.parseService(noBoards, ignored), "MC still requires CANBOARDS when POS is omitted");
110 auto malformedBoards = motionConfig("", "(CANBOARDS (type mtb4c))");
111 check(!parser.parseService(malformedBoards, ignored), "MC still validates CANBOARDS protocol and firmware fields");
112 yarp::os::Property noMapping;
113 noMapping.fromString("(SERVICE (type eomn_serv_MC_mc4plusfaps) (PROPERTIES "
114 "(ETHBOARD (type mc4plus)) " + canboards + "))");
115 check(!parser.parseService(noMapping, ignored), "Omitting POS does not bypass mandatory JOINTMAPPING validation");
116 auto badEncoderText = motionConfig("").toString();
117 const auto port = badEncoderText.find("POS:hand_thumb_oc");
118 check(port != std::string::npos, "Encoder test fixture contains the expected POS port");
119 badEncoderText.replace(port, std::string("POS:hand_thumb_oc").size(), "POS:invalid");
120 yarp::os::Property badEncoder;
121 badEncoder.fromString(badEncoderText);
122 check(!parser.parseService(badEncoder, ignored), "Omitting POS does not bypass encoder port validation");
123
124 yarp::os::Property posConfig;
125 posConfig.fromString("(SERVICE (type eomn_serv_AS_pos) (PROPERTIES " + canboards + R"(
126 (SENSORS (id thumb index middle ring) (sensorName hand hand hand hand)
127 (type eoas_pos_angle eoas_pos_angle eoas_pos_angle eoas_pos_angle)
128 (location CAN1:1 CAN1:1 CAN1:1 CAN1:1)
129 (port POS:hand_thumb_oc POS:hand_index_oc POS:hand_middle_oc POS:hand_ring_pinky_oc)
130 (boardType mtb4c mtb4c mtb4c mtb4c)
131 (connector CONN:J3_SDA3 CONN:J3_SDA0 CONN:J3_SDA1 CONN:J3_SDA2)))
132 (SETTINGS (acquisitionRate 50) (enabledSensors thumb index middle ring)))
133 )");
134 servConfigPOS_t standalone {};
135 check(parser.parseService(posConfig, standalone), "Standalone POS still parses");
136 const auto& sensors = standalone.ethservice.configuration.data.as.pos.config.boardconfig[0].sensors;
137 check(standalone.acquisitionrate == 50 && standalone.idList.size() == 4 &&
138 sensors[0].connector == 3 && sensors[1].connector == 0 &&
139 sensors[2].connector == 1 && sensors[3].connector == 2,
140 "Standalone POS settings and physical connector mapping are preserved");
141
143 eOmn_serv_parameter_t prepared {};
144 check(!cache.prepare(eomn_serv_category_mc, &omitted.ethservice, prepared),
145 "MC dependency cannot be resolved before standalone POS activation");
146 cache.rememberActivated(eomn_serv_category_mc, &omitted.ethservice);
147 check(!cache.prepare(eomn_serv_category_mc, &omitted.ethservice, prepared),
148 "An MC request cannot masquerade as standalone POS activation");
149 cache.rememberActivated(eomn_serv_category_pos, &standalone.ethservice);
150 check(cache.prepare(eomn_serv_category_mc, &omitted.ethservice, prepared),
151 "Activated standalone POS resolves the MC dependency");
152 const auto& resolved = prepared.configuration.data.mc.mc4plusfaps;
153 check(std::memcmp(&resolved.pos, &standalone.ethservice.configuration.data.as.pos,
154 sizeof(resolved.pos)) == 0,
155 "Full POS board, location and sensor configuration is inherited");
156 auto expectedRequest = omitted.ethservice;
157 expectedRequest.configuration.data.mc.mc4plusfaps.pos = standalone.ethservice.configuration.data.as.pos;
158 check(std::memcmp(&prepared, &expectedRequest, sizeof(prepared)) == 0,
159 "Resolving POS preserves every other MC service field");
160 check(std::memcmp(&resolved.arrayofjomodescriptors, &omittedData.arrayofjomodescriptors,
161 sizeof(resolved.arrayofjomodescriptors)) == 0,
162 "Resolving POS preserves MC actuator and encoder descriptors");
163 check(omittedData.pos.config.boardconfig[0].boardinfo.type == eobrd_cantype_none,
164 "Resolving POS does not mutate the original parser output");
166 check(!otherBoard.prepare(eomn_serv_category_mc, &omitted.ethservice, prepared),
167 "POS configuration cannot leak into a different Ethernet resource");
168 auto embedded = omitted.ethservice;
169 embedded.configuration.data.mc.mc4plusfaps.pos.config.boardconfig[0].boardinfo.type = eobrd_cantype_mtb4c;
170 check(!otherBoard.prepare(eomn_serv_category_mc, &embedded, prepared),
171 "Prepopulated embedded POS cannot bypass standalone POS activation");
172 check(cache.prepare(eomn_serv_category_mc, &embedded, prepared) &&
173 std::memcmp(&prepared.configuration.data.mc.mc4plusfaps.pos,
174 &standalone.ethservice.configuration.data.as.pos, sizeof(resolved.pos)) == 0,
175 "Activated standalone POS always replaces embedded MC POS data");
176 auto alternative = standalone.ethservice;
177 alternative.configuration.data.as.pos.config.boardconfig[0].sensors[0].connector = 0;
178 cache.rememberActivated(eomn_serv_category_pos, &alternative);
179 check(cache.prepare(eomn_serv_category_mc, &omitted.ethservice, prepared) &&
180 prepared.configuration.data.mc.mc4plusfaps.pos.config.boardconfig[0].sensors[0].connector == 3,
181 "A second POS owner does not overwrite the first active mapping");
182 cache.clearOnStop(eomn_serv_category_mc);
183 check(cache.prepare(eomn_serv_category_mc, &omitted.ethservice, prepared),
184 "Stopping MC preserves standalone POS configuration");
185 cache.clearOnStop(eomn_serv_category_pos);
186 check(!cache.prepare(eomn_serv_category_mc, &omitted.ethservice, prepared),
187 "Stopping POS invalidates its cached dependency");
188 cache.rememberActivated(eomn_serv_category_pos, &alternative);
189 check(cache.prepare(eomn_serv_category_mc, &omitted.ethservice, prepared) &&
190 prepared.configuration.data.mc.mc4plusfaps.pos.config.boardconfig[0].sensors[0].connector == 0,
191 "POS can be activated again with a new configuration");
192 cache.clearOnStop(eomn_serv_category_all);
193 check(!cache.prepare(eomn_serv_category_mc, &omitted.ethservice, prepared),
194 "Stopping all services invalidates the POS configuration");
195 check(cache.prepare(eomn_serv_category_pos, &standalone.ethservice, prepared) &&
196 std::memcmp(&prepared, &standalone.ethservice, sizeof(prepared)) == 0,
197 "Standalone POS requests pass through unchanged");
198 check(cache.prepare(eomn_serv_category_mc, nullptr, prepared), "Null requests retain existing handling");
199
201 check(!fake.serviceVerifyActivate(eomn_serv_category_mc, &omitted.ethservice),
202 "Fake resource enforces POS-first activation as the real resource does");
203 check(fake.serviceVerifyActivate(eomn_serv_category_pos, &standalone.ethservice) &&
204 fake.serviceVerifyActivate(eomn_serv_category_mc, &omitted.ethservice),
205 "Fake resource accepts MC after standalone POS activation");
206 check(fake.serviceStop(eomn_serv_category_pos) &&
207 !fake.serviceVerifyActivate(eomn_serv_category_mc, &omitted.ethservice),
208 "Fake resource does not reuse a stopped POS service");
209 check(!fake.serviceVerifyActivate(eomn_serv_category_mc, &embedded),
210 "Fake resource rejects prepopulated MC POS without standalone activation");
211 }
212 catch (const std::exception& error)
213 {
214 std::cerr << "FAIL: " << error.what() << '\n';
215 return 1;
216 }
217 return 0;
218}
@ data
servMC_encoder_t * getEncoderAtMotor(int index)
servMCcollector_t mc_service
bool parseService(yarp::os::Searchable &config, servConfigMais_t &maisconfig)
servMC_encoder_t * getEncoderAtJoint(int index)
bool serviceStop(eOmn_serv_category_t category, double timeout=0.500)
bool serviceVerifyActivate(eOmn_serv_category_t category, const eOmn_serv_parameter_t *param, double timeout=0.500)
void clearOnStop(eOmn_serv_category_t category)
void rememberActivated(eOmn_serv_category_t category, const eOmn_serv_parameter_t *parameter)
bool prepare(eOmn_serv_category_t category, const eOmn_serv_parameter_t *requested, eOmn_serv_parameter_t &prepared) const
bool error
int main()
Definition main.cpp:67
eOmn_serv_parameter_t ethservice
std::vector< std::string > idList
eOmn_serv_parameter_t ethservice
servMCproperties_t properties