GCC Code Coverage Report


Directory: ./
File: src/devices/EL6224.cpp
Date: 2026-09-23 16:22:15
Exec Total Coverage
Lines: 77 135 57.0%
Branches: 48 334 14.4%

Line Branch Exec Source
1 /*****************************************************************************
2 *
3 * This file is part of the ecapp library (EtherCAT application devices).
4 *
5 * Copyright (C) 2026 Florian Pose <fp@igh.de>
6 *
7 * The ecapp library is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public License
9 * as published by the Free Software Foundation, version 3 of the
10 * License.
11 *
12 * The ecapp library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with the ecapp library. If not, see
19 * <https://www.gnu.org/licenses/>.
20 *
21 ****************************************************************************/
22
23 #include "devices/EL6224.h"
24
25 #include "SlaveContext.h"
26 #include "ecapp/Domain.h"
27 #include "ecapp/Exceptions.h"
28 #include "ecapp/Factory.h"
29 #include "ecapp/IoLinkMaster.h"
30 #include "ecapp/Master.h"
31 #include "ecapp/SwappedSync.h"
32
33 #include <memory>
34 #include <sstream>
35 #include <stdexcept>
36
37 /****************************************************************************/
38
39 using std::make_unique;
40 using std::runtime_error;
41 using std::size_t;
42 using std::stringstream;
43 using std::unique_ptr;
44
45 namespace EcApp {
46
47 /* Master 0, Slave 17, "EL6224"
48 * Vendor ID: 0x00000002
49 * Product code: 0x18503052
50 * Revision number: 0x00150000
51 */
52
53 ec_pdo_entry_info_t el6224_pdo_entries[] = {
54 /* 1a80 */
55 {0xf100, 0x01, 8}, /* State Ch1 */
56 {0xf100, 0x02, 8}, /* State Ch2 */
57 {0xf100, 0x03, 8}, /* State Ch3 */
58 {0xf100, 0x04, 8}, /* State Ch4 */
59
60 /* 1a81 */
61 {0x0000, 0x00, 8}, /* Gap */
62 {0x0000, 0x00, 4}, /* Gap */
63 {0xf101, 0x0d, 1}, /* Device Diag */
64 {0x0000, 0x00, 2}, /* Gap */
65 {0xf101, 0x10, 1}, /* Device State */
66 };
67
68 ec_pdo_info_t el6224_pdos[] = {
69 {0x1a80, 4, el6224_pdo_entries + 0}, /* IO TxPDO-Map Inputs Ch.1 */
70 {0x1a81, 5, el6224_pdo_entries + 4}, /* IO TxPDO-Map Inputs Ch.1 */
71 };
72
73 ec_sync_info_t el6224_syncs[] = {
74 {0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
75 {1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
76 {2, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
77 {3, EC_DIR_INPUT, 2, el6224_pdos + 0, EC_WD_DISABLE},
78 {0xff}};
79
80 /****************************************************************************/
81
82 template <Mode mode>
83 1 EL6224<mode>::EL6224(
84 pdserv *pdserv,
85 pdtask *task,
86 const std::string &prefix,
87 Master *master,
88 uint16_t alias,
89 uint16_t position,
90 Domain *domainOut,
91 Domain *domainIn) :
92 sc(NULL),
93 domainOut(domainOut),
94 domainIn(domainIn),
95 serv(pdserv),
96 task(task),
97 prefix(prefix),
98 offState(-1),
99 state(NumPorts, 0x00),
100 stateOp(NumPorts, 0x00),
101
3/12
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
✓ Branch 13 taken 1 times.
✗ Branch 14 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 36 not taken.
✗ Branch 37 not taken.
1 devices(NumPorts)
102 {
103 // pdserv publishing (State/StateOp) happens in EL6224Adapter's
104 // constructor below, via SubDevice::registerInputChannel() -- that
105 // is also what makes those channels alias-able through
106 // SubDevice::setAlias().
107
108
1/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (!master) {
109 1 return;
110 }
111
112 ✗ sc = ecrt_master_slave_config(
113 master->getMaster(), alias, position, 0x00000002, 0x18503052);
114 ✗ if (!sc) {
115 ✗ stringstream err;
116 ✗ err << "Failed to get slave configuration.";
117 ✗ throw runtime_error(err.str());
118 }
119
120 ✗ if (ecrt_slave_config_pdos(
121 sc, EC_END, SwappedSync<mode>(el6224_syncs).getSync())) {
122 ✗ stringstream err;
123 ✗ err << "Failed to configure PDOs.";
124 ✗ throw runtime_error(err.str());
125 }
126 }
127
128 /****************************************************************************/
129
130 template <Mode mode>
131 1 void EL6224<mode>::setPortDevice(
132 unsigned int port,
133 std::unique_ptr<IoLinkDevice> device)
134 {
135 1 devices.at(port) = std::move(device);
136 1 }
137
138 /****************************************************************************/
139
140 template <Mode mode>
141 2 IoLinkDevice *EL6224<mode>::portDevice(unsigned int port)
142 {
143 2 return devices.at(port).get();
144 }
145
146 /****************************************************************************/
147
148 template <Mode mode>
149 ✗ const IoLinkDevice *EL6224<mode>::portDevice(unsigned int port) const
150 {
151 ✗ return devices.at(port).get();
152 }
153
154 /****************************************************************************/
155
156 template <Mode mode>
157 1 void EL6224<mode>::configure()
158 {
159
1/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (!sc) {
160 1 return;
161 }
162
163 ✗ uint16_t outputPdoIndex = 0x1600;
164 ✗ uint16_t inputPdoIndex = 0x1a00;
165 ✗ uint16_t outputPdoEntryIndex = 0x7000;
166 ✗ uint16_t inputPdoEntryIndex = 0x6000;
167
168 ✗ for (unsigned int i = 0; i < NumPorts; i++) {
169 ✗ const uint16_t sdoIndex(0x8000 + i * 0x0010);
170 ✗ if (devices[i]) {
171 ✗ devices[i]->addConfig(sc, sdoIndex);
172
173 ✗ if (devices[i]->addOutputs(
174 ✗ sc, outputPdoIndex, outputPdoEntryIndex, domainOut)) {
175 ✗ outputPdoIndex++;
176 ✗ outputPdoEntryIndex += 0x0010;
177 }
178 ✗ if (devices[i]->addInputs(
179 ✗ sc, inputPdoIndex, inputPdoEntryIndex, domainIn)) {
180 ✗ inputPdoIndex++;
181 ✗ inputPdoEntryIndex += 0x0010;
182 }
183 }
184 else {
185 ✗ const uint8_t data[] = {0x28, 0x00, 0x00, 0x00, 0x00, 0x00,
186 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
187 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
188 0x00, 0x00, 0x00, 0x00};
189 ✗ if (ecrt_slave_config_complete_sdo(sc, sdoIndex, data, 22)) {
190 ✗ stringstream err;
191 ✗ err << "Failed to configure SDO " << sdoIndex;
192 ✗ throw runtime_error(err.str());
193 }
194 }
195 }
196
197 ✗ offState = ecrt_slave_config_reg_pdo_entry(
198 ✗ sc, 0xf100, 0x01, domainIn->getDomain(), NULL);
199 ✗ if (offState < 0) {
200 ✗ stringstream err;
201 ✗ err << "Failed to register state input PDO entry.";
202 ✗ throw runtime_error(err.str());
203 }
204
205 ✗ for (unsigned int i = 0; i < NumPorts; i++) {
206 ✗ if (devices[i]) {
207 ✗ devices[i]->registerOutputs(sc, domainOut);
208 ✗ devices[i]->registerInputs(sc, domainIn);
209 }
210 }
211 }
212
213 /****************************************************************************/
214
215 template <Mode mode>
216 1 void EL6224<mode>::updateInputs()
217 {
218
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 if (domainIn->getData()) {
219 ✗ for (unsigned int i = 0; i < NumPorts; i++) {
220 ✗ state[i] = EC_READ_U8(domainIn->getData() + offState + i);
221 ✗ stateOp[i] = (state[i] & 0x0F) == 0x03;
222 }
223 }
224
225
2/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
5 for (unsigned int i = 0; i < NumPorts; i++) {
226
1/4
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
4 if (devices[i]) {
227 ✗ devices[i]->updateInputs();
228 }
229 }
230 1 }
231
232 /****************************************************************************/
233
234 template <Mode mode>
235 1 void EL6224<mode>::updateOutputs()
236 {
237
2/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
5 for (unsigned int i = 0; i < NumPorts; i++) {
238
1/4
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
4 if (devices[i]) {
239 ✗ devices[i]->updateOutputs();
240 }
241 }
242 1 }
243
244 /****************************************************************************/
245
246 template class EL6224<Mode::Control>;
247 template class EL6224<Mode::Simulation>;
248
249 /****************************************************************************/
250 // Generic SubDevice + IoLinkMaster adapter. Top-level channels are just
251 // the always-present per-port diagnostic bytes (from EtherCAT object
252 // 0xf100); a port's actual variables live on the SubDevice that
253 // port() returns once a driver has been assigned to it.
254 /****************************************************************************/
255
256 template <Mode mode>
257 2 class EL6224Adapter : public SubDevice, public IoLinkMaster
258 {
259 public:
260 1 explicit EL6224Adapter(const SlaveContext &ctx) :
261 SubDevice(
262 1 ctx.domainOut,
263 1 ctx.domainIn,
264 1 ctx.pdServ,
265 1 ctx.task,
266 ctx.prefix),
267 1 device(ctx.pdServ,
268 1 ctx.task,
269 ctx.prefix,
270 1 ctx.master,
271 1 ctx.alias,
272 1 ctx.position,
273 1 ctx.domainOut,
274 1 ctx.domainIn),
275 inputKinds_ {
276 {"PortState", EL6224<mode>::NumPorts, uint16_t(0)},
277
7/28
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✓ Branch 11 taken 1 times.
✗ Branch 12 not taken.
✓ Branch 15 taken 1 times.
✗ Branch 16 not taken.
✓ Branch 19 taken 1 times.
✗ Branch 20 not taken.
✓ Branch 22 taken 2 times.
✓ Branch 23 taken 1 times.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 43 not taken.
✗ Branch 44 not taken.
✗ Branch 48 not taken.
✗ Branch 49 not taken.
✗ Branch 52 not taken.
✗ Branch 53 not taken.
✗ Branch 56 not taken.
✗ Branch 57 not taken.
✗ Branch 60 not taken.
✗ Branch 61 not taken.
✗ Branch 63 not taken.
✗ Branch 64 not taken.
✗ Branch 72 not taken.
✗ Branch 73 not taken.
6 {"PortStateOp", EL6224<mode>::NumPorts, false}}
278 {
279 // Kind names ("PortState"/"PortStateOp") match inputKinds_
280 // below, not the pre-existing pdserv paths ("/State"/
281 // "/StateOp") -- registerInputChannel()'s kind argument is
282 // also the setAlias() lookup key, so it must agree with what
283 // inputKinds() advertises for readInput<T>("PortState", ...)
284 // etc. to line up with what setAlias() can attach an alias to.
285
2/8
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
1 registerInputChannel("PortState", device.state);
286
2/8
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
1 registerInputChannel("PortStateOp", device.stateOp);
287 1 }
288
289 1 void updateInputs() override { device.updateInputs(); }
290 1 void updateOutputs() override { device.updateOutputs(); }
291
292 6 const std::vector<ChannelKind> &inputKinds() const override
293 {
294 6 return inputKinds_;
295 }
296 1 const std::vector<ChannelKind> &outputKinds() const override
297 {
298 1 return outputKinds_;
299 }
300
301 1 unsigned int numPorts() const override { return EL6224<mode>::NumPorts; }
302
303 1 void setPortDevice(
304 unsigned int port,
305 const std::string &deviceName,
306 const std::string &alias) override
307 {
308
1/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
2 stringstream path;
309
2/8
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
1 path << device.prefix << "/";
310
1/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 if (alias.empty()) {
311
1/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 path << port + 1;
312 }
313 else {
314 ✗ path << alias;
315 }
316
3/12
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 14 taken 1 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 1 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 1 times.
✗ Branch 21 not taken.
2 device.setPortDevice(
317 port,
318 createIoLinkDevice(
319 1 deviceName, device.serv, device.task, path.str()));
320 1 }
321
322 1 void applyPortConfig() override { device.configure(); }
323
324 2 SubDevice &port(unsigned int i) override
325 {
326 2 IoLinkDevice *d = device.portDevice(i);
327
2/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 if (!d) {
328
1/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
2 stringstream err;
329
3/12
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 1 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 1 times.
✗ Branch 17 not taken.
1 err << "EL6224 port " << i << " has no device assigned.";
330
2/8
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 13 taken 1 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 1 times.
✗ Branch 17 not taken.
1 throw UnknownChannel(err.str());
331 }
332 1 return *d;
333 }
334
335 ✗ const SubDevice &port(unsigned int i) const override
336 {
337 ✗ const IoLinkDevice *d = device.portDevice(i);
338 ✗ if (!d) {
339 ✗ stringstream err;
340 ✗ err << "EL6224 port " << i << " has no device assigned.";
341 ✗ throw UnknownChannel(err.str());
342 }
343 ✗ return *d;
344 }
345
346 protected:
347 2 ChannelValue getInputAt(size_t i) const override
348 {
349 // Kind blocks: PortState[0..NumPorts), PortStateOp[0..NumPorts)
350 2 const unsigned int numPorts = EL6224<mode>::NumPorts;
351
2/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 if (i < numPorts) {
352 1 return uint16_t(device.portState(static_cast<unsigned int>(i)));
353 }
354 else {
355 return device.portStateOp(
356 1 static_cast<unsigned int>(i) - numPorts);
357 }
358 }
359
360 ✗ void setOutputAt(size_t, const ChannelValue &) override
361 {
362 throw UnknownChannel("EL6224 has no top-level output channels; "
363 ✗ "write to a port's own SubDevice instead.");
364 }
365
366 private:
367 EL6224<mode> device;
368 std::vector<ChannelKind> inputKinds_;
369 std::vector<ChannelKind> outputKinds_;
370 };
371
372 /****************************************************************************/
373
374 namespace {
375
376 12 DeviceFactoryFn makeEL6224Factory()
377 {
378 1 return [](const SlaveContext &ctx) -> unique_ptr<SubDevice> {
379
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ctx.mode == Mode::Control) {
380 ✗ return make_unique<EL6224Adapter<Mode::Control>>(ctx);
381 }
382 else {
383 1 return make_unique<EL6224Adapter<Mode::Simulation>>(ctx);
384 }
385
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 };
386 }
387
388 struct EL6224Registration
389 {
390 12 EL6224Registration()
391 {
392
4/8
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 12 times.
✗ Branch 14 not taken.
36 Registry::instance().add(
393 {"EL6224", __FILE__, 0x00000002, 0x18503052, AnyRevision,
394
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
24 wrapFactory(makeEL6224Factory())});
395 12 }
396 12 } el6224Registration;
397
398 } // namespace
399
400 36 } // namespace EcApp
401
402 /****************************************************************************/
403