GCC Code Coverage Report


Directory: ./
File: src/devices/EL6692.cpp
Date: 2026-10-12 15:11:37
Exec Total Coverage
Lines: 145 217 66.8%
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 /** PDO entry address of the n-th declared channel of one direction,
154 * starting at `base` (0x7000 for outputs, 0x6000 for inputs). Uses as
155 * few indices as possible: subindices 0x01..0xff are filled first,
156 * only then does the index advance by one. */
157 struct EntryAddress
158 {
159 uint16_t index;
160 uint8_t subindex;
161 };
162
163 3 EntryAddress entryAddress(uint16_t base, size_t n)
164 {
165 3 return {static_cast<uint16_t>(base + n / 255),
166 3 static_cast<uint8_t>(1 + n % 255)};
167 }
168
169 /** Fixed TxPDO 0x1a01 "TxPDO-Map External Sync Compact", mapped
170 * alongside the variable TxPDO 0x1a00 on every instance so the
171 * "External device not connected" bit is always available -- see
172 * EL6692<mode>::configure(). Verified against the real terminal
173 * ("ethercat pdos -v -pN"): 2 bytes total (2+6+3+1+1+1+1+1 bit); only
174 * the last entry is ever registered/read here, the rest is left
175 * unregistered, like the many unused "Gap" entries elsewhere in this
176 * library. */
177 ec_pdo_entry_info_t el6692DiagPdoEntries[] = {
178 {0x10f4, 0x01, 2}, // Sync Mode
179 {0x0000, 0x00, 6}, // Gap
180 {0x0000, 0x00, 3}, // Gap
181 {0x1800, 0x09, 1}, // TxPDO-Toggle
182 {0x1800, 0x07, 1}, // TxPDO-State
183 {0x10f4, 0x0e, 1}, // Control value update toggle
184 {0x10f4, 0x0f, 1}, // Time stamp update toggle
185 {0x10f4, 0x10, 1}, // External device not connected
186 };
187
188 } // namespace
189
190 /****************************************************************************/
191
192 template <Mode mode>
193 1 EL6692<mode>::EL6692(
194 Master *master,
195 uint16_t alias,
196 uint16_t position,
197 Domain *domainOut,
198 Domain *domainIn) :
199 1 sc(NULL), domainOut(domainOut), domainIn(domainIn)
200 {
201 // pdserv publishing ("Connected", and later each dynamically
202 // added channel) happens in EL6692Adapter's constructor/
203 // configure() below, via SubDevice::registerInputChannel()/etc.
204
205
1/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (!master) {
206 1 return;
207 }
208
209 ✗ sc = ecrt_master_slave_config(
210 master->getMaster(), alias, position, 0x00000002, 0x1a243052);
211 ✗ if (!sc) {
212 ✗ stringstream err;
213 ✗ err << "Failed to get slave configuration.";
214 ✗ throw runtime_error(err.str());
215 }
216 }
217
218 /****************************************************************************/
219
220 namespace {
221
222 /** Throws if two channels in `channels` share a name -- ecapp cannot
223 * address them unambiguously afterwards (see
224 * SubDevice::inputIndex()/outputIndex()). */
225 3 void checkUniqueNames(
226 const std::string &direction,
227 const std::vector<PdoChannel> &channels)
228 {
229
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
6 for (size_t i = 0; i < channels.size(); i++) {
230
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
5 for (size_t j = i + 1; j < channels.size(); j++) {
231
2/2
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
2 if (channels[i].name == channels[j].name) {
232
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 stringstream err;
233 err << "EL6692: " << direction << " channel '"
234
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.";
235
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());
236 }
237 }
238 }
239 2 }
240
241 /** Throws if an input and an output channel share a name. Both
242 * directions are registered as pdserv signals under the same
243 * "<prefix>/<name>" path (see configure() below), so a name reused
244 * across directions would silently try to register the same pdserv
245 * signal path twice -- unlike a same-direction duplicate, this is not
246 * caught by checkUniqueNames() above, since that only ever compares
247 * within one of the two lists. */
248 1 void checkNoCrossDirectionCollision(
249 const std::vector<PdoChannel> &inputChannels,
250 const std::vector<PdoChannel> &outputChannels)
251 {
252
2/2
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
2 for (const auto &in : inputChannels) {
253
2/2
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 1 times.
3 for (const auto &out : outputChannels) {
254
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if (in.name == out.name) {
255 ✗ stringstream err;
256 err << "EL6692: channel '" << in.name
257 ✗ << "' declared as both an input and an output -- "
258 "both would register the same pdserv signal "
259 ✗ "path.";
260 ✗ throw runtime_error(err.str());
261 }
262 }
263 }
264 1 }
265
266 } // namespace
267
268 /****************************************************************************/
269
270 template <Mode mode>
271 2 void EL6692<mode>::configure(
272 const std::vector<PdoChannel> &inputChannels,
273 const std::vector<PdoChannel> &outputChannels)
274 {
275
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);
276
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);
277
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 checkNoCrossDirectionCollision(inputChannels, outputChannels);
278
279 1 inputs_.clear();
280
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) {
281 2 InChannel ch;
282
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 ch.name = pc.name;
283
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 ch.value = pc.type;
284
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));
285 }
286
287 1 outputs_.clear();
288
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) {
289 4 OutChannel ch;
290
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 ch.name = pc.name;
291 2 ch.forcible = pc.forcible;
292
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 ch.commanded = pc.type;
293
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 ch.forcedValue = pc.type;
294
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 ch.driven = pc.type;
295
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));
296 }
297
298 // Build RxPDO 0x1600 (outputs, application-defined) and TxPDO
299 // 0x1a00 (inputs, application-defined) entries. Index/subindex
300 // are ecapp's own choice, packed as densely as possible (see
301 // entryAddress()) -- see EcApp::VariablePdo: this terminal
302 // accepts freely defined entries, there is nothing in its own
303 // object dictionary these need to match.
304 2 std::vector<ec_pdo_entry_info_t> outEntries;
305
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++) {
306 2 auto addr = entryAddress(0x7000, i);
307
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 outEntries.push_back(
308 4 {addr.index, addr.subindex,
309
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))});
310 }
311 2 std::vector<ec_pdo_entry_info_t> inEntries;
312
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++) {
313 1 auto addr = entryAddress(0x6000, i);
314
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
3 inEntries.push_back(
315 2 {addr.index, addr.subindex,
316
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))});
317 }
318
319
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 {
320 1 0x1600, static_cast<unsigned int>(outEntries.size()),
321 1 outEntries.empty() ? nullptr : outEntries.data()};
322 1 ec_pdo_info_t diagPdo {
323 0x1a01,
324 sizeof(el6692DiagPdoEntries) / sizeof(*el6692DiagPdoEntries),
325 el6692DiagPdoEntries};
326
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 {
327 1 0x1a00, static_cast<unsigned int>(inEntries.size()),
328 1 inEntries.empty() ? nullptr : inEntries.data()};
329 1 ec_pdo_info_t inPdos[] = {diagPdo, inPdo};
330
331 1 ec_sync_info_t syncs[] = {
332 {0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
333 {1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
334 {2, EC_DIR_OUTPUT, 1, &outPdo, EC_WD_DISABLE},
335 {3, EC_DIR_INPUT, 2, inPdos, EC_WD_DISABLE},
336 {0xff}};
337
338
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (sc) {
339 ✗ if (ecrt_slave_config_pdos(
340 sc, EC_END, SwappedSync<mode>(syncs).getSync())) {
341 ✗ stringstream err;
342 ✗ err << "Failed to configure PDOs.";
343 ✗ throw runtime_error(err.str());
344 }
345
346 ✗ for (size_t i = 0; i < outputs_.size(); i++) {
347 ✗ auto &ch = outputs_[i];
348 ✗ unsigned int bitLen = bitLenOf(ch.commanded);
349 ✗ auto addr = entryAddress(0x7000, i);
350 ✗ ch.offset = ecrt_slave_config_reg_pdo_entry(
351 ✗ sc, addr.index, addr.subindex, domainOut->getDomain(),
352 bitLen == 1 ? &ch.bit : NULL);
353 ✗ if (ch.offset < 0) {
354 ✗ stringstream err;
355 ✗ err << "Failed to register EL6692 output PDO entry '"
356 ✗ << ch.name << "'.";
357 ✗ throw runtime_error(err.str());
358 }
359 }
360 ✗ for (size_t i = 0; i < inputs_.size(); i++) {
361 ✗ auto &ch = inputs_[i];
362 ✗ unsigned int bitLen = bitLenOf(ch.value);
363 ✗ auto addr = entryAddress(0x6000, i);
364 ✗ ch.offset = ecrt_slave_config_reg_pdo_entry(
365 ✗ sc, addr.index, addr.subindex, domainIn->getDomain(),
366 bitLen == 1 ? &ch.bit : NULL);
367 ✗ if (ch.offset < 0) {
368 ✗ stringstream err;
369 ✗ err << "Failed to register EL6692 input PDO entry '"
370 ✗ << ch.name << "'.";
371 ✗ throw runtime_error(err.str());
372 }
373 }
374 ✗ offConnected = ecrt_slave_config_reg_pdo_entry(
375 ✗ sc, 0x10f4, 0x10, domainIn->getDomain(), &bitConnected);
376 ✗ if (offConnected < 0) {
377 ✗ stringstream err;
378 ✗ err << "Failed to register EL6692 diagnosis PDO entry.";
379 ✗ throw runtime_error(err.str());
380 }
381 }
382
383 // pdserv registration (done once the final channel list is known,
384 // not in add{Input,Output}(), matching IoLinkMaster::
385 // applyPortConfig()'s contract) happens in EL6692Adapter::
386 // configure() below, via SubDevice::registerInputChannel()/
387 // registerOutputChannel()/registerParameter() -- that is also what
388 // makes every channel alias-able through SubDevice::setAlias().
389 1 }
390
391 /****************************************************************************/
392
393 template <Mode mode>
394 2 void EL6692<mode>::updateInputs()
395 {
396
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 if (!domainIn->getData()) {
397 2 return;
398 }
399 ✗ const uint8_t *data = domainIn->getData();
400 ✗ for (auto &ch : inputs_) {
401 ✗ ch.value = readRaw(data, ch.offset, ch.bit, ch.value);
402 }
403 ✗ connected_ = !EC_READ_BIT(data + offConnected, bitConnected);
404 }
405
406 /****************************************************************************/
407
408 template <Mode mode>
409 3 void EL6692<mode>::updateOutputs()
410 {
411 // Forcing is computed unconditionally, like Ex20xx/Ex4xxx do --
412 // only the actual wire write needs real domain data, so the
413 // forcing logic itself stays testable without a bus/master.
414 3 uint8_t *data = domainOut->getData();
415
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_) {
416
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
417 : ch.commanded;
418
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 if (data) {
419 ✗ writeRaw(data, ch.offset, ch.bit, ch.driven);
420 }
421 }
422 3 }
423
424 /****************************************************************************/
425
426 template <Mode mode>
427 2 ChannelValue EL6692<mode>::getInput(unsigned int index) const
428 {
429 2 return inputs_.at(index).value;
430 }
431
432 /****************************************************************************/
433
434 template <Mode mode>
435 3 void EL6692<mode>::setOutput(unsigned int index, const ChannelValue &value)
436 {
437 3 auto &ch = outputs_.at(index);
438 // value must hold ch's own alternative: a same-index assignment
439 // is guaranteed (by the standard) to update in place, preserving
440 // the address already handed to pdserv for this channel (see
441 // SubDevice::registerOutputChannel()'s ChannelValue overload); a
442 // different-index assignment would not, and would silently
443 // misinterpret the new alternative's bytes as the old,
444 // pdserv-registered type. Matches ChannelValue's own documented
445 // contract (SubDevice.h): wrong-type access throws
446 // std::bad_variant_access.
447
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()) {
448 1 throw std::bad_variant_access();
449 }
450 2 ch.commanded = value;
451 2 }
452
453 /****************************************************************************/
454
455 template class EL6692<Mode::Control>;
456 template class EL6692<Mode::Simulation>;
457
458 /****************************************************************************/
459 // Generic SubDevice + VariablePdo adapter. Channel 0 of inputKinds()
460 // is always the built-in "Connected" diagnosis channel; declared
461 // channels (materialized into inputKinds_/outputKinds_ by
462 // configure()) follow, one count==1 kind per channel -- unlike a
463 // fixed terminal's homogeneous count>1 kind, VariablePdo channels are
464 // individually named and independently typed.
465 /****************************************************************************/
466
467 template <Mode mode>
468 2 class EL6692Adapter : public SubDevice, public VariablePdo
469 {
470 public:
471 1 explicit EL6692Adapter(const SlaveContext &ctx) :
472 SubDevice(
473 1 ctx.domainOut,
474 1 ctx.domainIn,
475 1 ctx.pdServ,
476 1 ctx.task,
477 ctx.prefix),
478 1 device(ctx.master,
479 1 ctx.alias,
480 1 ctx.position,
481 1 ctx.domainOut,
482 1 ctx.domainIn),
483
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}}
484 {
485
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 {});
486 1 }
487
488 2 void updateInputs() override { device.updateInputs(); }
489 3 void updateOutputs() override { device.updateOutputs(); }
490
491 7 const std::vector<ChannelKind> &inputKinds() const override
492 {
493 7 return inputKinds_;
494 }
495 7 const std::vector<ChannelKind> &outputKinds() const override
496 {
497 7 return outputKinds_;
498 }
499
500 2 void configure(
501 const std::vector<PdoChannel> &inputs,
502 const std::vector<PdoChannel> &outputs) override
503 {
504 2 device.configure(inputs, outputs);
505
506 1 inputKinds_.resize(1); // keep "Connected", drop any stale rebuild
507
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++) {
508
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)});
509 1 registerInputChannel(inputName(i), device.inputs_[i].value);
510 }
511 1 outputKinds_.clear();
512
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++) {
513
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)});
514
515 2 auto &ch = device.outputs_[i];
516 2 registerOutputChannel(ch.name, ch.commanded);
517
2/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 if (ch.forcible) {
518 // ch.driven (the post-forcing effective value) has no
519 // matching ChannelKind -- it's a read-only diagnostic
520 // signal, not itself part of the generic API -- so it
521 // gets its own pdserv-only path instead, same as
522 // Ex4xxx/Ex20xx/Festo's "EffectiveOutput".
523
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);
524
1/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 9 taken 1 times.
✗ Branch 10 not taken.
1 registerParameter(
525 ch.name + "/EnableForcing", ch.name, ch.enableForcing,
526 AsBoolean {});
527
1/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
1 registerParameter(
528 ch.name + "/ForcedValue", ch.name, ch.forcedValue);
529 }
530 }
531 1 }
532
533 protected:
534 2 ChannelValue getInputAt(size_t i) const override
535 {
536
2/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 if (i == 0) {
537 1 return device.connected();
538 }
539 1 return device.getInput(static_cast<unsigned int>(i - 1));
540 }
541
542 3 void setOutputAt(size_t i, const ChannelValue &value) override
543 {
544 3 device.setOutput(static_cast<unsigned int>(i), value);
545 2 }
546
547 private:
548 // Small helpers so configure() above doesn't need friend
549 // access into EL6692<mode>'s private channel vectors just to read
550 // back a name/type for the ChannelKind list.
551 2 const std::string &inputName(unsigned int i) const
552 {
553 2 return device.inputChannelName(i);
554 }
555 2 const std::string &outputName(unsigned int i) const
556 {
557 2 return device.outputChannelName(i);
558 }
559 2 ChannelValue outputType(unsigned int i) const
560 {
561 2 return device.outputChannelType(i);
562 }
563
564 EL6692<mode> device;
565 std::vector<ChannelKind> inputKinds_;
566 std::vector<ChannelKind> outputKinds_;
567 };
568
569 /****************************************************************************/
570
571 namespace {
572
573 12 DeviceFactoryFn makeEL6692Factory()
574 {
575 1 return [](const SlaveContext &ctx) -> unique_ptr<SubDevice> {
576
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ctx.mode == Mode::Control) {
577 ✗ return make_unique<EL6692Adapter<Mode::Control>>(ctx);
578 }
579 else {
580 1 return make_unique<EL6692Adapter<Mode::Simulation>>(ctx);
581 }
582
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 };
583 }
584
585 struct EL6692Registration
586 {
587 12 EL6692Registration()
588 {
589
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(
590 {"EL6692", __FILE__, 0x00000002, 0x1a243052, AnyRevision,
591
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
24 wrapFactory(makeEL6692Factory())});
592 12 }
593 12 } el6692Registration;
594
595 } // namespace
596
597 36 } // namespace EcApp
598
599 /****************************************************************************/
600