GCC Code Coverage Report


Directory: ./
File: src/devices/Ex1xxx.cpp
Date: 2026-09-03 16:05:57
Exec Total Coverage
Lines: 46 79 58.2%
Branches: 21 206 10.2%

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 pdserv *pdserv,
171 pdtask *task,
172 const std::string &prefix,
173 Master *master,
174 uint16_t alias,
175 uint16_t position,
176 Domain *domain,
177 Type typeIdx) :
178 1 type(ex1xxxType[typeIdx]),
179 sc {nullptr},
180 domain(domain),
181 off_in {0},
182
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)
183 {
184
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) {
185 pdserv_signal(
186 task, 1, (prefix + "/Inputs").c_str(), pd_boolean_T,
187 input.data(), input.size(), NULL);
188 }
189
190
1/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (!master) {
191 1 return;
192 }
193
194 sc = ecrt_master_slave_config(
195 master->getMaster(), alias, position, 0x00000002,
196 type.product_code);
197 if (!sc) {
198 stringstream err;
199 err << "Failed to get slave configuration.";
200 throw runtime_error(err.str());
201 }
202
203 if (ecrt_slave_config_pdos(
204 sc, EC_END, SwappedSync<mode>(type.sync).getSync())) {
205 stringstream err;
206 err << "Failed to configure PDOs.";
207 throw runtime_error(err.str());
208 }
209
210 off_in = ecrt_slave_config_reg_pdo_entry(
211 sc, 0x6000, 0x01, domain->getDomain(), NULL);
212 if (off_in < 0) {
213 stringstream err;
214 err << "Failed to register Ex1xxx value PDO entry.";
215 throw runtime_error(err.str());
216 }
217 }
218
219 /****************************************************************************/
220
221 template <Mode mode>
222 1 void Ex1xxx<mode>::update()
223 {
224
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 if (domain->getData()) {
225 for (unsigned int i = 0; i < type.num_channels; i++) {
226 input[i] = getInput(i);
227 }
228 }
229 1 }
230
231 /****************************************************************************/
232
233 template <Mode mode>
234 3 bool Ex1xxx<mode>::getInput(unsigned int channel) const
235 {
236
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) {
237 const unsigned int byteOffset = channel / 8;
238 const unsigned int bitOffset = channel % 8;
239
240 return EC_READ_BIT(
241 domain->getData() + off_in + byteOffset, bitOffset);
242 }
243 else {
244 3 return false;
245 }
246 }
247
248 /****************************************************************************/
249
250 template <Mode mode>
251 void Ex1xxx<mode>::setInput(unsigned int channel, bool value)
252 {
253 if (domain->getData() && channel < type.num_channels) {
254 const unsigned int byteOffset = channel / 8;
255 const unsigned int bitOffset = channel % 8;
256
257 EC_WRITE_BIT(
258 domain->getData() + off_in + byteOffset, bitOffset, value);
259 }
260 }
261
262 /****************************************************************************/
263
264 template <Mode mode>
265 1 unsigned int Ex1xxx<mode>::numChannels() const
266 {
267 1 return type.num_channels;
268 }
269
270 /****************************************************************************/
271
272 template class Ex1xxx<Mode::Control>;
273 template class Ex1xxx<Mode::Simulation>;
274
275 /****************************************************************************/
276 // Generic SubDevice adapter: pure digital input, no outputs. Application
277 // code never names Ex1xxx, only e.g. "EL1008" -- the factory resolves it.
278 /****************************************************************************/
279
280 template <Mode mode>
281 2 class Ex1xxxAdapter : public SubDevice
282 {
283 public:
284 1 Ex1xxxAdapter(const SlaveContext &ctx, typename Ex1xxx<mode>::Type t) :
285 1 device(ctx.pdServ,
286 1 ctx.task,
287 ctx.prefix,
288 1 ctx.master,
289 1 ctx.alias,
290 1 ctx.position,
291 1 ctx.domainIn,
292 t),
293
6/24
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 1 times.
✗ Branch 10 not taken.
✓ Branch 13 taken 1 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 1 times.
✓ Branch 17 taken 1 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 34 not taken.
✗ Branch 35 not taken.
✗ Branch 37 not taken.
✗ Branch 38 not taken.
✗ Branch 41 not taken.
✗ Branch 42 not taken.
✗ Branch 44 not taken.
✗ Branch 45 not taken.
✗ Branch 50 not taken.
✗ Branch 51 not taken.
5 inputKinds_ {{"Input", device.numChannels(), false}}
294 1 {}
295
296 1 void updateInputs() override { device.update(); }
297 void updateOutputs() override {}
298
299 9 const std::vector<ChannelKind> &inputKinds() const override
300 {
301 9 return inputKinds_;
302 }
303 2 const std::vector<ChannelKind> &outputKinds() const override
304 {
305 2 return outputKinds_;
306 }
307
308 protected:
309 3 ChannelValue getInputAt(size_t i) const override
310 {
311 3 return device.getInput(static_cast<unsigned int>(i));
312 }
313 void setOutputAt(size_t, const ChannelValue &) override
314 {
315 throw UnknownChannel("Ex1xxx has no output channels.");
316 }
317
318 private:
319 Ex1xxx<mode> device;
320 std::vector<ChannelKind> inputKinds_;
321 std::vector<ChannelKind> outputKinds_;
322 };
323
324 /****************************************************************************/
325
326 namespace {
327
328 56 DeviceFactoryFn makeEx1xxxFactory(unsigned int typeIdx)
329 {
330 2 return [typeIdx](const SlaveContext &ctx) -> unique_ptr<SubDevice> {
331
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ctx.mode == Mode::Control) {
332 return make_unique<Ex1xxxAdapter<Mode::Control>>(
333 ctx, static_cast<Ex1xxx<Mode::Control>::Type>(typeIdx));
334 }
335 else {
336
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 return make_unique<Ex1xxxAdapter<Mode::Simulation>>(
337 ctx,
338 3 static_cast<Ex1xxx<Mode::Simulation>::Type>(typeIdx));
339 }
340 56 };
341 }
342
343 struct Ex1xxxRegistrations
344 {
345 8 Ex1xxxRegistrations()
346 {
347
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 8 times.
64 for (unsigned int i = 0; i < Ex1xxx<Mode::Control>::NumTypes; i++) {
348 56 const auto &t = ex1xxxType[i];
349
4/8
✓ Branch 3 taken 56 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 56 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 56 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 56 times.
✗ Branch 14 not taken.
280 Registry::instance().add(
350 112 {t.name, __FILE__, 0x00000002, t.product_code,
351
1/2
✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
112 AnyRevision, wrapFactory(makeEx1xxxFactory(i))});
352 }
353 8 }
354 8 } ex1xxxRegistrations;
355
356 } // namespace
357
358 24 } // namespace EcApp
359
360 /****************************************************************************/
361