GCC Code Coverage Report


Directory: ./
File: src/devices/EL6692.cpp
Date: 2026-09-23 16:22:15
Exec Total Coverage
Lines: 138 208 66.3%
Branches: 88 450 19.6%

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/EL6692.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 #include "ecapp/VariablePdo.h"
32
33 #include <memory>
34 #include <sstream>
35 #include <stdexcept>
36 #include <type_traits>
37 #include <variant>
38
39 /****************************************************************************/
40
41 using std::make_unique;
42 using std::runtime_error;
43 using std::size_t;
44 using std::stringstream;
45 using std::unique_ptr;
46
47 namespace EcApp {
48
49 namespace {
50
51 /** Bit length of a channel's type on the wire -- bool is packed into
52 * a single bit like every other digital signal in this library
53 * (EL1008, EL2622, ...); every other alternative gets its natural
54 * memory width. */
55 3 unsigned int bitLenOf(const ChannelValue &v)
56 {
57 3 return std::visit(
58 3 [](auto &&value) -> unsigned int {
59 using T = std::decay_t<decltype(value)>;
60 if constexpr (std::is_same_v<T, bool>) {
61 1 return 1;
62 }
63 else {
64 2 return sizeof(T) * 8;
65 }
66 },
67
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
6 v);
68 }
69
70 /** Reads the domain data at (offset, bit) into a ChannelValue holding
71 * the same alternative as `prototype`. */
72 ChannelValue
73 ✗ readRaw(const uint8_t *data,
74 int offset,
75 unsigned int bit,
76 const ChannelValue &prototype)
77 {
78 return std::visit(
79 ✗ [&](auto &&proto) -> ChannelValue {
80 using T = std::decay_t<decltype(proto)>;
81 if constexpr (std::is_same_v<T, bool>) {
82 ✗ return static_cast<bool>(EC_READ_BIT(data + offset, bit));
83 }
84 else if constexpr (std::is_same_v<T, uint8_t>) {
85 ✗ return EC_READ_U8(data + offset);
86 }
87 else if constexpr (std::is_same_v<T, int8_t>) {
88 ✗ return EC_READ_S8(data + offset);
89 }
90 else if constexpr (std::is_same_v<T, uint16_t>) {
91 ✗ return EC_READ_U16(data + offset);
92 }
93 else if constexpr (std::is_same_v<T, int16_t>) {
94 ✗ return EC_READ_S16(data + offset);
95 }
96 else if constexpr (std::is_same_v<T, uint32_t>) {
97 ✗ return EC_READ_U32(data + offset);
98 }
99 else if constexpr (std::is_same_v<T, int32_t>) {
100 ✗ return EC_READ_S32(data + offset);
101 }
102 else if constexpr (std::is_same_v<T, float>) {
103 ✗ return EC_READ_REAL(data + offset);
104 }
105 else { // double
106 ✗ return EC_READ_LREAL(data + offset);
107 }
108 },
109 ✗ prototype);
110 }
111
112 /** Writes `value` to the domain data at (offset, bit). */
113 ✗ void writeRaw(
114 uint8_t *data,
115 int offset,
116 unsigned int bit,
117 const ChannelValue &value)
118 {
119 ✗ std::visit(
120 ✗ [&](auto &&v) {
121 using T = std::decay_t<decltype(v)>;
122 if constexpr (std::is_same_v<T, bool>) {
123 ✗ EC_WRITE_BIT(data + offset, bit, v);
124 }
125 else if constexpr (std::is_same_v<T, uint8_t>) {
126 ✗ EC_WRITE_U8(data + offset, v);
127 }
128 else if constexpr (std::is_same_v<T, int8_t>) {
129 ✗ EC_WRITE_S8(data + offset, v);
130 }
131 else if constexpr (std::is_same_v<T, uint16_t>) {
132 ✗ EC_WRITE_U16(data + offset, v);
133 }
134 else if constexpr (std::is_same_v<T, int16_t>) {
135 ✗ EC_WRITE_S16(data + offset, v);
136 }
137 else if constexpr (std::is_same_v<T, uint32_t>) {
138 ✗ EC_WRITE_U32(data + offset, v);
139 }
140 else if constexpr (std::is_same_v<T, int32_t>) {
141 ✗ EC_WRITE_S32(data + offset, v);
142 }
143 else if constexpr (std::is_same_v<T, float>) {
144 ✗ EC_WRITE_REAL(data + offset, v);
145 }
146 else { // double
147 ✗ EC_WRITE_LREAL(data + offset, v);
148 }
149 ✗ },
150 value);
151 }
152
153 /** Fixed TxPDO 0x1a01 "TxPDO-Map External Sync Compact", mapped
154 * alongside the variable TxPDO 0x1a00 on every instance so the
155 * "External device not connected" bit is always available -- see
156 * EL6692<mode>::configure(). Verified against the real terminal
157 * ("ethercat pdos -v -pN"): 2 bytes total (2+6+3+1+1+1+1+1 bit); only
158 * the last entry is ever registered/read here, the rest is left
159 * unregistered, like the many unused "Gap" entries elsewhere in this
160 * library. */
161 ec_pdo_entry_info_t el6692DiagPdoEntries[] = {
162 {0x10f4, 0x01, 2}, // Sync Mode
163 {0x0000, 0x00, 6}, // Gap
164 {0x0000, 0x00, 3}, // Gap
165 {0x1800, 0x09, 1}, // TxPDO-Toggle
166 {0x1800, 0x07, 1}, // TxPDO-State
167 {0x10f4, 0x0e, 1}, // Control value update toggle
168 {0x10f4, 0x0f, 1}, // Time stamp update toggle
169 {0x10f4, 0x10, 1}, // External device not connected
170 };
171
172 } // namespace
173
174 /****************************************************************************/
175
176 template <Mode mode>
177 1 EL6692<mode>::EL6692(
178 Master *master,
179 uint16_t alias,
180 uint16_t position,
181 Domain *domainOut,
182 Domain *domainIn) :
183 1 sc(NULL), domainOut(domainOut), domainIn(domainIn)
184 {
185 // pdserv publishing ("Connected", and later each dynamically
186 // added channel) happens in EL6692Adapter's constructor/
187 // configure() below, via SubDevice::registerInputChannel()/etc.
188
189
1/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (!master) {
190 1 return;
191 }
192
193 ✗ sc = ecrt_master_slave_config(
194 master->getMaster(), alias, position, 0x00000002, 0x1a243052);
195 ✗ if (!sc) {
196 ✗ stringstream err;
197 ✗ err << "Failed to get slave configuration.";
198 ✗ throw runtime_error(err.str());
199 }
200 }
201
202 /****************************************************************************/
203
204 namespace {
205
206 /** Throws if two channels in `channels` share a name -- ecapp cannot
207 * address them unambiguously afterwards (see
208 * SubDevice::inputIndex()/outputIndex()). */
209 3 void checkUniqueNames(
210 const std::string &direction,
211 const std::vector<PdoChannel> &channels)
212 {
213
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
6 for (size_t i = 0; i < channels.size(); i++) {
214
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
5 for (size_t j = i + 1; j < channels.size(); j++) {
215
2/2
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
2 if (channels[i].name == channels[j].name) {
216
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 stringstream err;
217 err << "EL6692: " << direction << " channel '"
218
5/10
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✓ Branch 11 taken 1 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 1 times.
✗ Branch 15 not taken.
1 << channels[i].name << "' declared more than once.";
219
2/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
1 throw runtime_error(err.str());
220 }
221 }
222 }
223 2 }
224
225 /** Throws if an input and an output channel share a name. Both
226 * directions are registered as pdserv signals under the same
227 * "<prefix>/<name>" path (see configure() below), so a name reused
228 * across directions would silently try to register the same pdserv
229 * signal path twice -- unlike a same-direction duplicate, this is not
230 * caught by checkUniqueNames() above, since that only ever compares
231 * within one of the two lists. */
232 1 void checkNoCrossDirectionCollision(
233 const std::vector<PdoChannel> &inputChannels,
234 const std::vector<PdoChannel> &outputChannels)
235 {
236
2/2
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
2 for (const auto &in : inputChannels) {
237
2/2
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 1 times.
3 for (const auto &out : outputChannels) {
238
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if (in.name == out.name) {
239 ✗ stringstream err;
240 err << "EL6692: channel '" << in.name
241 ✗ << "' declared as both an input and an output -- "
242 "both would register the same pdserv signal "
243 ✗ "path.";
244 ✗ throw runtime_error(err.str());
245 }
246 }
247 }
248 1 }
249
250 } // namespace
251
252 /****************************************************************************/
253
254 template <Mode mode>
255 2 void EL6692<mode>::configure(
256 const std::vector<PdoChannel> &inputChannels,
257 const std::vector<PdoChannel> &outputChannels)
258 {
259
3/8
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
3 checkUniqueNames("input", inputChannels);
260
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 checkUniqueNames("output", outputChannels);
261
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 checkNoCrossDirectionCollision(inputChannels, outputChannels);
262
263 1 inputs_.clear();
264
2/4
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
2 for (const auto &pc : inputChannels) {
265 2 InChannel ch;
266
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 ch.name = pc.name;
267
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 ch.value = pc.type;
268
1/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
1 inputs_.push_back(std::move(ch));
269 }
270
271 1 outputs_.clear();
272
2/4
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 1 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
3 for (const auto &pc : outputChannels) {
273 4 OutChannel ch;
274
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 ch.name = pc.name;
275 2 ch.forcible = pc.forcible;
276
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 ch.commanded = pc.type;
277
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 ch.forcedValue = pc.type;
278
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 ch.driven = pc.type;
279
1/4
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
2 outputs_.push_back(std::move(ch));
280 }
281
282 // Build RxPDO 0x1600 (outputs, application-defined) and TxPDO
283 // 0x1a00 (inputs, application-defined) entries. Index/subindex
284 // are ecapp's own choice, spaced like every fixed-table device in
285 // this library (e.g. Ex4xxx's 0x7000 + n*0x10) -- see
286 // EcApp::VariablePdo: this terminal accepts freely defined
287 // entries, there is nothing in its own object dictionary these
288 // need to match.
289 2 std::vector<ec_pdo_entry_info_t> outEntries;
290
2/4
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
3 for (size_t i = 0; i < outputs_.size(); i++) {
291
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
4 outEntries.push_back(
292 {static_cast<uint16_t>(0x7000 + i * 0x10), 0x01,
293
1/4
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
2 static_cast<uint8_t>(bitLenOf(outputs_[i].commanded))});
294 }
295 2 std::vector<ec_pdo_entry_info_t> inEntries;
296
2/4
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 for (size_t i = 0; i < inputs_.size(); i++) {
297
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 inEntries.push_back(
298 {static_cast<uint16_t>(0x6000 + i * 0x10), 0x01,
299
1/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
1 static_cast<uint8_t>(bitLenOf(inputs_[i].value))});
300 }
301
302
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
3 ec_pdo_info_t outPdo {
303 1 0x1600, static_cast<unsigned int>(outEntries.size()),
304 1 outEntries.empty() ? nullptr : outEntries.data()};
305 1 ec_pdo_info_t diagPdo {
306 0x1a01,
307 sizeof(el6692DiagPdoEntries) / sizeof(*el6692DiagPdoEntries),
308 el6692DiagPdoEntries};
309
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
3 ec_pdo_info_t inPdo {
310 1 0x1a00, static_cast<unsigned int>(inEntries.size()),
311 1 inEntries.empty() ? nullptr : inEntries.data()};
312 1 ec_pdo_info_t inPdos[] = {diagPdo, inPdo};
313
314 1 ec_sync_info_t syncs[] = {
315 {0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
316 {1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
317 {2, EC_DIR_OUTPUT, 1, &outPdo, EC_WD_DISABLE},
318 {3, EC_DIR_INPUT, 2, inPdos, EC_WD_DISABLE},
319 {0xff}};
320
321
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (sc) {
322 ✗ if (ecrt_slave_config_pdos(
323 sc, EC_END, SwappedSync<mode>(syncs).getSync())) {
324 ✗ stringstream err;
325 ✗ err << "Failed to configure PDOs.";
326 ✗ throw runtime_error(err.str());
327 }
328
329 ✗ for (size_t i = 0; i < outputs_.size(); i++) {
330 ✗ auto &ch = outputs_[i];
331 ✗ unsigned int bitLen = bitLenOf(ch.commanded);
332 ✗ ch.offset = ecrt_slave_config_reg_pdo_entry(
333 sc, static_cast<uint16_t>(0x7000 + i * 0x10), 0x01,
334 ✗ domainOut->getDomain(), bitLen == 1 ? &ch.bit : NULL);
335 ✗ if (ch.offset < 0) {
336 ✗ stringstream err;
337 ✗ err << "Failed to register EL6692 output PDO entry '"
338 ✗ << ch.name << "'.";
339 ✗ throw runtime_error(err.str());
340 }
341 }
342 ✗ for (size_t i = 0; i < inputs_.size(); i++) {
343 ✗ auto &ch = inputs_[i];
344 ✗ unsigned int bitLen = bitLenOf(ch.value);
345 ✗ ch.offset = ecrt_slave_config_reg_pdo_entry(
346 sc, static_cast<uint16_t>(0x6000 + i * 0x10), 0x01,
347 ✗ domainIn->getDomain(), bitLen == 1 ? &ch.bit : NULL);
348 ✗ if (ch.offset < 0) {
349 ✗ stringstream err;
350 ✗ err << "Failed to register EL6692 input PDO entry '"
351 ✗ << ch.name << "'.";
352 ✗ throw runtime_error(err.str());
353 }
354 }
355 ✗ offConnected = ecrt_slave_config_reg_pdo_entry(
356 ✗ sc, 0x10f4, 0x10, domainIn->getDomain(), &bitConnected);
357 ✗ if (offConnected < 0) {
358 ✗ stringstream err;
359 ✗ err << "Failed to register EL6692 diagnosis PDO entry.";
360 ✗ throw runtime_error(err.str());
361 }
362 }
363
364 // pdserv registration (done once the final channel list is known,
365 // not in add{Input,Output}(), matching IoLinkMaster::
366 // applyPortConfig()'s contract) happens in EL6692Adapter::
367 // configure() below, via SubDevice::registerInputChannel()/
368 // registerOutputChannel()/registerParameter() -- that is also what
369 // makes every channel alias-able through SubDevice::setAlias().
370 1 }
371
372 /****************************************************************************/
373
374 template <Mode mode>
375 2 void EL6692<mode>::updateInputs()
376 {
377
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 if (!domainIn->getData()) {
378 2 return;
379 }
380 ✗ const uint8_t *data = domainIn->getData();
381 ✗ for (auto &ch : inputs_) {
382 ✗ ch.value = readRaw(data, ch.offset, ch.bit, ch.value);
383 }
384 ✗ connected_ = !EC_READ_BIT(data + offConnected, bitConnected);
385 }
386
387 /****************************************************************************/
388
389 template <Mode mode>
390 3 void EL6692<mode>::updateOutputs()
391 {
392 // Forcing is computed unconditionally, like Ex20xx/Ex4xxx do --
393 // only the actual wire write needs real domain data, so the
394 // forcing logic itself stays testable without a bus/master.
395 3 uint8_t *data = domainOut->getData();
396
2/4
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 3 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
7 for (auto &ch : outputs_) {
397
4/12
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
4 ch.driven = ch.forcible && ch.enableForcing ? ch.forcedValue
398 : ch.commanded;
399
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 if (data) {
400 ✗ writeRaw(data, ch.offset, ch.bit, ch.driven);
401 }
402 }
403 3 }
404
405 /****************************************************************************/
406
407 template <Mode mode>
408 2 ChannelValue EL6692<mode>::getInput(unsigned int index) const
409 {
410 2 return inputs_.at(index).value;
411 }
412
413 /****************************************************************************/
414
415 template <Mode mode>
416 3 void EL6692<mode>::setOutput(unsigned int index, const ChannelValue &value)
417 {
418 3 auto &ch = outputs_.at(index);
419 // value must hold ch's own alternative: a same-index assignment
420 // is guaranteed (by the standard) to update in place, preserving
421 // the address already handed to pdserv for this channel (see
422 // SubDevice::registerOutputChannel()'s ChannelValue overload); a
423 // different-index assignment would not, and would silently
424 // misinterpret the new alternative's bytes as the old,
425 // pdserv-registered type. Matches ChannelValue's own documented
426 // contract (SubDevice.h): wrong-type access throws
427 // std::bad_variant_access.
428
2/4
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
3 if (value.index() != ch.commanded.index()) {
429 1 throw std::bad_variant_access();
430 }
431 2 ch.commanded = value;
432 2 }
433
434 /****************************************************************************/
435
436 template class EL6692<Mode::Control>;
437 template class EL6692<Mode::Simulation>;
438
439 /****************************************************************************/
440 // Generic SubDevice + VariablePdo adapter. Channel 0 of inputKinds()
441 // is always the built-in "Connected" diagnosis channel; declared
442 // channels (materialized into inputKinds_/outputKinds_ by
443 // configure()) follow, one count==1 kind per channel -- unlike a
444 // fixed terminal's homogeneous count>1 kind, VariablePdo channels are
445 // individually named and independently typed.
446 /****************************************************************************/
447
448 template <Mode mode>
449 2 class EL6692Adapter : public SubDevice, public VariablePdo
450 {
451 public:
452 1 explicit EL6692Adapter(const SlaveContext &ctx) :
453 SubDevice(
454 1 ctx.domainOut,
455 1 ctx.domainIn,
456 1 ctx.pdServ,
457 1 ctx.task,
458 ctx.prefix),
459 1 device(ctx.master,
460 1 ctx.alias,
461 1 ctx.position,
462 1 ctx.domainOut,
463 1 ctx.domainIn),
464
6/24
✓ 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 18 taken 1 times.
✓ Branch 19 taken 1 times.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 37 not taken.
✗ Branch 38 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✗ Branch 46 not taken.
✗ Branch 47 not taken.
✗ Branch 50 not taken.
✗ Branch 51 not taken.
✗ Branch 53 not taken.
✗ Branch 54 not taken.
✗ Branch 61 not taken.
✗ Branch 62 not taken.
5 inputKinds_ {{"Connected", 1, false}}
465 {
466
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("Connected", device.connected_, AsBoolean {});
467 1 }
468
469 2 void updateInputs() override { device.updateInputs(); }
470 3 void updateOutputs() override { device.updateOutputs(); }
471
472 7 const std::vector<ChannelKind> &inputKinds() const override
473 {
474 7 return inputKinds_;
475 }
476 7 const std::vector<ChannelKind> &outputKinds() const override
477 {
478 7 return outputKinds_;
479 }
480
481 2 void configure(
482 const std::vector<PdoChannel> &inputs,
483 const std::vector<PdoChannel> &outputs) override
484 {
485 2 device.configure(inputs, outputs);
486
487 1 inputKinds_.resize(1); // keep "Connected", drop any stale rebuild
488
2/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
2 for (unsigned int i = 0; i < device.numInputs(); i++) {
489
1/4
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 13 taken 1 times.
✗ Branch 14 not taken.
1 inputKinds_.push_back({inputName(i), 1, device.getInput(i)});
490 1 registerInputChannel(inputName(i), device.inputs_[i].value);
491 }
492 1 outputKinds_.clear();
493
2/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
3 for (unsigned int i = 0; i < device.numOutputs(); i++) {
494
1/4
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 13 taken 2 times.
✗ Branch 14 not taken.
2 outputKinds_.push_back({outputName(i), 1, outputType(i)});
495
496 2 auto &ch = device.outputs_[i];
497 2 registerOutputChannel(ch.name, ch.commanded);
498
2/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 if (ch.forcible) {
499 // ch.driven (the post-forcing effective value) has no
500 // matching ChannelKind -- it's a read-only diagnostic
501 // signal, not itself part of the generic API -- so it
502 // gets its own pdserv-only path instead, same as
503 // Ex4xxx/Ex20xx/Festo's "EffectiveOutput".
504
1/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 9 taken 1 times.
✗ Branch 10 not taken.
1 registerOutputChannel(ch.name + "/Output", ch.driven);
505
1/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 9 taken 1 times.
✗ Branch 10 not taken.
1 registerParameter(
506 ch.name + "/EnableForcing", ch.name, ch.enableForcing,
507 AsBoolean {});
508
1/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
1 registerParameter(
509 ch.name + "/ForcedValue", ch.name, ch.forcedValue);
510 }
511 }
512 1 }
513
514 protected:
515 2 ChannelValue getInputAt(size_t i) const override
516 {
517
2/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 if (i == 0) {
518 1 return device.connected();
519 }
520 1 return device.getInput(static_cast<unsigned int>(i - 1));
521 }
522
523 3 void setOutputAt(size_t i, const ChannelValue &value) override
524 {
525 3 device.setOutput(static_cast<unsigned int>(i), value);
526 2 }
527
528 private:
529 // Small helpers so configure() above doesn't need friend
530 // access into EL6692<mode>'s private channel vectors just to read
531 // back a name/type for the ChannelKind list.
532 2 const std::string &inputName(unsigned int i) const
533 {
534 2 return device.inputChannelName(i);
535 }
536 2 const std::string &outputName(unsigned int i) const
537 {
538 2 return device.outputChannelName(i);
539 }
540 2 ChannelValue outputType(unsigned int i) const
541 {
542 2 return device.outputChannelType(i);
543 }
544
545 EL6692<mode> device;
546 std::vector<ChannelKind> inputKinds_;
547 std::vector<ChannelKind> outputKinds_;
548 };
549
550 /****************************************************************************/
551
552 namespace {
553
554 12 DeviceFactoryFn makeEL6692Factory()
555 {
556 1 return [](const SlaveContext &ctx) -> unique_ptr<SubDevice> {
557
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ctx.mode == Mode::Control) {
558 ✗ return make_unique<EL6692Adapter<Mode::Control>>(ctx);
559 }
560 else {
561 1 return make_unique<EL6692Adapter<Mode::Simulation>>(ctx);
562 }
563
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 };
564 }
565
566 struct EL6692Registration
567 {
568 12 EL6692Registration()
569 {
570
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(
571 {"EL6692", __FILE__, 0x00000002, 0x1a243052, AnyRevision,
572
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
24 wrapFactory(makeEL6692Factory())});
573 12 }
574 12 } el6692Registration;
575
576 } // namespace
577
578 36 } // namespace EcApp
579
580 /****************************************************************************/
581