GCC Code Coverage Report


Directory: ./
File: src/devices/IfmIoLink.cpp
Date: 2026-10-12 15:11:37
Exec Total Coverage
Lines: 39 563 6.9%
Branches: 84 906 9.3%

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