GCC Code Coverage Report


Directory: ./
File: src/devices/Ex1xxx.cpp
Date: 2026-09-23 16:22:15
Exec Total Coverage
Lines: 42 73 57.5%
Branches: 23 202 11.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/Ex1xxx.h"
24
25 #include "SlaveContext.h"
26 #include "ecapp/Domain.h"
27 #include "ecapp/Exceptions.h"
28 #include "ecapp/Factory.h"
29 #include "ecapp/Master.h"
30 #include "ecapp/SwappedSync.h"
31
32 #include <memory>
33 #include <sstream>
34 #include <stdexcept>
35
36 /****************************************************************************/
37
38 using std::make_unique;
39 using std::runtime_error;
40 using std::size_t;
41 using std::stringstream;
42 using std::unique_ptr;
43
44 namespace EcApp {
45
46 /*
47 * EL1004, EL1014
48 */
49
50 ec_pdo_entry_info_t el10x4_pdo_entries[] = {
51 {0x6000, 0x01, 1}, /* Input */
52 {0x6010, 0x01, 1}, /* Input */
53 {0x6020, 0x01, 1}, /* Input */
54 {0x6030, 0x01, 1}, /* Input */
55 };
56
57 ec_pdo_info_t el10x4_pdos[] = {
58 {0x1a00, 1, el10x4_pdo_entries + 0}, /* Channel 1 */
59 {0x1a01, 1, el10x4_pdo_entries + 1}, /* Channel 2 */
60 {0x1a02, 1, el10x4_pdo_entries + 2}, /* Channel 3 */
61 {0x1a03, 1, el10x4_pdo_entries + 3}, /* Channel 4 */
62 };
63
64 ec_sync_info_t el10x4_syncs[] = {
65 {0, EC_DIR_INPUT, 4, el10x4_pdos + 0, EC_WD_DISABLE},
66 {0xff}};
67
68 /*
69 * EL1008, EL1018, EPP1008-0001
70 */
71
72 ec_pdo_entry_info_t ex10x8_pdo_entries[] = {
73 {0x6000, 0x01, 1}, /* Input */
74 {0x6010, 0x01, 1}, /* Input */
75 {0x6020, 0x01, 1}, /* Input */
76 {0x6030, 0x01, 1}, /* Input */
77 {0x6040, 0x01, 1}, /* Input */
78 {0x6050, 0x01, 1}, /* Input */
79 {0x6060, 0x01, 1}, /* Input */
80 {0x6070, 0x01, 1}, /* Input */
81 };
82
83 ec_pdo_info_t ex10x8_pdos[] = {
84 {0x1a00, 1, ex10x8_pdo_entries + 0}, /* Channel 1 */
85 {0x1a01, 1, ex10x8_pdo_entries + 1}, /* Channel 2 */
86 {0x1a02, 1, ex10x8_pdo_entries + 2}, /* Channel 3 */
87 {0x1a03, 1, ex10x8_pdo_entries + 3}, /* Channel 4 */
88 {0x1a04, 1, ex10x8_pdo_entries + 4}, /* Channel 5 */
89 {0x1a05, 1, ex10x8_pdo_entries + 5}, /* Channel 6 */
90 {0x1a06, 1, ex10x8_pdo_entries + 6}, /* Channel 7 */
91 {0x1a07, 1, ex10x8_pdo_entries + 7}, /* Channel 8 */
92 };
93
94 ec_sync_info_t ex10x8_syncs[] = {
95 {0, EC_DIR_INPUT, 8, ex10x8_pdos + 0, EC_WD_DISABLE},
96 {0xff}};
97
98 /* Master 0, Slave 3, "EL1809"
99 * Vendor ID: 0x00000002
100 * Product code: 0x07113052
101 * Revision number: 0x00120000
102 */
103
104 ec_pdo_entry_info_t el1809_pdo_entries[] = {
105 {0x6000, 0x01, 1}, /* Input */
106 {0x6010, 0x01, 1}, /* Input */
107 {0x6020, 0x01, 1}, /* Input */
108 {0x6030, 0x01, 1}, /* Input */
109 {0x6040, 0x01, 1}, /* Input */
110 {0x6050, 0x01, 1}, /* Input */
111 {0x6060, 0x01, 1}, /* Input */
112 {0x6070, 0x01, 1}, /* Input */
113 {0x6080, 0x01, 1}, /* Input */
114 {0x6090, 0x01, 1}, /* Input */
115 {0x60a0, 0x01, 1}, /* Input */
116 {0x60b0, 0x01, 1}, /* Input */
117 {0x60c0, 0x01, 1}, /* Input */
118 {0x60d0, 0x01, 1}, /* Input */
119 {0x60e0, 0x01, 1}, /* Input */
120 {0x60f0, 0x01, 1}, /* Input */
121 };
122
123 ec_pdo_info_t el1809_pdos[] = {
124 {0x1a00, 1, el1809_pdo_entries + 0}, /* Channel 1 */
125 {0x1a01, 1, el1809_pdo_entries + 1}, /* Channel 2 */
126 {0x1a02, 1, el1809_pdo_entries + 2}, /* Channel 3 */
127 {0x1a03, 1, el1809_pdo_entries + 3}, /* Channel 4 */
128 {0x1a04, 1, el1809_pdo_entries + 4}, /* Channel 5 */
129 {0x1a05, 1, el1809_pdo_entries + 5}, /* Channel 6 */
130 {0x1a06, 1, el1809_pdo_entries + 6}, /* Channel 7 */
131 {0x1a07, 1, el1809_pdo_entries + 7}, /* Channel 8 */
132 {0x1a08, 1, el1809_pdo_entries + 8}, /* Channel 9 */
133 {0x1a09, 1, el1809_pdo_entries + 9}, /* Channel 10 */
134 {0x1a0a, 1, el1809_pdo_entries + 10}, /* Channel 11 */
135 {0x1a0b, 1, el1809_pdo_entries + 11}, /* Channel 12 */
136 {0x1a0c, 1, el1809_pdo_entries + 12}, /* Channel 13 */
137 {0x1a0d, 1, el1809_pdo_entries + 13}, /* Channel 14 */
138 {0x1a0e, 1, el1809_pdo_entries + 14}, /* Channel 15 */
139 {0x1a0f, 1, el1809_pdo_entries + 15}, /* Channel 16 */
140 };
141
142 ec_sync_info_t el1809_syncs[] = {
143 {0, EC_DIR_INPUT, 16, el1809_pdos + 0, EC_WD_DISABLE},
144 {0xff}};
145
146 /****************************************************************************/
147
148 struct Ex1xxxType
149 {
150 const char *name;
151 uint32_t product_code;
152 unsigned int num_channels;
153 ec_sync_info_t *sync;
154 };
155
156 const static Ex1xxxType ex1xxxType[Ex1xxx<Mode::Control>::NumTypes] = {
157 {"EL1004", 0x03ec3052, 4, el10x4_syncs},
158 {"EL1008", 0x03f03052, 8, ex10x8_syncs},
159 {"EL1014", 0x03f63052, 4, el10x4_syncs},
160 {"EL1018", 0x03fa3052, 8, ex10x8_syncs},
161 {"EL1808", 0x07103052, 8, ex10x8_syncs},
162 {"EL1809", 0x07113052, 16, el1809_syncs},
163 {"EPP1008", 0x64760509, 8, ex10x8_syncs},
164 };
165
166 /****************************************************************************/
167
168 template <Mode mode>
169 1 Ex1xxx<mode>::Ex1xxx(
170 Master *master,
171 uint16_t alias,
172 uint16_t position,
173 Domain *domain,
174 Type typeIdx) :
175 1 type(ex1xxxType[typeIdx]),
176 sc {nullptr},
177 domain(domain),
178 off_in {0},
179
1/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
1 input(type.num_channels, false)
180 {
181 // pdserv publishing ("Input", published as pd_boolean_T even
182 // though stored as uint8_t) happens in Ex1xxxAdapter's constructor
183 // below, via SubDevice::registerInputChannel() -- that is also
184 // what makes this channel alias-able through SubDevice::setAlias().
185
186
1/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (!master) {
187 1 return;
188 }
189
190 ✗ sc = ecrt_master_slave_config(
191 master->getMaster(), alias, position, 0x00000002,
192 ✗ type.product_code);
193 ✗ if (!sc) {
194 ✗ stringstream err;
195 ✗ err << "Failed to get slave configuration.";
196 ✗ throw runtime_error(err.str());
197 }
198
199 ✗ if (ecrt_slave_config_pdos(
200 ✗ sc, EC_END, SwappedSync<mode>(type.sync).getSync())) {
201 ✗ stringstream err;
202 ✗ err << "Failed to configure PDOs.";
203 ✗ throw runtime_error(err.str());
204 }
205
206 ✗ off_in = ecrt_slave_config_reg_pdo_entry(
207 sc, 0x6000, 0x01, domain->getDomain(), NULL);
208 ✗ if (off_in < 0) {
209 ✗ stringstream err;
210 ✗ err << "Failed to register Ex1xxx value PDO entry.";
211 ✗ throw runtime_error(err.str());
212 }
213 }
214
215 /****************************************************************************/
216
217 template <Mode mode>
218 1 void Ex1xxx<mode>::update()
219 {
220
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 if (domain->getData()) {
221 ✗ for (unsigned int i = 0; i < type.num_channels; i++) {
222 ✗ input[i] = getInput(i);
223 }
224 }
225 1 }
226
227 /****************************************************************************/
228
229 template <Mode mode>
230 3 bool Ex1xxx<mode>::getInput(unsigned int channel) const
231 {
232
2/12
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
3 if (domain->getData() && channel < type.num_channels) {
233 ✗ const unsigned int byteOffset = channel / 8;
234 ✗ const unsigned int bitOffset = channel % 8;
235
236 ✗ return EC_READ_BIT(
237 domain->getData() + off_in + byteOffset, bitOffset);
238 }
239 else {
240 3 return false;
241 }
242 }
243
244 /****************************************************************************/
245
246 template <Mode mode>
247 ✗ void Ex1xxx<mode>::setInput(unsigned int channel, bool value)
248 {
249 ✗ if (domain->getData() && channel < type.num_channels) {
250 ✗ const unsigned int byteOffset = channel / 8;
251 ✗ const unsigned int bitOffset = channel % 8;
252
253 ✗ EC_WRITE_BIT(
254 domain->getData() + off_in + byteOffset, bitOffset, value);
255 }
256 }
257
258 /****************************************************************************/
259
260 template <Mode mode>
261 1 unsigned int Ex1xxx<mode>::numChannels() const
262 {
263 1 return type.num_channels;
264 }
265
266 /****************************************************************************/
267
268 template class Ex1xxx<Mode::Control>;
269 template class Ex1xxx<Mode::Simulation>;
270
271 /****************************************************************************/
272 // Generic SubDevice adapter: pure digital input, no outputs. Application
273 // code never names Ex1xxx, only e.g. "EL1008" -- the factory resolves it.
274 /****************************************************************************/
275
276 template <Mode mode>
277 2 class Ex1xxxAdapter : public SubDevice
278 {
279 public:
280 1 Ex1xxxAdapter(const SlaveContext &ctx, typename Ex1xxx<mode>::Type t) :
281 1 SubDevice(nullptr, ctx.domainIn, ctx.pdServ, ctx.task, ctx.prefix),
282 3 device(ctx.master, ctx.alias, ctx.position, ctx.domainIn, t),
283
7/28
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 1 times.
✗ Branch 14 not taken.
✓ Branch 17 taken 1 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 1 times.
✓ Branch 21 taken 1 times.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 38 not taken.
✗ Branch 39 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✗ Branch 46 not taken.
✗ Branch 47 not taken.
✗ Branch 49 not taken.
✗ Branch 50 not taken.
✗ Branch 53 not taken.
✗ Branch 54 not taken.
✗ Branch 56 not taken.
✗ Branch 57 not taken.
✗ Branch 64 not taken.
✗ Branch 65 not taken.
5 inputKinds_ {{"Input", device.numChannels(), false}}
284 {
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("Input", device.input, AsBoolean {});
286 1 }
287
288 1 void updateInputs() override { device.update(); }
289 ✗ void updateOutputs() override {}
290
291 10 const std::vector<ChannelKind> &inputKinds() const override
292 {
293 10 return inputKinds_;
294 }
295 2 const std::vector<ChannelKind> &outputKinds() const override
296 {
297 2 return outputKinds_;
298 }
299
300 protected:
301 3 ChannelValue getInputAt(size_t i) const override
302 {
303 3 return device.getInput(static_cast<unsigned int>(i));
304 }
305 ✗ void setOutputAt(size_t, const ChannelValue &) override
306 {
307 ✗ throw UnknownChannel("Ex1xxx has no output channels.");
308 }
309
310 private:
311 Ex1xxx<mode> device;
312 std::vector<ChannelKind> inputKinds_;
313 std::vector<ChannelKind> outputKinds_;
314 };
315
316 /****************************************************************************/
317
318 namespace {
319
320 84 DeviceFactoryFn makeEx1xxxFactory(unsigned int typeIdx)
321 {
322 2 return [typeIdx](const SlaveContext &ctx) -> unique_ptr<SubDevice> {
323
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ctx.mode == Mode::Control) {
324 ✗ return make_unique<Ex1xxxAdapter<Mode::Control>>(
325 ✗ ctx, static_cast<Ex1xxx<Mode::Control>::Type>(typeIdx));
326 }
327 else {
328
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 return make_unique<Ex1xxxAdapter<Mode::Simulation>>(
329 ctx,
330 3 static_cast<Ex1xxx<Mode::Simulation>::Type>(typeIdx));
331 }
332 84 };
333 }
334
335 struct Ex1xxxRegistrations
336 {
337 12 Ex1xxxRegistrations()
338 {
339
2/2
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 12 times.
96 for (unsigned int i = 0; i < Ex1xxx<Mode::Control>::NumTypes; i++) {
340 84 const auto &t = ex1xxxType[i];
341
4/8
✓ Branch 3 taken 84 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 84 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 84 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 84 times.
✗ Branch 14 not taken.
420 Registry::instance().add(
342 168 {t.name, __FILE__, 0x00000002, t.product_code,
343
1/2
✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
168 AnyRevision, wrapFactory(makeEx1xxxFactory(i))});
344 }
345 12 }
346 12 } ex1xxxRegistrations;
347
348 } // namespace
349
350 36 } // namespace EcApp
351
352 /****************************************************************************/
353