GCC Code Coverage Report


Directory: ./
File: src/devices/IfmIoLink.cpp
Date: 2026-09-23 16:22:15
Exec Total Coverage
Lines: 38 541 7.0%
Branches: 80 896 8.9%

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 * IO-Link port drivers for EL6224, for ifm sensors/actuators.
22 *
23 * Flow and pressure are converted to SI (m^3/s, Pa) here, even though
24 * the IODD documents them in L/h and bar: ecapp reports these two
25 * quantities in SI throughout. Temperature stays in degC, same as
26 * the IODD -- there is no SI temperature convention used instead.
27 *
28 ****************************************************************************/
29
30 #include "ecapp/Domain.h"
31 #include "ecapp/Exceptions.h"
32 #include "ecapp/IoLinkDevice.h"
33
34 #include <arpa/inet.h> // ntohs(), ntohl()
35 #include <cstring>
36 #include <memory>
37 #include <sstream>
38 #include <stdexcept>
39 #include <string>
40 #include <vector>
41
42 using std::runtime_error;
43 using std::size_t;
44 using std::string;
45 using std::stringstream;
46
47 /****************************************************************************/
48
49 namespace EcApp {
50
51 namespace {
52
53 /** L/h -> m^3/s. */
54 constexpr float LitersPerHourToSI = 1.0f / 3.6e6f;
55
56 /** L/min -> m^3/s. */
57 constexpr float LitersPerMinuteToSI = 1.0f / 6.0e4f;
58
59 /** bar -> Pa. */
60 constexpr float BarToSI = 1e5f;
61
62 } // namespace
63
64 /****************************************************************************/
65 // ifm SU6020/SU8020 (flow/temperature sensor, one digital output).
66 /****************************************************************************/
67
68 1 class IfmSU : public IoLinkDevice
69 {
70 public:
71 1 IfmSU(pdserv *pdServ, pdtask *task, const string &prefix) :
72
8/16
✓ 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 19 taken 1 times.
✗ Branch 20 not taken.
✓ Branch 23 taken 1 times.
✗ Branch 24 not taken.
✓ Branch 26 taken 5 times.
✓ Branch 27 taken 1 times.
✗ Branch 36 not taken.
✗ Branch 37 not taken.
1 IoLinkDevice(pdServ, task, prefix)
73 {
74
2/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
1 registerInputChannel("Flow", flow);
75
2/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
1 registerInputChannel("Temperature", temperature);
76
2/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
1 registerInputChannel("Out1", out1);
77
2/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
1 registerInputChannel("Out2", out2);
78
2/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
1 registerInputChannel("State", state);
79 1 }
80
81 ✗ void updateOutputs() override
82 {
83 ✗ if (domainOut_ && domainOut_->getData()) {
84 ✗ EC_WRITE_BIT(
85 domainOut_->getData() + offReset, bitOffReset, reset);
86 }
87 }
88
89 ✗ bool addOutputs(
90 ec_slave_config_t *sc,
91 uint16_t pdoIndex,
92 uint16_t pdoEntryIndex,
93 Domain *) override
94 {
95 ✗ if (ecrt_slave_config_pdo_assign_add(sc, 2, pdoIndex)) {
96 ✗ stringstream err;
97 ✗ err << "Failed to assign PDO " << pdoIndex;
98 ✗ throw runtime_error(err.str());
99 }
100 ✗ if (ecrt_slave_config_pdo_mapping_clear(sc, pdoIndex)) {
101 ✗ stringstream err;
102 ✗ err << "Failed to clear PDO mapping of " << pdoIndex;
103 ✗ throw runtime_error(err.str());
104 }
105 ✗ if (ecrt_slave_config_pdo_mapping_add(sc, pdoIndex, 0x0000, 0x00, 4)
106 ✗ || ecrt_slave_config_pdo_mapping_add(
107 sc, pdoIndex, pdoEntryIndex, 0x01, 1)
108 ✗ || ecrt_slave_config_pdo_mapping_add(
109 sc, pdoIndex, pdoEntryIndex, 0x02, 1)
110 ✗ || ecrt_slave_config_pdo_mapping_add(
111 sc, pdoIndex, 0x0000, 0x00, 2)
112 ✗ || ecrt_slave_config_pdo_mapping_add(
113 sc, pdoIndex, 0x0000, 0x00, 8)) {
114 ✗ stringstream err;
115 ✗ err << "Failed to map PDO entry in " << pdoIndex;
116 ✗ throw runtime_error(err.str());
117 }
118
119 /* ifm SU6020/SU8020 Outputs
120 * {0x0000, 0x00, 4}, Gap
121 * {0x7000, 0x01, 1}, Totalizer Reset and Inactive
122 * {0x7000, 0x02, 1}, Flow Override
123 * {0x0000, 0x00, 2}, Gap
124 * {0x0000, 0x00, 8}, Gap
125 */
126 ✗ outputPdoEntryIndex = pdoEntryIndex;
127 ✗ return true;
128 }
129
130 ✗ void registerOutputs(ec_slave_config_t *sc, Domain *domainOut) override
131 {
132 ✗ domainOut_ = domainOut;
133 ✗ offReset = ecrt_slave_config_reg_pdo_entry(
134 ✗ sc, outputPdoEntryIndex, 0x01, domainOut->getDomain(),
135 &bitOffReset);
136 ✗ if (offReset < 0) {
137 ✗ stringstream err;
138 ✗ err << "Failed to register output PDO entry "
139 ✗ << outputPdoEntryIndex << " 0x01.";
140 ✗ throw runtime_error(err.str());
141 }
142 }
143
144 ✗ bool addInputs(
145 ec_slave_config_t *sc,
146 uint16_t pdoIndex,
147 uint16_t pdoEntryIndex,
148 Domain *) override
149 {
150 ✗ if (ecrt_slave_config_pdo_assign_add(sc, 3, pdoIndex)) {
151 ✗ stringstream err;
152 ✗ err << "Failed to assign PDO " << pdoIndex;
153 ✗ throw runtime_error(err.str());
154 }
155 ✗ if (ecrt_slave_config_pdo_mapping_clear(sc, pdoIndex)) {
156 ✗ stringstream err;
157 ✗ err << "Failed to clear PDO mapping of " << pdoIndex;
158 ✗ throw runtime_error(err.str());
159 }
160 ✗ if (ecrt_slave_config_pdo_mapping_add(
161 sc, pdoIndex, pdoEntryIndex, 0x01, 32)
162 ✗ || ecrt_slave_config_pdo_mapping_add(
163 sc, pdoIndex, pdoEntryIndex, 0x02, 32)
164 ✗ || ecrt_slave_config_pdo_mapping_add(
165 sc, pdoIndex, 0x0000, 0x00, 16)
166 ✗ || ecrt_slave_config_pdo_mapping_add(
167 sc, pdoIndex, pdoEntryIndex, 0x03, 32)
168 ✗ || ecrt_slave_config_pdo_mapping_add(
169 sc, pdoIndex, 0x0000, 0x00, 8)
170 ✗ || ecrt_slave_config_pdo_mapping_add(
171 sc, pdoIndex, pdoEntryIndex, 0x04, 1)
172 ✗ || ecrt_slave_config_pdo_mapping_add(
173 sc, pdoIndex, pdoEntryIndex, 0x05, 1)
174 ✗ || ecrt_slave_config_pdo_mapping_add(
175 sc, pdoIndex, 0x0000, 0x00, 2)
176 ✗ || ecrt_slave_config_pdo_mapping_add(
177 sc, pdoIndex, pdoEntryIndex, 0x06, 4)) {
178 ✗ stringstream err;
179 ✗ err << "Failed to map PDO entry in " << pdoIndex;
180 ✗ throw runtime_error(err.str());
181 }
182
183 /* ifm SU6020/SU8020 Inputs
184 * {0x6000, 0x01, 32}, Totalizer (REAL)
185 * {0x6000, 0x02, 32}, Flow (DINT)
186 * {0x0000, 0x00, 16}, Gap
187 * {0x6000, 0x03, 32}, Temperature (DINT)
188 * {0x0000, 0x00, 8}, Gap
189 * {0x6000, 0x04, 1}, OUT1
190 * {0x6000, 0x05, 1}, OUT2
191 * {0x0000, 0x00, 2}, Gap
192 * {0x6000, 0x06, 4}, Device Status
193 */
194 ✗ inputPdoEntryIndex = pdoEntryIndex;
195 ✗ return true;
196 }
197
198 ✗ void registerInputs(ec_slave_config_t *sc, Domain *domainIn) override
199 {
200 ✗ domainIn_ = domainIn;
201 ✗ offFlow = ecrt_slave_config_reg_pdo_entry(
202 ✗ sc, inputPdoEntryIndex, 0x02, domainIn->getDomain(), NULL);
203 ✗ offTemp = ecrt_slave_config_reg_pdo_entry(
204 ✗ sc, inputPdoEntryIndex, 0x03, domainIn->getDomain(), NULL);
205 ✗ offDig = ecrt_slave_config_reg_pdo_entry(
206 ✗ sc, inputPdoEntryIndex, 0x04, domainIn->getDomain(), NULL);
207 ✗ if (offFlow < 0 || offTemp < 0 || offDig < 0) {
208 ✗ stringstream err;
209 ✗ err << "Failed to register IfmSU input PDO entry.";
210 ✗ throw runtime_error(err.str());
211 }
212 }
213
214 5 const std::vector<ChannelKind> &inputKinds() const override
215 {
216 5 return inputKinds_;
217 }
218 1 const std::vector<ChannelKind> &outputKinds() const override
219 {
220 1 return outputKinds_;
221 }
222
223 protected:
224 2 ChannelValue getInputAt(size_t i) const override
225 {
226
1/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 switch (i) {
227 2 case 0:
228 2 return flow;
229 ✗ case 1:
230 ✗ return temperature;
231 ✗ case 2:
232 ✗ return out1;
233 ✗ case 3:
234 ✗ return out2;
235 ✗ case 4:
236 ✗ return uint16_t(state);
237 ✗ default:
238 ✗ throw UnknownChannel("IfmSU: invalid channel index.");
239 }
240 }
241
242 ✗ void setOutputAt(size_t, const ChannelValue &) override
243 {
244 throw UnknownChannel(
245 ✗ "IfmSU has no generically exposed output channels.");
246 }
247
248 int offReset = -1;
249 unsigned int bitOffReset = 0;
250 bool reset = false; // never driven -- ported as-is from bda,
251 // which never exposed a way to set it either
252
253 int offFlow = -1;
254 float flow = 0.0f; // m^3/s
255 int offTemp = -1;
256 float temperature = 0.0f; // degC
257 int offDig = -1;
258 bool out1 = false;
259 bool out2 = false;
260 uint8_t state = 0;
261
262 protected:
263 Domain *domainOut_ = nullptr;
264 Domain *domainIn_ = nullptr;
265
266 private:
267 uint16_t outputPdoEntryIndex = 0x0000;
268 uint16_t inputPdoEntryIndex = 0x0000;
269
270 std::vector<ChannelKind> inputKinds_ {
271 {"Flow", 1, float(0)},
272 {"Temperature", 1, float(0)},
273 {"Out1", 1, false},
274 {"Out2", 1, false},
275 {"State", 1, uint16_t(0)}};
276 std::vector<ChannelKind> outputKinds_;
277 };
278
279 /****************************************************************************/
280
281 2 class IfmSU6020 : public IfmSU
282 {
283 public:
284 1 using IfmSU::IfmSU;
285
286 ✗ void addConfig(ec_slave_config_t *sc, uint16_t sdoIndex) override
287 {
288 // ifm-0005B3-20231003-IODD11-de.pdf, chapter "Kommunikation"
289 ✗ IoLinkPortIdentity identity;
290 ✗ identity.vendorId = 310;
291 ✗ identity.deviceId = 0x0005B3;
292 ✗ identity.minCycleTimeMs = 9.6;
293 ✗ identity.processDataInBits = 128;
294 ✗ identity.processDataOutBits = 8;
295 ✗ identity.sioSupported = true;
296 ✗ configurePort(sc, sdoIndex, identity);
297 }
298
299 1 void updateInputs() override
300 {
301 /*
302 * ifm-0005B3-20231003-IODD11-de.pdf
303 * Stroemung: [L/h] = MeasurementValue * 0.1
304 * Temperatur: [degC] (-4400 to 12400) * 0.01
305 * Status: 0 OK, 1 Wartung, 2 ausserhalb Spec, 3 Test, 4 Ausfall
306 */
307
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!domainIn_) {
308 1 return;
309 }
310 ✗ int32_t raw = ntohl(EC_READ_S32(domainIn_->getData() + offFlow));
311 ✗ flow = raw * 0.1f * LitersPerHourToSI;
312 ✗ raw = ntohl(EC_READ_S32(domainIn_->getData() + offTemp));
313 ✗ temperature = raw * 1e-2f;
314 ✗ uint8_t byte = EC_READ_U8(domainIn_->getData() + offDig);
315 ✗ out1 = (byte & 0x01) != 0x00;
316 ✗ out2 = (byte & 0x02) != 0x00;
317 ✗ state = (byte >> 4);
318 }
319 };
320
321 /****************************************************************************/
322
323 ✗ class IfmSU8020 : public IfmSU
324 {
325 public:
326 ✗ using IfmSU::IfmSU;
327
328 ✗ void addConfig(ec_slave_config_t *sc, uint16_t sdoIndex) override
329 {
330 // ifm-0005B4-20231020-IODD11-de.pdf, chapter "Kommunikation"
331 ✗ IoLinkPortIdentity identity;
332 ✗ identity.vendorId = 310;
333 ✗ identity.deviceId = 0x0005B4;
334 ✗ identity.minCycleTimeMs = 9.6;
335 ✗ identity.processDataInBits = 128;
336 ✗ identity.processDataOutBits = 8;
337 ✗ identity.sioSupported = true;
338 ✗ configurePort(sc, sdoIndex, identity);
339 }
340
341 ✗ void updateInputs() override
342 {
343 /*
344 * ifm-0005B4-20231020-IODD11-de.pdf
345 * Stroemung: [L/h] = MeasurementValue
346 * Temperatur: [degC] (-4400 to 12400) * 0.01
347 */
348 ✗ if (!domainIn_) {
349 ✗ return;
350 }
351 ✗ int32_t raw = ntohl(EC_READ_S32(domainIn_->getData() + offFlow));
352 ✗ flow = raw * LitersPerHourToSI;
353 ✗ raw = ntohl(EC_READ_S32(domainIn_->getData() + offTemp));
354 ✗ temperature = raw * 1e-2f;
355 ✗ uint8_t byte = EC_READ_U8(domainIn_->getData() + offDig);
356 ✗ out1 = (byte & 0x01) != 0x00;
357 ✗ out2 = (byte & 0x02) != 0x00;
358 ✗ state = (byte >> 4);
359 }
360 };
361
362 /****************************************************************************/
363 // ifm PV8004 (pressure/temperature sensor, no outputs).
364 /****************************************************************************/
365
366 ✗ class IfmPV8004 : public IoLinkDevice
367 {
368 public:
369 ✗ IfmPV8004(pdserv *pdServ, pdtask *task, const string &prefix) :
370 ✗ IoLinkDevice(pdServ, task, prefix)
371 {
372 ✗ registerInputChannel("Pressure", pressure);
373 ✗ registerInputChannel("Temperature", temperature);
374 ✗ registerInputChannel("Out1", out1);
375 ✗ registerInputChannel("Out2", out2);
376 ✗ registerInputChannel("State", state);
377 }
378
379 ✗ void addConfig(ec_slave_config_t *sc, uint16_t sdoIndex) override
380 {
381 // ifm-0004BA-20200507-IODD11-de.pdf, chapter "Kommunikation"
382 ✗ IoLinkPortIdentity identity;
383 ✗ identity.vendorId = 310;
384 ✗ identity.deviceId = 0x0004BA;
385 ✗ identity.minCycleTimeMs = 4.5;
386 ✗ identity.processDataInBits = 64;
387 ✗ identity.sioSupported = true;
388 ✗ configurePort(sc, sdoIndex, identity);
389 }
390
391 ✗ bool addInputs(
392 ec_slave_config_t *sc,
393 uint16_t pdoIndex,
394 uint16_t pdoEntryIndex,
395 Domain *) override
396 {
397 /* ifm PV8004 Inputs
398 * {0x6000, 0x01, 16}, Pressure (INT)
399 * {0x0000, 0x00, 16}, Gap
400 * {0x6000, 0x02, 16}, Temperature (INT)
401 * {0x0000, 0x00, 8}, Gap
402 * {0x6000, 0x03, 1}, OUT1
403 * {0x6000, 0x04, 1}, OUT2
404 * {0x0000, 0x00, 2}, Gap
405 * {0x6000, 0x05, 4}, Device Status
406 */
407 ✗ if (ecrt_slave_config_pdo_assign_add(sc, 3, pdoIndex)) {
408 ✗ stringstream err;
409 ✗ err << "Failed to assign PDO " << pdoIndex;
410 ✗ throw runtime_error(err.str());
411 }
412 ✗ if (ecrt_slave_config_pdo_mapping_clear(sc, pdoIndex)) {
413 ✗ stringstream err;
414 ✗ err << "Failed to clear PDO mapping of " << pdoIndex;
415 ✗ throw runtime_error(err.str());
416 }
417 ✗ if (ecrt_slave_config_pdo_mapping_add(
418 sc, pdoIndex, pdoEntryIndex, 0x01, 16)
419 ✗ || ecrt_slave_config_pdo_mapping_add(
420 sc, pdoIndex, 0x0000, 0x00, 16)
421 ✗ || ecrt_slave_config_pdo_mapping_add(
422 sc, pdoIndex, pdoEntryIndex, 0x02, 16)
423 ✗ || ecrt_slave_config_pdo_mapping_add(
424 sc, pdoIndex, 0x0000, 0x00, 8)
425 ✗ || ecrt_slave_config_pdo_mapping_add(
426 sc, pdoIndex, pdoEntryIndex, 0x03, 1)
427 ✗ || ecrt_slave_config_pdo_mapping_add(
428 sc, pdoIndex, pdoEntryIndex, 0x04, 1)
429 ✗ || ecrt_slave_config_pdo_mapping_add(
430 sc, pdoIndex, 0x0000, 0x00, 2)
431 ✗ || ecrt_slave_config_pdo_mapping_add(
432 sc, pdoIndex, pdoEntryIndex, 0x05, 4)) {
433 ✗ stringstream err;
434 ✗ err << "Failed to map PDO entry in " << pdoIndex;
435 ✗ throw runtime_error(err.str());
436 }
437
438 ✗ inputPdoEntryIndex = pdoEntryIndex;
439 ✗ return true;
440 }
441
442 ✗ void registerInputs(ec_slave_config_t *sc, Domain *domainIn) override
443 {
444 ✗ domainIn_ = domainIn;
445 ✗ offPressure = ecrt_slave_config_reg_pdo_entry(
446 ✗ sc, inputPdoEntryIndex, 0x01, domainIn->getDomain(), NULL);
447 ✗ offTemp = ecrt_slave_config_reg_pdo_entry(
448 ✗ sc, inputPdoEntryIndex, 0x02, domainIn->getDomain(), NULL);
449 ✗ offDig = ecrt_slave_config_reg_pdo_entry(
450 ✗ sc, inputPdoEntryIndex, 0x03, domainIn->getDomain(), NULL);
451 ✗ if (offPressure < 0 || offTemp < 0 || offDig < 0) {
452 ✗ stringstream err;
453 ✗ err << "Failed to register IfmPV8004 input PDO entry.";
454 ✗ throw runtime_error(err.str());
455 }
456 }
457
458 ✗ void updateInputs() override
459 {
460 /*
461 * ifm-0004BA-20200507-IODD11-de.pdf
462 * Druck: [bar] (-1000 to 10500) * 0.001
463 * Temperatur: [degC] (-4500 to 9500) * 0.01
464 */
465 ✗ if (!domainIn_) {
466 ✗ return;
467 }
468 ✗ int16_t raw = ntohs(EC_READ_S16(domainIn_->getData() + offPressure));
469 ✗ pressure = raw * 1e-3f * BarToSI;
470 ✗ raw = ntohs(EC_READ_S16(domainIn_->getData() + offTemp));
471 ✗ temperature = raw * 1e-2f;
472 ✗ uint8_t byte = EC_READ_U8(domainIn_->getData() + offDig);
473 ✗ out1 = (byte & 0x01) != 0x00;
474 ✗ out2 = (byte & 0x02) != 0x00;
475 ✗ state = (byte >> 4);
476 }
477 ✗ void updateOutputs() override {}
478
479 ✗ const std::vector<ChannelKind> &inputKinds() const override
480 {
481 ✗ return inputKinds_;
482 }
483 ✗ const std::vector<ChannelKind> &outputKinds() const override
484 {
485 ✗ return outputKinds_;
486 }
487
488 protected:
489 ✗ ChannelValue getInputAt(size_t i) const override
490 {
491 ✗ switch (i) {
492 ✗ case 0:
493 ✗ return pressure;
494 ✗ case 1:
495 ✗ return temperature;
496 ✗ case 2:
497 ✗ return out1;
498 ✗ case 3:
499 ✗ return out2;
500 ✗ case 4:
501 ✗ return uint16_t(state);
502 ✗ default:
503 ✗ throw UnknownChannel("IfmPV8004: invalid channel index.");
504 }
505 }
506 ✗ void setOutputAt(size_t, const ChannelValue &) override
507 {
508 ✗ throw UnknownChannel("IfmPV8004 has no output channels.");
509 }
510
511 private:
512 int offPressure = -1;
513 float pressure = 0.0f; // Pa
514 int offTemp = -1;
515 float temperature = 0.0f; // degC
516 int offDig = -1;
517 bool out1 = false;
518 bool out2 = false;
519 uint8_t state = 0;
520
521 uint16_t inputPdoEntryIndex = 0x0000;
522 Domain *domainIn_ = nullptr;
523
524 std::vector<ChannelKind> inputKinds_ {
525 {"Pressure", 1, float(0)},
526 {"Temperature", 1, float(0)},
527 {"Out1", 1, false},
528 {"Out2", 1, false},
529 {"State", 1, uint16_t(0)}};
530 std::vector<ChannelKind> outputKinds_;
531 };
532
533 /****************************************************************************/
534 // ifm DV2131 (buzzer + LED outputs, feedback + status inputs).
535 /****************************************************************************/
536
537 ✗ class IfmDV2131 : public IoLinkDevice
538 {
539 public:
540 ✗ IfmDV2131(pdserv *pdServ, pdtask *task, const string &prefix) :
541 ✗ IoLinkDevice(pdServ, task, prefix)
542 {
543 ✗ registerInputChannel("Feedback", feedback);
544 ✗ registerInputChannel("Status", status);
545 ✗ registerOutputChannel("EnableBuzzer", enableBuzzer);
546 ✗ registerOutputChannel("BuzzerStyle", buzzerStyle);
547 ✗ registerOutputChannel("LedStates", ledStates);
548 }
549
550 ✗ void addConfig(ec_slave_config_t *sc, uint16_t sdoIndex) override
551 {
552 ✗ IoLinkPortIdentity identity;
553 ✗ identity.vendorId = 310;
554 ✗ identity.deviceId = 0x000498;
555 ✗ identity.minCycleTimeMs = 8.0;
556 ✗ identity.processDataInBits = 16;
557 ✗ identity.processDataOutBits = 16;
558 ✗ identity.sioSupported = true;
559 ✗ configurePort(sc, sdoIndex, identity);
560 }
561
562 ✗ bool addOutputs(
563 ec_slave_config_t *sc,
564 uint16_t pdoIndex,
565 uint16_t pdoEntryIndex,
566 Domain *) override
567 {
568 ✗ if (ecrt_slave_config_pdo_assign_add(sc, 2, pdoIndex)) {
569 ✗ stringstream err;
570 ✗ err << "Failed to assign PDO " << pdoIndex;
571 ✗ throw runtime_error(err.str());
572 }
573 ✗ if (ecrt_slave_config_pdo_mapping_clear(sc, pdoIndex)) {
574 ✗ stringstream err;
575 ✗ err << "Failed to clear PDO mapping of " << pdoIndex;
576 ✗ throw runtime_error(err.str());
577 }
578 ✗ if (ecrt_slave_config_pdo_mapping_add(
579 sc, pdoIndex, pdoEntryIndex, 0x01, 1) // buzzer
580 ✗ || ecrt_slave_config_pdo_mapping_add(
581 sc, pdoIndex, 0x0000, 0x00, 3)
582 ✗ || ecrt_slave_config_pdo_mapping_add(
583 sc, pdoIndex, pdoEntryIndex, 0x02, 3) // style
584 ✗ || ecrt_slave_config_pdo_mapping_add(
585 sc, pdoIndex, 0x0000, 0x00, 1)
586 ✗ || ecrt_slave_config_pdo_mapping_add(
587 sc, pdoIndex, pdoEntryIndex, 0x03, 1) // led-a
588 ✗ || ecrt_slave_config_pdo_mapping_add(
589 sc, pdoIndex, pdoEntryIndex, 0x04, 1) // led-b
590 ✗ || ecrt_slave_config_pdo_mapping_add(
591 sc, pdoIndex, pdoEntryIndex, 0x05, 1) // led-c
592 ✗ || ecrt_slave_config_pdo_mapping_add(
593 sc, pdoIndex, 0x0000, 0x00, 5)) {
594 ✗ stringstream err;
595 ✗ err << "Failed to map PDO entry in " << pdoIndex;
596 ✗ throw runtime_error(err.str());
597 }
598
599 ✗ outputPdoEntryIndex = pdoEntryIndex;
600 ✗ return true;
601 }
602
603 ✗ bool addInputs(
604 ec_slave_config_t *sc,
605 uint16_t pdoIndex,
606 uint16_t pdoEntryIndex,
607 Domain *) override
608 {
609 ✗ if (ecrt_slave_config_pdo_assign_add(sc, 3, pdoIndex)) {
610 ✗ stringstream err;
611 ✗ err << "Failed to assign PDO " << pdoIndex;
612 ✗ throw runtime_error(err.str());
613 }
614 ✗ if (ecrt_slave_config_pdo_mapping_clear(sc, pdoIndex)) {
615 ✗ stringstream err;
616 ✗ err << "Failed to clear PDO mapping of " << pdoIndex;
617 ✗ throw runtime_error(err.str());
618 }
619 ✗ if (ecrt_slave_config_pdo_mapping_add(
620 sc, pdoIndex, pdoEntryIndex, 0x01, 1) // feedback
621 ✗ || ecrt_slave_config_pdo_mapping_add(
622 sc, pdoIndex, 0x0000, 0x00, 7)
623 ✗ || ecrt_slave_config_pdo_mapping_add(
624 sc, pdoIndex, pdoEntryIndex, 0x02, 1) // status1
625 ✗ || ecrt_slave_config_pdo_mapping_add(
626 sc, pdoIndex, pdoEntryIndex, 0x03, 1) // status2
627 ✗ || ecrt_slave_config_pdo_mapping_add(
628 sc, pdoIndex, pdoEntryIndex, 0x04, 1) // status3
629 ✗ || ecrt_slave_config_pdo_mapping_add(
630 sc, pdoIndex, pdoEntryIndex, 0x05, 1) // status4
631 ✗ || ecrt_slave_config_pdo_mapping_add(
632 sc, pdoIndex, pdoEntryIndex, 0x06, 1) // status5
633 ✗ || ecrt_slave_config_pdo_mapping_add(
634 sc, pdoIndex, pdoEntryIndex, 0x07, 1) // status6
635 ✗ || ecrt_slave_config_pdo_mapping_add(
636 sc, pdoIndex, pdoEntryIndex, 0x08, 1) // status7
637 ✗ || ecrt_slave_config_pdo_mapping_add(
638 sc, pdoIndex, pdoEntryIndex, 0x09, 1)) { // st.8
639 ✗ stringstream err;
640 ✗ err << "Failed to map PDO entry in " << pdoIndex;
641 ✗ throw runtime_error(err.str());
642 }
643
644 ✗ inputPdoEntryIndex = pdoEntryIndex;
645 ✗ return true;
646 }
647
648 ✗ void registerOutputs(ec_slave_config_t *sc, Domain *domainOut) override
649 {
650 ✗ domainOut_ = domainOut;
651 ✗ offBuzzer = ecrt_slave_config_reg_pdo_entry(
652 ✗ sc, outputPdoEntryIndex, 0x01, domainOut->getDomain(), NULL);
653 ✗ offLed = ecrt_slave_config_reg_pdo_entry(
654 ✗ sc, outputPdoEntryIndex, 0x03, domainOut->getDomain(), NULL);
655 ✗ if (offBuzzer < 0 || offLed < 0) {
656 ✗ stringstream err;
657 ✗ err << "Failed to register IfmDV2131 output PDO entry.";
658 ✗ throw runtime_error(err.str());
659 }
660 }
661
662 ✗ void registerInputs(ec_slave_config_t *sc, Domain *domainIn) override
663 {
664 ✗ domainIn_ = domainIn;
665 ✗ offFeedback = ecrt_slave_config_reg_pdo_entry(
666 ✗ sc, inputPdoEntryIndex, 0x01, domainIn->getDomain(), NULL);
667 ✗ offStatus = ecrt_slave_config_reg_pdo_entry(
668 ✗ sc, inputPdoEntryIndex, 0x02, domainIn->getDomain(), NULL);
669 ✗ if (offFeedback < 0 || offStatus < 0) {
670 ✗ stringstream err;
671 ✗ err << "Failed to register IfmDV2131 input PDO entry.";
672 ✗ throw runtime_error(err.str());
673 }
674 }
675
676 ✗ void updateInputs() override
677 {
678 ✗ if (!domainIn_ || !domainIn_->getData()) {
679 ✗ return;
680 }
681 ✗ feedback = EC_READ_U8(domainIn_->getData() + offFeedback) & 0x01;
682 ✗ status = EC_READ_U8(domainIn_->getData() + offStatus);
683 }
684
685 ✗ void updateOutputs() override
686 {
687 ✗ if (!domainOut_ || !domainOut_->getData()) {
688 ✗ return;
689 }
690 ✗ EC_WRITE_U8(
691 domainOut_->getData() + offBuzzer,
692 enableBuzzer | (buzzerStyle << 4));
693 ✗ EC_WRITE_U8(domainOut_->getData() + offLed, ledStates);
694 }
695
696 ✗ const std::vector<ChannelKind> &inputKinds() const override
697 {
698 ✗ return inputKinds_;
699 }
700 ✗ const std::vector<ChannelKind> &outputKinds() const override
701 {
702 ✗ return outputKinds_;
703 }
704
705 protected:
706 ✗ ChannelValue getInputAt(size_t i) const override
707 {
708 ✗ switch (i) {
709 ✗ case 0:
710 ✗ return uint16_t(feedback);
711 ✗ case 1:
712 ✗ return uint16_t(status);
713 ✗ default:
714 ✗ throw UnknownChannel("IfmDV2131: invalid channel index.");
715 }
716 }
717 ✗ void setOutputAt(size_t i, const ChannelValue &value) override
718 {
719 ✗ switch (i) {
720 ✗ case 0:
721 ✗ enableBuzzer = std::get<uint16_t>(value) ? 1 : 0;
722 ✗ return;
723 ✗ case 1:
724 ✗ buzzerStyle = static_cast<uint8_t>(std::get<uint16_t>(value));
725 ✗ return;
726 ✗ case 2:
727 ✗ ledStates = static_cast<uint8_t>(std::get<uint16_t>(value));
728 ✗ return;
729 ✗ default:
730 ✗ throw UnknownChannel("IfmDV2131: invalid channel index.");
731 }
732 }
733
734 private:
735 int offBuzzer = -1;
736 uint8_t enableBuzzer = 0;
737 uint8_t buzzerStyle = 0;
738
739 int offLed = -1;
740 uint8_t ledStates = 0;
741
742 int offFeedback = -1;
743 uint8_t feedback = 0;
744
745 int offStatus = -1;
746 uint8_t status = 0;
747
748 uint16_t outputPdoEntryIndex = 0x0000;
749 uint16_t inputPdoEntryIndex = 0x0000;
750 Domain *domainOut_ = nullptr;
751 Domain *domainIn_ = nullptr;
752
753 std::vector<ChannelKind> inputKinds_ {
754 {"Feedback", 1, uint16_t(0)},
755 {"Status", 1, uint16_t(0)}};
756 std::vector<ChannelKind> outputKinds_ {
757 {"EnableBuzzer", 1, uint16_t(0)},
758 {"BuzzerStyle", 1, uint16_t(0)},
759 {"LedStates", 1, uint16_t(0)}};
760 };
761
762 /****************************************************************************/
763 // ifm SM4000 (flow/temperature sensor, inputs only, single combined
764 // 64-bit process value -- no digital outputs, no device-status byte).
765 /****************************************************************************/
766
767 ✗ class IfmSM4000 : public IoLinkDevice
768 {
769 public:
770 ✗ IfmSM4000(pdserv *pdServ, pdtask *task, const string &prefix) :
771 ✗ IoLinkDevice(pdServ, task, prefix)
772 {
773 ✗ registerInputChannel("Flow", flow);
774 ✗ registerInputChannel("Temperature", temperature);
775 }
776
777 ✗ void addConfig(ec_slave_config_t *sc, uint16_t sdoIndex) override
778 {
779 // ifm-SM4000-20170209-IODD11-de.pdf, chapter "Kommunikation"
780 ✗ IoLinkPortIdentity identity;
781 ✗ identity.vendorId = 310;
782 ✗ identity.deviceId = 0x00029F;
783 ✗ identity.minCycleTimeMs = 5.0;
784 ✗ identity.processDataInBits = 64;
785 ✗ identity.sioSupported = true;
786 ✗ configurePort(sc, sdoIndex, identity);
787 }
788
789 ✗ bool addInputs(
790 ec_slave_config_t *sc,
791 uint16_t pdoIndex,
792 uint16_t pdoEntryIndex,
793 Domain *) override
794 {
795 /*
796 * IO-Link Port1: process data, ARRAY [0..7] OF USINT (64 bit)
797 */
798 ✗ if (ecrt_slave_config_pdo_assign_add(sc, 3, pdoIndex)) {
799 ✗ stringstream err;
800 ✗ err << "Failed to assign PDO " << pdoIndex;
801 ✗ throw runtime_error(err.str());
802 }
803 ✗ if (ecrt_slave_config_pdo_mapping_clear(sc, pdoIndex)) {
804 ✗ stringstream err;
805 ✗ err << "Failed to clear PDO mapping of " << pdoIndex;
806 ✗ throw runtime_error(err.str());
807 }
808 ✗ if (ecrt_slave_config_pdo_mapping_add(
809 ✗ sc, pdoIndex, pdoEntryIndex, 0x01, 64)) {
810 ✗ stringstream err;
811 ✗ err << "Failed to map PDO entry in " << pdoIndex;
812 ✗ throw runtime_error(err.str());
813 }
814
815 ✗ inputPdoEntryIndex = pdoEntryIndex;
816 ✗ return true;
817 }
818
819 ✗ void registerInputs(ec_slave_config_t *sc, Domain *domainIn) override
820 {
821 ✗ domainIn_ = domainIn;
822 ✗ offIn = ecrt_slave_config_reg_pdo_entry(
823 ✗ sc, inputPdoEntryIndex, 0x01, domainIn->getDomain(), NULL);
824 ✗ if (offIn < 0) {
825 ✗ stringstream err;
826 ✗ err << "Failed to register IfmSM4000 input PDO entry.";
827 ✗ throw runtime_error(err.str());
828 }
829 }
830
831 ✗ void updateInputs() override
832 {
833 /*
834 * ifm-SM4000-20170209-IODD11-de.pdf
835 * Durchfluss: [L/min] (-1999 to 3600) * 0.001
836 * Temperatur: 14 bit (LSB sind OUT1 und OUT2), * 0.1 degC
837 */
838 ✗ if (!domainIn_) {
839 ✗ return;
840 }
841 ✗ int16_t raw = ntohs(EC_READ_S16(domainIn_->getData() + offIn + 4));
842 ✗ flow = raw * 0.06f * LitersPerHourToSI;
843 ✗ uint16_t uraw = ntohs(EC_READ_U16(domainIn_->getData() + offIn + 6));
844 // arithmetic shift on the *signed* word sign-extends the 14-bit
845 // temperature field correctly; OUT1/OUT2 are its two LSBs.
846 ✗ temperature = (static_cast<int16_t>(uraw) >> 2) * 0.1f;
847 }
848 ✗ void updateOutputs() override {}
849
850 ✗ const std::vector<ChannelKind> &inputKinds() const override
851 {
852 ✗ return inputKinds_;
853 }
854 ✗ const std::vector<ChannelKind> &outputKinds() const override
855 {
856 ✗ return outputKinds_;
857 }
858
859 protected:
860 ✗ ChannelValue getInputAt(size_t i) const override
861 {
862 ✗ switch (i) {
863 ✗ case 0:
864 ✗ return flow;
865 ✗ case 1:
866 ✗ return temperature;
867 ✗ default:
868 ✗ throw UnknownChannel("IfmSM4000: invalid channel index.");
869 }
870 }
871 ✗ void setOutputAt(size_t, const ChannelValue &) override
872 {
873 ✗ throw UnknownChannel("IfmSM4000 has no output channels.");
874 }
875
876 private:
877 int offIn = -1;
878 float flow = 0.0f; // m^3/s
879 float temperature = 0.0f; // degC
880
881 uint16_t inputPdoEntryIndex = 0x0000;
882 Domain *domainIn_ = nullptr;
883
884 std::vector<ChannelKind> inputKinds_ {
885 {"Flow", 1, float(0)},
886 {"Temperature", 1, float(0)}};
887 std::vector<ChannelKind> outputKinds_;
888 };
889
890 /****************************************************************************/
891 // ifm SM7000 (flow/temperature sensor with totalizer, inputs only --
892 // same wire layout as SM4000 plus a leading 32-bit float totalizer).
893 /****************************************************************************/
894
895 ✗ class IfmSM7000 : public IoLinkDevice
896 {
897 public:
898 ✗ IfmSM7000(pdserv *pdServ, pdtask *task, const string &prefix) :
899 ✗ IoLinkDevice(pdServ, task, prefix)
900 {
901 ✗ registerInputChannel("Totalizer", totalizer);
902 ✗ registerInputChannel("Flow", flow);
903 ✗ registerInputChannel("Temperature", temperature);
904 ✗ registerInputChannel("Out1", out1);
905 ✗ registerInputChannel("Out2", out2);
906 }
907
908 ✗ void addConfig(ec_slave_config_t *sc, uint16_t sdoIndex) override
909 {
910 // ifm-SM7000-20161103-IODD11-de.pdf, page 1 ("SM") and page 2
911 // ("Gesamt Bitlaenge = 64")
912 ✗ IoLinkPortIdentity identity;
913 ✗ identity.vendorId = 310;
914 ✗ identity.deviceId = 0x00023C;
915 ✗ identity.minCycleTimeMs = 5.0;
916 ✗ identity.processDataInBits = 64;
917 ✗ identity.sioSupported = true;
918 ✗ configurePort(sc, sdoIndex, identity);
919 }
920
921 ✗ bool addInputs(
922 ec_slave_config_t *sc,
923 uint16_t pdoIndex,
924 uint16_t pdoEntryIndex,
925 Domain *) override
926 {
927 /*
928 * IO-Link Port1: process data, ARRAY [0..7] OF USINT (64 bit)
929 */
930 ✗ if (ecrt_slave_config_pdo_assign_add(sc, 3, pdoIndex)) {
931 ✗ stringstream err;
932 ✗ err << "Failed to assign PDO " << pdoIndex;
933 ✗ throw runtime_error(err.str());
934 }
935 ✗ if (ecrt_slave_config_pdo_mapping_clear(sc, pdoIndex)) {
936 ✗ stringstream err;
937 ✗ err << "Failed to clear PDO mapping of " << pdoIndex;
938 ✗ throw runtime_error(err.str());
939 }
940 ✗ if (ecrt_slave_config_pdo_mapping_add(
941 ✗ sc, pdoIndex, pdoEntryIndex, 0x01, 64)) {
942 ✗ stringstream err;
943 ✗ err << "Failed to map PDO entry in " << pdoIndex;
944 ✗ throw runtime_error(err.str());
945 }
946
947 ✗ inputPdoEntryIndex = pdoEntryIndex;
948 ✗ return true;
949 }
950
951 ✗ void registerInputs(ec_slave_config_t *sc, Domain *domainIn) override
952 {
953 ✗ domainIn_ = domainIn;
954 ✗ offIn = ecrt_slave_config_reg_pdo_entry(
955 ✗ sc, inputPdoEntryIndex, 0x01, domainIn->getDomain(), NULL);
956 ✗ if (offIn < 0) {
957 ✗ stringstream err;
958 ✗ err << "Failed to register IfmSM7000 input PDO entry.";
959 ✗ throw runtime_error(err.str());
960 }
961 }
962
963 ✗ void updateInputs() override
964 {
965 /*
966 * ifm-SM7000-20161103-IODD11-de.pdf, Gesamt Bitlaenge = 64
967 * Totalisator: Float32T (IEEE754), unveraendert seit Reset, [l]
968 * Durchfluss: [l/min] (-6000 to 6000) * 0.01
969 * Temperatur: 14 bit (LSB sind OUT1 und OUT2), * 0.1 degC
970 */
971 ✗ if (!domainIn_) {
972 ✗ return;
973 }
974 ✗ uint32_t totalBits = ntohl(EC_READ_U32(domainIn_->getData() + offIn));
975 float totalLiters;
976 ✗ std::memcpy(&totalLiters, &totalBits, sizeof(totalLiters));
977 ✗ totalizer = totalLiters * 1e-3f; // l -> m^3
978
979 ✗ int16_t raw = ntohs(EC_READ_S16(domainIn_->getData() + offIn + 4));
980 ✗ flow = raw * 0.01f * LitersPerMinuteToSI;
981
982 ✗ uint16_t uraw = ntohs(EC_READ_U16(domainIn_->getData() + offIn + 6));
983 // arithmetic shift on the *signed* word sign-extends the 14-bit
984 // temperature field correctly; OUT1/OUT2 are its two LSBs.
985 ✗ temperature = (static_cast<int16_t>(uraw) >> 2) * 0.1f;
986 ✗ out1 = (uraw & 0x0001) != 0x0000;
987 ✗ out2 = (uraw & 0x0002) != 0x0000;
988 }
989 ✗ void updateOutputs() override {}
990
991 ✗ const std::vector<ChannelKind> &inputKinds() const override
992 {
993 ✗ return inputKinds_;
994 }
995 ✗ const std::vector<ChannelKind> &outputKinds() const override
996 {
997 ✗ return outputKinds_;
998 }
999
1000 protected:
1001 ✗ ChannelValue getInputAt(size_t i) const override
1002 {
1003 ✗ switch (i) {
1004 ✗ case 0:
1005 ✗ return totalizer;
1006 ✗ case 1:
1007 ✗ return flow;
1008 ✗ case 2:
1009 ✗ return temperature;
1010 ✗ case 3:
1011 ✗ return out1;
1012 ✗ case 4:
1013 ✗ return out2;
1014 ✗ default:
1015 ✗ throw UnknownChannel("IfmSM7000: invalid channel index.");
1016 }
1017 }
1018 ✗ void setOutputAt(size_t, const ChannelValue &) override
1019 {
1020 ✗ throw UnknownChannel("IfmSM7000 has no output channels.");
1021 }
1022
1023 private:
1024 int offIn = -1;
1025 float totalizer = 0.0f; // m^3
1026 float flow = 0.0f; // m^3/s
1027 float temperature = 0.0f; // degC
1028 bool out1 = false;
1029 bool out2 = false;
1030
1031 uint16_t inputPdoEntryIndex = 0x0000;
1032 Domain *domainIn_ = nullptr;
1033
1034 std::vector<ChannelKind> inputKinds_ {
1035 {"Totalizer", 1, float(0)},
1036 {"Flow", 1, float(0)},
1037 {"Temperature", 1, float(0)},
1038 {"Out1", 1, false},
1039 {"Out2", 1, false}};
1040 std::vector<ChannelKind> outputKinds_;
1041 };
1042
1043 /****************************************************************************/
1044 // ifm TA2105/TA2115/TA2135/TA2145/TA2345/TA2105/TA2405/TA2415/TA2435/
1045 // TA2445 (temperature transmitter, inputs only). All nine order codes
1046 // share one IO-Link device profile (same Vendor/Device ID, identical
1047 // process data) and differ only mechanically (probe length, process
1048 // connection) -- see ifm-000179-20200114-IODD11-de.pdf, chapter 1.
1049 /****************************************************************************/
1050
1051 ✗ class IfmTATransmitter : public IoLinkDevice
1052 {
1053 public:
1054 ✗ IfmTATransmitter(pdserv *pdServ, pdtask *task, const string &prefix) :
1055 ✗ IoLinkDevice(pdServ, task, prefix)
1056 {
1057 ✗ registerInputChannel("Temperature", temperature);
1058 }
1059
1060 ✗ void addConfig(ec_slave_config_t *sc, uint16_t sdoIndex) override
1061 {
1062 // ifm-000179-20200114-IODD11-de.pdf, chapter 2
1063 // ("Kommunikation") and chapter 6.1 (process data). IO-Link
1064 // revision isn't spelt out as a field in this IODD, but the
1065 // "IO-Link 1.1 Systemtest" system commands imply V1.1 (the
1066 // default configurePort() assumes).
1067 ✗ IoLinkPortIdentity identity;
1068 ✗ identity.vendorId = 310;
1069 ✗ identity.deviceId = 0x000179;
1070 ✗ identity.minCycleTimeMs = 2.3;
1071 ✗ identity.processDataInBits = 16;
1072 ✗ identity.sioSupported = false;
1073 ✗ configurePort(sc, sdoIndex, identity);
1074 }
1075
1076 ✗ bool addInputs(
1077 ec_slave_config_t *sc,
1078 uint16_t pdoIndex,
1079 uint16_t pdoEntryIndex,
1080 Domain *) override
1081 {
1082 /* ifm TA2xxx Inputs
1083 * {0x6000, 0x01, 16}, Temperature (INT)
1084 */
1085 ✗ if (ecrt_slave_config_pdo_assign_add(sc, 3, pdoIndex)) {
1086 ✗ stringstream err;
1087 ✗ err << "Failed to assign PDO " << pdoIndex;
1088 ✗ throw runtime_error(err.str());
1089 }
1090 ✗ if (ecrt_slave_config_pdo_mapping_clear(sc, pdoIndex)) {
1091 ✗ stringstream err;
1092 ✗ err << "Failed to clear PDO mapping of " << pdoIndex;
1093 ✗ throw runtime_error(err.str());
1094 }
1095 ✗ if (ecrt_slave_config_pdo_mapping_add(
1096 ✗ sc, pdoIndex, pdoEntryIndex, 0x01, 16)) {
1097 ✗ stringstream err;
1098 ✗ err << "Failed to map PDO entry in " << pdoIndex;
1099 ✗ throw runtime_error(err.str());
1100 }
1101
1102 ✗ inputPdoEntryIndex = pdoEntryIndex;
1103 ✗ return true;
1104 }
1105
1106 ✗ void registerInputs(ec_slave_config_t *sc, Domain *domainIn) override
1107 {
1108 ✗ domainIn_ = domainIn;
1109 ✗ offTemp = ecrt_slave_config_reg_pdo_entry(
1110 ✗ sc, inputPdoEntryIndex, 0x01, domainIn->getDomain(), NULL);
1111 ✗ if (offTemp < 0) {
1112 ✗ stringstream err;
1113 ✗ err << "Failed to register IfmTATransmitter input PDO entry.";
1114 ✗ throw runtime_error(err.str());
1115 }
1116 }
1117
1118 ✗ void updateInputs() override
1119 {
1120 /*
1121 * ifm-000179-20200114-IODD11-de.pdf, chapter 6.1
1122 * Temperatur: [degC] (-536 to 1574) * 0.1
1123 */
1124 ✗ if (!domainIn_) {
1125 ✗ return;
1126 }
1127 ✗ int16_t raw = ntohs(EC_READ_S16(domainIn_->getData() + offTemp));
1128 ✗ temperature = raw * 0.1f;
1129 }
1130 ✗ void updateOutputs() override {}
1131
1132 ✗ const std::vector<ChannelKind> &inputKinds() const override
1133 {
1134 ✗ return inputKinds_;
1135 }
1136 ✗ const std::vector<ChannelKind> &outputKinds() const override
1137 {
1138 ✗ return outputKinds_;
1139 }
1140
1141 protected:
1142 ✗ ChannelValue getInputAt(size_t i) const override
1143 {
1144 ✗ switch (i) {
1145 ✗ case 0:
1146 ✗ return temperature;
1147 ✗ default:
1148 throw UnknownChannel(
1149 ✗ "IfmTATransmitter: invalid channel index.");
1150 }
1151 }
1152 ✗ void setOutputAt(size_t, const ChannelValue &) override
1153 {
1154 ✗ throw UnknownChannel("IfmTATransmitter has no output channels.");
1155 }
1156
1157 private:
1158 int offTemp = -1;
1159 float temperature = 0.0f; // degC
1160
1161 uint16_t inputPdoEntryIndex = 0x0000;
1162 Domain *domainIn_ = nullptr;
1163
1164 std::vector<ChannelKind> inputKinds_ {{"Temperature", 1, float(0)}};
1165 std::vector<ChannelKind> outputKinds_;
1166 };
1167
1168 /****************************************************************************/
1169
1170
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
13 ECAPP_REGISTER_IOLINK_DEVICE("ifm-SU6020", IfmSU6020);
1171
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 ECAPP_REGISTER_IOLINK_DEVICE("ifm-SU8020", IfmSU8020);
1172
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 ECAPP_REGISTER_IOLINK_DEVICE("ifm-PV8004", IfmPV8004);
1173
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 ECAPP_REGISTER_IOLINK_DEVICE("ifm-DV2131", IfmDV2131);
1174
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 ECAPP_REGISTER_IOLINK_DEVICE("ifm-SM4000", IfmSM4000);
1175
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 ECAPP_REGISTER_IOLINK_DEVICE("ifm-SM7000", IfmSM7000);
1176
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 ECAPP_REGISTER_IOLINK_DEVICE("ifm-TA2105", IfmTATransmitter);
1177
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 ECAPP_REGISTER_IOLINK_DEVICE("ifm-TA2115", IfmTATransmitter);
1178
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 ECAPP_REGISTER_IOLINK_DEVICE("ifm-TA2135", IfmTATransmitter);
1179
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 ECAPP_REGISTER_IOLINK_DEVICE("ifm-TA2145", IfmTATransmitter);
1180
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 ECAPP_REGISTER_IOLINK_DEVICE("ifm-TA2345", IfmTATransmitter);
1181
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 ECAPP_REGISTER_IOLINK_DEVICE("ifm-TA2405", IfmTATransmitter);
1182
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 ECAPP_REGISTER_IOLINK_DEVICE("ifm-TA2415", IfmTATransmitter);
1183
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 ECAPP_REGISTER_IOLINK_DEVICE("ifm-TA2435", IfmTATransmitter);
1184
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
12 ECAPP_REGISTER_IOLINK_DEVICE("ifm-TA2445", IfmTATransmitter);
1185
1186 36 } // namespace EcApp
1187
1188 /****************************************************************************/
1189