GCC Code Coverage Report


Directory: ./
File: src/devices/EL6224.cpp
Date: 2026-10-12 15:11:37
Exec Total Coverage
Lines: 77 127 60.6%
Branches: 48 326 14.7%

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 ✗ for (unsigned int i = 0; i < NumPorts; i++) {
164 ✗ const uint16_t sdoIndex(0x8000 + i * 0x0010);
165 ✗ if (devices[i]) {
166 ✗ devices[i]->addConfig(sc, sdoIndex);
167
168 // PDO and PDO entry indices are fixed per port (port n always
169 // uses 0x160n/0x1a0n and 0x70n0/0x60n0), so unused ports
170 // leave gaps instead of shifting the following ports down.
171 ✗ devices[i]->addOutputs(
172 ✗ sc, 0x1600 + i, 0x7000 + i * 0x0010, domainOut);
173 ✗ devices[i]->addInputs(
174 ✗ sc, 0x1a00 + i, 0x6000 + i * 0x0010, domainIn);
175 }
176 else {
177 ✗ const uint8_t data[] = {0x28, 0x00, 0x00, 0x00, 0x00, 0x00,
178 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
179 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
180 0x00, 0x00, 0x00, 0x00};
181 ✗ if (ecrt_slave_config_complete_sdo(sc, sdoIndex, data, 22)) {
182 ✗ stringstream err;
183 ✗ err << "Failed to configure SDO " << sdoIndex;
184 ✗ throw runtime_error(err.str());
185 }
186 }
187 }
188
189 ✗ offState = ecrt_slave_config_reg_pdo_entry(
190 ✗ sc, 0xf100, 0x01, domainIn->getDomain(), NULL);
191 ✗ if (offState < 0) {
192 ✗ stringstream err;
193 ✗ err << "Failed to register state input PDO entry.";
194 ✗ throw runtime_error(err.str());
195 }
196
197 ✗ for (unsigned int i = 0; i < NumPorts; i++) {
198 ✗ if (devices[i]) {
199 ✗ devices[i]->registerOutputs(sc, domainOut);
200 ✗ devices[i]->registerInputs(sc, domainIn);
201 }
202 }
203 }
204
205 /****************************************************************************/
206
207 template <Mode mode>
208 1 void EL6224<mode>::updateInputs()
209 {
210
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 if (domainIn->getData()) {
211 ✗ for (unsigned int i = 0; i < NumPorts; i++) {
212 ✗ state[i] = EC_READ_U8(domainIn->getData() + offState + i);
213 ✗ stateOp[i] = (state[i] & 0x0F) == 0x03;
214 }
215 }
216
217
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++) {
218
1/4
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
4 if (devices[i]) {
219 ✗ devices[i]->updateInputs();
220 }
221 }
222 1 }
223
224 /****************************************************************************/
225
226 template <Mode mode>
227 1 void EL6224<mode>::updateOutputs()
228 {
229
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++) {
230
1/4
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
4 if (devices[i]) {
231 ✗ devices[i]->updateOutputs();
232 }
233 }
234 1 }
235
236 /****************************************************************************/
237
238 template class EL6224<Mode::Control>;
239 template class EL6224<Mode::Simulation>;
240
241 /****************************************************************************/
242 // Generic SubDevice + IoLinkMaster adapter. Top-level channels are just
243 // the always-present per-port diagnostic bytes (from EtherCAT object
244 // 0xf100); a port's actual variables live on the SubDevice that
245 // port() returns once a driver has been assigned to it.
246 /****************************************************************************/
247
248 template <Mode mode>
249 2 class EL6224Adapter : public SubDevice, public IoLinkMaster
250 {
251 public:
252 1 explicit EL6224Adapter(const SlaveContext &ctx) :
253 SubDevice(
254 1 ctx.domainOut,
255 1 ctx.domainIn,
256 1 ctx.pdServ,
257 1 ctx.task,
258 ctx.prefix),
259 1 device(ctx.pdServ,
260 1 ctx.task,
261 ctx.prefix,
262 1 ctx.master,
263 1 ctx.alias,
264 1 ctx.position,
265 1 ctx.domainOut,
266 1 ctx.domainIn),
267 inputKinds_ {
268 {"PortState", EL6224<mode>::NumPorts, uint16_t(0)},
269
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}}
270 {
271 // Kind names ("PortState"/"PortStateOp") match inputKinds_
272 // below, not the pre-existing pdserv paths ("/State"/
273 // "/StateOp") -- registerInputChannel()'s kind argument is
274 // also the setAlias() lookup key, so it must agree with what
275 // inputKinds() advertises for readInput<T>("PortState", ...)
276 // etc. to line up with what setAlias() can attach an alias to.
277
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);
278
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);
279 1 }
280
281 1 void updateInputs() override { device.updateInputs(); }
282 1 void updateOutputs() override { device.updateOutputs(); }
283
284 6 const std::vector<ChannelKind> &inputKinds() const override
285 {
286 6 return inputKinds_;
287 }
288 1 const std::vector<ChannelKind> &outputKinds() const override
289 {
290 1 return outputKinds_;
291 }
292
293 1 unsigned int numPorts() const override { return EL6224<mode>::NumPorts; }
294
295 1 void setPortDevice(
296 unsigned int port,
297 const std::string &deviceName,
298 const std::string &alias) override
299 {
300
1/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
2 stringstream path;
301
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 << "/";
302
1/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 if (alias.empty()) {
303
1/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 path << port + 1;
304 }
305 else {
306 ✗ path << alias;
307 }
308
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(
309 port,
310 createIoLinkDevice(
311 1 deviceName, device.serv, device.task, path.str()));
312 1 }
313
314 1 void applyPortConfig() override { device.configure(); }
315
316 2 SubDevice &port(unsigned int i) override
317 {
318 2 IoLinkDevice *d = device.portDevice(i);
319
2/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 if (!d) {
320
1/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
2 stringstream err;
321
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.";
322
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());
323 }
324 1 return *d;
325 }
326
327 ✗ const SubDevice &port(unsigned int i) const override
328 {
329 ✗ const IoLinkDevice *d = device.portDevice(i);
330 ✗ if (!d) {
331 ✗ stringstream err;
332 ✗ err << "EL6224 port " << i << " has no device assigned.";
333 ✗ throw UnknownChannel(err.str());
334 }
335 ✗ return *d;
336 }
337
338 protected:
339 2 ChannelValue getInputAt(size_t i) const override
340 {
341 // Kind blocks: PortState[0..NumPorts), PortStateOp[0..NumPorts)
342 2 const unsigned int numPorts = EL6224<mode>::NumPorts;
343
2/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 if (i < numPorts) {
344 1 return uint16_t(device.portState(static_cast<unsigned int>(i)));
345 }
346 else {
347 return device.portStateOp(
348 1 static_cast<unsigned int>(i) - numPorts);
349 }
350 }
351
352 ✗ void setOutputAt(size_t, const ChannelValue &) override
353 {
354 throw UnknownChannel("EL6224 has no top-level output channels; "
355 ✗ "write to a port's own SubDevice instead.");
356 }
357
358 private:
359 EL6224<mode> device;
360 std::vector<ChannelKind> inputKinds_;
361 std::vector<ChannelKind> outputKinds_;
362 };
363
364 /****************************************************************************/
365
366 namespace {
367
368 12 DeviceFactoryFn makeEL6224Factory()
369 {
370 1 return [](const SlaveContext &ctx) -> unique_ptr<SubDevice> {
371
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ctx.mode == Mode::Control) {
372 ✗ return make_unique<EL6224Adapter<Mode::Control>>(ctx);
373 }
374 else {
375 1 return make_unique<EL6224Adapter<Mode::Simulation>>(ctx);
376 }
377
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 };
378 }
379
380 struct EL6224Registration
381 {
382 12 EL6224Registration()
383 {
384
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(
385 {"EL6224", __FILE__, 0x00000002, 0x18503052, AnyRevision,
386
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
24 wrapFactory(makeEL6224Factory())});
387 12 }
388 12 } el6224Registration;
389
390 } // namespace
391
392 36 } // namespace EcApp
393
394 /****************************************************************************/
395