GCC Code Coverage Report


Directory: ./
File: src/devices/EL6224.cpp
Date: 2026-09-03 16:05:57
Exec Total Coverage
Lines: 67 131 51.1%
Branches: 37 342 10.8%

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
1/8
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
1 if (pdserv && task) {
104 pdserv_signal(
105 task, 1, (prefix + "/State").c_str(), pd_uint8_T,
106 state.data(), state.size(), NULL);
107 pdserv_signal(
108 task, 1, (prefix + "/StateOp").c_str(), pd_uint8_T,
109 stateOp.data(), stateOp.size(), NULL);
110 }
111
112
1/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (!master) {
113 1 return;
114 }
115
116 sc = ecrt_master_slave_config(
117 master->getMaster(), alias, position, 0x00000002, 0x18503052);
118 if (!sc) {
119 stringstream err;
120 err << "Failed to get slave configuration.";
121 throw runtime_error(err.str());
122 }
123
124 if (ecrt_slave_config_pdos(
125 sc, EC_END, SwappedSync<mode>(el6224_syncs).getSync())) {
126 stringstream err;
127 err << "Failed to configure PDOs.";
128 throw runtime_error(err.str());
129 }
130 }
131
132 /****************************************************************************/
133
134 template <Mode mode>
135 1 void EL6224<mode>::setPortDevice(
136 unsigned int port,
137 std::unique_ptr<IoLinkDevice> device)
138 {
139 1 devices.at(port) = std::move(device);
140 1 }
141
142 /****************************************************************************/
143
144 template <Mode mode>
145 2 IoLinkDevice *EL6224<mode>::portDevice(unsigned int port)
146 {
147 2 return devices.at(port).get();
148 }
149
150 /****************************************************************************/
151
152 template <Mode mode>
153 const IoLinkDevice *EL6224<mode>::portDevice(unsigned int port) const
154 {
155 return devices.at(port).get();
156 }
157
158 /****************************************************************************/
159
160 template <Mode mode>
161 1 void EL6224<mode>::configure()
162 {
163
1/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (!sc) {
164 1 return;
165 }
166
167 uint16_t outputPdoIndex = 0x1600;
168 uint16_t inputPdoIndex = 0x1a00;
169 uint16_t outputPdoEntryIndex = 0x7000;
170 uint16_t inputPdoEntryIndex = 0x6000;
171
172 for (unsigned int i = 0; i < NumPorts; i++) {
173 const uint16_t sdoIndex(0x8000 + i * 0x0010);
174 if (devices[i]) {
175 devices[i]->addConfig(sc, sdoIndex);
176
177 if (devices[i]->addOutputs(
178 sc, outputPdoIndex, outputPdoEntryIndex, domainOut)) {
179 outputPdoIndex++;
180 outputPdoEntryIndex += 0x0010;
181 }
182 if (devices[i]->addInputs(
183 sc, inputPdoIndex, inputPdoEntryIndex, domainIn)) {
184 inputPdoIndex++;
185 inputPdoEntryIndex += 0x0010;
186 }
187
188 stringstream path;
189 path << prefix << "/" << i + 1;
190 devices[i]->registerVariables(serv, task, path.str());
191 }
192 else {
193 const uint8_t data[] = {0x28, 0x00, 0x00, 0x00, 0x00, 0x00,
194 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
195 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
196 0x00, 0x00, 0x00, 0x00};
197 if (ecrt_slave_config_complete_sdo(sc, sdoIndex, data, 22)) {
198 stringstream err;
199 err << "Failed to configure SDO " << sdoIndex;
200 throw runtime_error(err.str());
201 }
202 }
203 }
204
205 offState = ecrt_slave_config_reg_pdo_entry(
206 sc, 0xf100, 0x01, domainIn->getDomain(), NULL);
207 if (offState < 0) {
208 stringstream err;
209 err << "Failed to register state input PDO entry.";
210 throw runtime_error(err.str());
211 }
212
213 for (unsigned int i = 0; i < NumPorts; i++) {
214 if (devices[i]) {
215 devices[i]->registerOutputs(sc, domainOut);
216 devices[i]->registerInputs(sc, domainIn);
217 }
218 }
219 }
220
221 /****************************************************************************/
222
223 template <Mode mode>
224 1 void EL6224<mode>::updateInputs()
225 {
226
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 if (domainIn->getData()) {
227 for (unsigned int i = 0; i < NumPorts; i++) {
228 state[i] = EC_READ_U8(domainIn->getData() + offState + i);
229 stateOp[i] = (state[i] & 0x0F) == 0x03;
230 }
231 }
232
233
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++) {
234
1/4
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
4 if (devices[i]) {
235 devices[i]->updateInputs();
236 }
237 }
238 1 }
239
240 /****************************************************************************/
241
242 template <Mode mode>
243 1 void EL6224<mode>::updateOutputs()
244 {
245
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++) {
246
1/4
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
4 if (devices[i]) {
247 devices[i]->updateOutputs();
248 }
249 }
250 1 }
251
252 /****************************************************************************/
253
254 template class EL6224<Mode::Control>;
255 template class EL6224<Mode::Simulation>;
256
257 /****************************************************************************/
258 // Generic SubDevice + IoLinkMaster adapter. Top-level channels are just
259 // the always-present per-port diagnostic bytes (from EtherCAT object
260 // 0xf100); a port's actual variables live on the SubDevice that
261 // port() returns once a driver has been assigned to it.
262 /****************************************************************************/
263
264 template <Mode mode>
265 2 class EL6224Adapter : public SubDevice, public IoLinkMaster
266 {
267 public:
268 1 explicit EL6224Adapter(const SlaveContext &ctx) :
269 1 device(ctx.pdServ,
270 1 ctx.task,
271 ctx.prefix,
272 1 ctx.master,
273 1 ctx.alias,
274 1 ctx.position,
275 1 ctx.domainOut,
276 1 ctx.domainIn),
277 inputKinds_ {
278 {"PortState", EL6224<mode>::NumPorts, uint16_t(0)},
279
6/24
✓ Branch 3 taken 1 times.
✗ Branch 4 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 18 taken 2 times.
✓ Branch 19 taken 1 times.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 36 not taken.
✗ Branch 37 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
✗ Branch 44 not taken.
✗ Branch 45 not taken.
✗ Branch 48 not taken.
✗ Branch 49 not taken.
✗ Branch 51 not taken.
✗ Branch 52 not taken.
✗ Branch 58 not taken.
✗ Branch 59 not taken.
5 {"PortStateOp", EL6224<mode>::NumPorts, false}}
280 1 {}
281
282 1 void updateInputs() override { device.updateInputs(); }
283 1 void updateOutputs() override { device.updateOutputs(); }
284
285 6 const std::vector<ChannelKind> &inputKinds() const override
286 {
287 6 return inputKinds_;
288 }
289 1 const std::vector<ChannelKind> &outputKinds() const override
290 {
291 1 return outputKinds_;
292 }
293
294 1 unsigned int numPorts() const override { return EL6224<mode>::NumPorts; }
295
296 void
297 1 setPortDevice(unsigned int port, const std::string &deviceName) override
298 {
299
1/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
1 device.setPortDevice(port, createIoLinkDevice(deviceName));
300 1 }
301
302 1 void applyPortConfig() override { device.configure(); }
303
304 2 SubDevice &port(unsigned int i) override
305 {
306 2 IoLinkDevice *d = device.portDevice(i);
307
2/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 if (!d) {
308
1/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
2 stringstream err;
309
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.";
310
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());
311 }
312 1 return *d;
313 }
314
315 const SubDevice &port(unsigned int i) const override
316 {
317 const IoLinkDevice *d = device.portDevice(i);
318 if (!d) {
319 stringstream err;
320 err << "EL6224 port " << i << " has no device assigned.";
321 throw UnknownChannel(err.str());
322 }
323 return *d;
324 }
325
326 protected:
327 2 ChannelValue getInputAt(size_t i) const override
328 {
329 // Kind blocks: PortState[0..NumPorts), PortStateOp[0..NumPorts)
330 2 const unsigned int numPorts = EL6224<mode>::NumPorts;
331
2/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 if (i < numPorts) {
332 1 return uint16_t(device.portState(static_cast<unsigned int>(i)));
333 }
334 else {
335 return device.portStateOp(
336 1 static_cast<unsigned int>(i) - numPorts);
337 }
338 }
339
340 void setOutputAt(size_t, const ChannelValue &) override
341 {
342 throw UnknownChannel("EL6224 has no top-level output channels; "
343 "write to a port's own SubDevice instead.");
344 }
345
346 private:
347 EL6224<mode> device;
348 std::vector<ChannelKind> inputKinds_;
349 std::vector<ChannelKind> outputKinds_;
350 };
351
352 /****************************************************************************/
353
354 namespace {
355
356 8 DeviceFactoryFn makeEL6224Factory()
357 {
358 1 return [](const SlaveContext &ctx) -> unique_ptr<SubDevice> {
359
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ctx.mode == Mode::Control) {
360 return make_unique<EL6224Adapter<Mode::Control>>(ctx);
361 }
362 else {
363 1 return make_unique<EL6224Adapter<Mode::Simulation>>(ctx);
364 }
365
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 };
366 }
367
368 struct EL6224Registration
369 {
370 8 EL6224Registration()
371 {
372
4/8
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 8 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 8 times.
✗ Branch 14 not taken.
24 Registry::instance().add(
373 {"EL6224", __FILE__, 0x00000002, 0x18503052, AnyRevision,
374
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
16 wrapFactory(makeEL6224Factory())});
375 8 }
376 8 } el6224Registration;
377
378 } // namespace
379
380 24 } // namespace EcApp
381
382 /****************************************************************************/
383