GCC Code Coverage Report


Directory: ./
File: src/devices/IfmIoLink.cpp
Date: 2026-09-03 16:05:57
Exec Total Coverage
Lines: 19 407 4.7%
Branches: 30 630 4.8%

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 <cstdio>
36 #include <cstring>
37 #include <memory>
38 #include <sstream>
39 #include <stdexcept>
40 #include <string>
41 #include <vector>
42
43 using std::runtime_error;
44 using std::size_t;
45 using std::string;
46 using std::stringstream;
47
48 /****************************************************************************/
49
50 namespace EcApp {
51
52 namespace {
53
54 /** L/h -> m^3/s. */
55 constexpr float LitersPerHourToSI = 1.0f / 3.6e6f;
56
57 /** bar -> Pa. */
58 constexpr float BarToSI = 1e5f;
59
60 /** Turns a hex-digit-pair string (as pasted from an SDO dump) into raw
61 * bytes, for ecrt_slave_config_complete_sdo(). */
62 std::vector<uint8_t> fromHex(const char *data)
63 {
64 std::vector<uint8_t> ret(strlen(data) / 2);
65 for (size_t i = 0; i < ret.size(); i++) {
66 unsigned int byteVal;
67 sscanf(data + 2 * i, "%2X", &byteVal);
68 ret[i] = static_cast<uint8_t>(byteVal);
69 }
70 return ret;
71 }
72
73 void completeSdo(ec_slave_config_t *sc, uint16_t sdoIndex, const char *hex)
74 {
75 std::vector<uint8_t> data = fromHex(hex);
76 if (ecrt_slave_config_complete_sdo(
77 sc, sdoIndex, data.data(), data.size())) {
78 stringstream err;
79 err << "Failed to configure SDO " << sdoIndex;
80 throw runtime_error(err.str());
81 }
82 }
83
84 } // namespace
85
86 /****************************************************************************/
87 // ifm SU6020/SU8020 (flow/temperature sensor, one digital output).
88 /****************************************************************************/
89
90
8/16
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
✓ Branch 16 taken 1 times.
✗ Branch 17 not taken.
✓ Branch 20 taken 1 times.
✗ Branch 21 not taken.
✓ Branch 24 taken 1 times.
✗ Branch 25 not taken.
✓ Branch 28 taken 1 times.
✗ Branch 29 not taken.
✓ Branch 31 taken 5 times.
✓ Branch 32 taken 1 times.
✗ Branch 41 not taken.
✗ Branch 42 not taken.
2 class IfmSU : public IoLinkDevice
91 {
92 public:
93 void updateOutputs() override
94 {
95 if (domainOut_ && domainOut_->getData()) {
96 EC_WRITE_BIT(
97 domainOut_->getData() + offReset, bitOffReset, reset);
98 }
99 }
100
101 bool addOutputs(
102 ec_slave_config_t *sc,
103 uint16_t pdoIndex,
104 uint16_t pdoEntryIndex,
105 Domain *) override
106 {
107 if (ecrt_slave_config_pdo_assign_add(sc, 2, pdoIndex)) {
108 stringstream err;
109 err << "Failed to assign PDO " << pdoIndex;
110 throw runtime_error(err.str());
111 }
112 if (ecrt_slave_config_pdo_mapping_clear(sc, pdoIndex)) {
113 stringstream err;
114 err << "Failed to clear PDO mapping of " << pdoIndex;
115 throw runtime_error(err.str());
116 }
117 if (ecrt_slave_config_pdo_mapping_add(sc, pdoIndex, 0x0000, 0x00, 4)
118 || ecrt_slave_config_pdo_mapping_add(
119 sc, pdoIndex, pdoEntryIndex, 0x01, 1)
120 || ecrt_slave_config_pdo_mapping_add(
121 sc, pdoIndex, pdoEntryIndex, 0x02, 1)
122 || ecrt_slave_config_pdo_mapping_add(
123 sc, pdoIndex, 0x0000, 0x00, 2)
124 || ecrt_slave_config_pdo_mapping_add(
125 sc, pdoIndex, 0x0000, 0x00, 8)) {
126 stringstream err;
127 err << "Failed to map PDO entry in " << pdoIndex;
128 throw runtime_error(err.str());
129 }
130
131 /* ifm SU6020/SU8020 Outputs
132 * {0x0000, 0x00, 4}, Gap
133 * {0x7000, 0x01, 1}, Totalizer Reset and Inactive
134 * {0x7000, 0x02, 1}, Flow Override
135 * {0x0000, 0x00, 2}, Gap
136 * {0x0000, 0x00, 8}, Gap
137 */
138 outputPdoEntryIndex = pdoEntryIndex;
139 return true;
140 }
141
142 void registerOutputs(ec_slave_config_t *sc, Domain *domainOut) override
143 {
144 domainOut_ = domainOut;
145 offReset = ecrt_slave_config_reg_pdo_entry(
146 sc, outputPdoEntryIndex, 0x01, domainOut->getDomain(),
147 &bitOffReset);
148 if (offReset < 0) {
149 stringstream err;
150 err << "Failed to register output PDO entry "
151 << outputPdoEntryIndex << " 0x01.";
152 throw runtime_error(err.str());
153 }
154 }
155
156 bool addInputs(
157 ec_slave_config_t *sc,
158 uint16_t pdoIndex,
159 uint16_t pdoEntryIndex,
160 Domain *) override
161 {
162 if (ecrt_slave_config_pdo_assign_add(sc, 3, pdoIndex)) {
163 stringstream err;
164 err << "Failed to assign PDO " << pdoIndex;
165 throw runtime_error(err.str());
166 }
167 if (ecrt_slave_config_pdo_mapping_clear(sc, pdoIndex)) {
168 stringstream err;
169 err << "Failed to clear PDO mapping of " << pdoIndex;
170 throw runtime_error(err.str());
171 }
172 if (ecrt_slave_config_pdo_mapping_add(
173 sc, pdoIndex, pdoEntryIndex, 0x01, 32)
174 || ecrt_slave_config_pdo_mapping_add(
175 sc, pdoIndex, pdoEntryIndex, 0x02, 32)
176 || ecrt_slave_config_pdo_mapping_add(
177 sc, pdoIndex, 0x0000, 0x00, 16)
178 || ecrt_slave_config_pdo_mapping_add(
179 sc, pdoIndex, pdoEntryIndex, 0x03, 32)
180 || ecrt_slave_config_pdo_mapping_add(
181 sc, pdoIndex, 0x0000, 0x00, 8)
182 || ecrt_slave_config_pdo_mapping_add(
183 sc, pdoIndex, pdoEntryIndex, 0x04, 1)
184 || ecrt_slave_config_pdo_mapping_add(
185 sc, pdoIndex, pdoEntryIndex, 0x05, 1)
186 || ecrt_slave_config_pdo_mapping_add(
187 sc, pdoIndex, 0x0000, 0x00, 2)
188 || ecrt_slave_config_pdo_mapping_add(
189 sc, pdoIndex, pdoEntryIndex, 0x06, 4)) {
190 stringstream err;
191 err << "Failed to map PDO entry in " << pdoIndex;
192 throw runtime_error(err.str());
193 }
194
195 /* ifm SU6020/SU8020 Inputs
196 * {0x6000, 0x01, 32}, Totalizer (REAL)
197 * {0x6000, 0x02, 32}, Flow (DINT)
198 * {0x0000, 0x00, 16}, Gap
199 * {0x6000, 0x03, 32}, Temperature (DINT)
200 * {0x0000, 0x00, 8}, Gap
201 * {0x6000, 0x04, 1}, OUT1
202 * {0x6000, 0x05, 1}, OUT2
203 * {0x0000, 0x00, 2}, Gap
204 * {0x6000, 0x06, 4}, Device Status
205 */
206 inputPdoEntryIndex = pdoEntryIndex;
207 return true;
208 }
209
210 void registerInputs(ec_slave_config_t *sc, Domain *domainIn) override
211 {
212 domainIn_ = domainIn;
213 offFlow = ecrt_slave_config_reg_pdo_entry(
214 sc, inputPdoEntryIndex, 0x02, domainIn->getDomain(), NULL);
215 offTemp = ecrt_slave_config_reg_pdo_entry(
216 sc, inputPdoEntryIndex, 0x03, domainIn->getDomain(), NULL);
217 offDig = ecrt_slave_config_reg_pdo_entry(
218 sc, inputPdoEntryIndex, 0x04, domainIn->getDomain(), NULL);
219 if (offFlow < 0 || offTemp < 0 || offDig < 0) {
220 stringstream err;
221 err << "Failed to register IfmSU input PDO entry.";
222 throw runtime_error(err.str());
223 }
224 }
225
226 void registerVariables(pdserv *pdserv, pdtask *task, const string &prefix)
227 override
228 {
229 if (pdserv && task) {
230 pdserv_signal(
231 task, 1, (prefix + "/Flow").c_str(), pd_single_T, &flow,
232 1, NULL);
233 pdserv_signal(
234 task, 1, (prefix + "/Temperature").c_str(), pd_single_T,
235 &temperature, 1, NULL);
236 pdserv_signal(
237 task, 1, (prefix + "/Out1").c_str(), pd_boolean_T, &out1,
238 1, NULL);
239 pdserv_signal(
240 task, 1, (prefix + "/Out2").c_str(), pd_boolean_T, &out2,
241 1, NULL);
242 pdserv_signal(
243 task, 1, (prefix + "/State").c_str(), pd_uint8_T, &state,
244 1, NULL);
245 }
246 }
247
248 5 const std::vector<ChannelKind> &inputKinds() const override
249 {
250 5 return inputKinds_;
251 }
252 1 const std::vector<ChannelKind> &outputKinds() const override
253 {
254 1 return outputKinds_;
255 }
256
257 protected:
258 2 ChannelValue getInputAt(size_t i) const override
259 {
260
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) {
261 2 case 0:
262 2 return flow;
263 case 1:
264 return temperature;
265 case 2:
266 return out1;
267 case 3:
268 return out2;
269 case 4:
270 return uint16_t(state);
271 default:
272 throw UnknownChannel("IfmSU: invalid channel index.");
273 }
274 }
275
276 void setOutputAt(size_t, const ChannelValue &) override
277 {
278 throw UnknownChannel(
279 "IfmSU has no generically exposed output channels.");
280 }
281
282 int offReset = -1;
283 unsigned int bitOffReset = 0;
284 bool reset = false; // never driven -- ported as-is from bda,
285 // which never exposed a way to set it either
286
287 int offFlow = -1;
288 float flow = 0.0f; // m^3/s
289 int offTemp = -1;
290 float temperature = 0.0f; // degC
291 int offDig = -1;
292 bool out1 = false;
293 bool out2 = false;
294 uint8_t state = 0;
295
296 protected:
297 Domain *domainOut_ = nullptr;
298 Domain *domainIn_ = nullptr;
299
300 private:
301 uint16_t outputPdoEntryIndex = 0x0000;
302 uint16_t inputPdoEntryIndex = 0x0000;
303
304 std::vector<ChannelKind> inputKinds_ {
305 {"Flow", 1, float(0)},
306 {"Temperature", 1, float(0)},
307 {"Out1", 1, false},
308 {"Out2", 1, false},
309 {"State", 1, uint16_t(0)}};
310 std::vector<ChannelKind> outputKinds_;
311 };
312
313 /****************************************************************************/
314
315 3 class IfmSU6020 : public IfmSU
316 {
317 public:
318 void addConfig(ec_slave_config_t *sc, uint16_t sdoIndex) override
319 {
320 completeSdo(
321 sc, sdoIndex, "2800b30500003601000011014800cf48000000002300");
322 }
323
324 1 void updateInputs() override
325 {
326 /*
327 * ifm-0005B3-20231003-IODD11-de.pdf
328 * Stroemung: [L/h] = MeasurementValue * 0.1
329 * Temperatur: [degC] (-4400 to 12400) * 0.01
330 * Status: 0 OK, 1 Wartung, 2 ausserhalb Spec, 3 Test, 4 Ausfall
331 */
332
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!domainIn_) {
333 1 return;
334 }
335 int32_t raw = ntohl(EC_READ_S32(domainIn_->getData() + offFlow));
336 flow = raw * 0.1f * LitersPerHourToSI;
337 raw = ntohl(EC_READ_S32(domainIn_->getData() + offTemp));
338 temperature = raw * 1e-2f;
339 uint8_t byte = EC_READ_U8(domainIn_->getData() + offDig);
340 out1 = (byte & 0x01) != 0x00;
341 out2 = (byte & 0x02) != 0x00;
342 state = (byte >> 4);
343 }
344 };
345
346 /****************************************************************************/
347
348 class IfmSU8020 : public IfmSU
349 {
350 public:
351 void addConfig(ec_slave_config_t *sc, uint16_t sdoIndex) override
352 {
353 completeSdo(
354 sc, sdoIndex, "2800b40500003601000011014800cf48000000002300");
355 }
356
357 void updateInputs() override
358 {
359 /*
360 * ifm-0005B4-20231020-IODD11-de.pdf
361 * Stroemung: [L/h] = MeasurementValue
362 * Temperatur: [degC] (-4400 to 12400) * 0.01
363 */
364 if (!domainIn_) {
365 return;
366 }
367 int32_t raw = ntohl(EC_READ_S32(domainIn_->getData() + offFlow));
368 flow = raw * LitersPerHourToSI;
369 raw = ntohl(EC_READ_S32(domainIn_->getData() + offTemp));
370 temperature = raw * 1e-2f;
371 uint8_t byte = EC_READ_U8(domainIn_->getData() + offDig);
372 out1 = (byte & 0x01) != 0x00;
373 out2 = (byte & 0x02) != 0x00;
374 state = (byte >> 4);
375 }
376 };
377
378 /****************************************************************************/
379 // ifm PV8004 (pressure/temperature sensor, no outputs).
380 /****************************************************************************/
381
382 class IfmPV8004 : public IoLinkDevice
383 {
384 public:
385 void addConfig(ec_slave_config_t *sc, uint16_t sdoIndex) override
386 {
387 completeSdo(
388 sc, sdoIndex, "2800ba0400003601000011012d00c700000000002300");
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 registerVariables(pdserv *pdserv, pdtask *task, const string &prefix)
459 override
460 {
461 if (pdserv && task) {
462 pdserv_signal(
463 task, 1, (prefix + "/Pressure").c_str(), pd_single_T,
464 &pressure, 1, NULL);
465 pdserv_signal(
466 task, 1, (prefix + "/Temperature").c_str(), pd_single_T,
467 &temperature, 1, NULL);
468 pdserv_signal(
469 task, 1, (prefix + "/Out1").c_str(), pd_boolean_T, &out1,
470 1, NULL);
471 pdserv_signal(
472 task, 1, (prefix + "/Out2").c_str(), pd_boolean_T, &out2,
473 1, NULL);
474 pdserv_signal(
475 task, 1, (prefix + "/State").c_str(), pd_uint8_T, &state,
476 1, NULL);
477 }
478 }
479
480 void updateInputs() override
481 {
482 /*
483 * ifm-0004BA-20200507-IODD11-de.pdf
484 * Druck: [bar] (-1000 to 10500) * 0.001
485 * Temperatur: [degC] (-4500 to 9500) * 0.01
486 */
487 if (!domainIn_) {
488 return;
489 }
490 int16_t raw = ntohs(EC_READ_S16(domainIn_->getData() + offPressure));
491 pressure = raw * 1e-3f * BarToSI;
492 raw = ntohs(EC_READ_S16(domainIn_->getData() + offTemp));
493 temperature = raw * 1e-2f;
494 uint8_t byte = EC_READ_U8(domainIn_->getData() + offDig);
495 out1 = (byte & 0x01) != 0x00;
496 out2 = (byte & 0x02) != 0x00;
497 state = (byte >> 4);
498 }
499 void updateOutputs() override {}
500
501 const std::vector<ChannelKind> &inputKinds() const override
502 {
503 return inputKinds_;
504 }
505 const std::vector<ChannelKind> &outputKinds() const override
506 {
507 return outputKinds_;
508 }
509
510 protected:
511 ChannelValue getInputAt(size_t i) const override
512 {
513 switch (i) {
514 case 0:
515 return pressure;
516 case 1:
517 return temperature;
518 case 2:
519 return out1;
520 case 3:
521 return out2;
522 case 4:
523 return uint16_t(state);
524 default:
525 throw UnknownChannel("IfmPV8004: invalid channel index.");
526 }
527 }
528 void setOutputAt(size_t, const ChannelValue &) override
529 {
530 throw UnknownChannel("IfmPV8004 has no output channels.");
531 }
532
533 private:
534 int offPressure = -1;
535 float pressure = 0.0f; // Pa
536 int offTemp = -1;
537 float temperature = 0.0f; // degC
538 int offDig = -1;
539 bool out1 = false;
540 bool out2 = false;
541 uint8_t state = 0;
542
543 uint16_t inputPdoEntryIndex = 0x0000;
544 Domain *domainIn_ = nullptr;
545
546 std::vector<ChannelKind> inputKinds_ {
547 {"Pressure", 1, float(0)},
548 {"Temperature", 1, float(0)},
549 {"Out1", 1, false},
550 {"Out2", 1, false},
551 {"State", 1, uint16_t(0)}};
552 std::vector<ChannelKind> outputKinds_;
553 };
554
555 /****************************************************************************/
556 // ifm DV2131 (buzzer + LED outputs, feedback + status inputs).
557 /****************************************************************************/
558
559 class IfmDV2131 : public IoLinkDevice
560 {
561 public:
562 void addConfig(ec_slave_config_t *sc, uint16_t sdoIndex) override
563 {
564 completeSdo(
565 sc, sdoIndex, "28009804000036010000110144005050000000002300");
566 }
567
568 bool addOutputs(
569 ec_slave_config_t *sc,
570 uint16_t pdoIndex,
571 uint16_t pdoEntryIndex,
572 Domain *) override
573 {
574 if (ecrt_slave_config_pdo_assign_add(sc, 2, pdoIndex)) {
575 stringstream err;
576 err << "Failed to assign PDO " << pdoIndex;
577 throw runtime_error(err.str());
578 }
579 if (ecrt_slave_config_pdo_mapping_clear(sc, pdoIndex)) {
580 stringstream err;
581 err << "Failed to clear PDO mapping of " << pdoIndex;
582 throw runtime_error(err.str());
583 }
584 if (ecrt_slave_config_pdo_mapping_add(
585 sc, pdoIndex, pdoEntryIndex, 0x01, 1) // buzzer
586 || ecrt_slave_config_pdo_mapping_add(
587 sc, pdoIndex, 0x0000, 0x00, 3)
588 || ecrt_slave_config_pdo_mapping_add(
589 sc, pdoIndex, pdoEntryIndex, 0x02, 3) // style
590 || ecrt_slave_config_pdo_mapping_add(
591 sc, pdoIndex, 0x0000, 0x00, 1)
592 || ecrt_slave_config_pdo_mapping_add(
593 sc, pdoIndex, pdoEntryIndex, 0x03, 1) // led-a
594 || ecrt_slave_config_pdo_mapping_add(
595 sc, pdoIndex, pdoEntryIndex, 0x04, 1) // led-b
596 || ecrt_slave_config_pdo_mapping_add(
597 sc, pdoIndex, pdoEntryIndex, 0x05, 1) // led-c
598 || ecrt_slave_config_pdo_mapping_add(
599 sc, pdoIndex, 0x0000, 0x00, 5)) {
600 stringstream err;
601 err << "Failed to map PDO entry in " << pdoIndex;
602 throw runtime_error(err.str());
603 }
604
605 outputPdoEntryIndex = pdoEntryIndex;
606 return true;
607 }
608
609 bool addInputs(
610 ec_slave_config_t *sc,
611 uint16_t pdoIndex,
612 uint16_t pdoEntryIndex,
613 Domain *) override
614 {
615 if (ecrt_slave_config_pdo_assign_add(sc, 3, pdoIndex)) {
616 stringstream err;
617 err << "Failed to assign PDO " << pdoIndex;
618 throw runtime_error(err.str());
619 }
620 if (ecrt_slave_config_pdo_mapping_clear(sc, pdoIndex)) {
621 stringstream err;
622 err << "Failed to clear PDO mapping of " << pdoIndex;
623 throw runtime_error(err.str());
624 }
625 if (ecrt_slave_config_pdo_mapping_add(
626 sc, pdoIndex, pdoEntryIndex, 0x01, 1) // feedback
627 || ecrt_slave_config_pdo_mapping_add(
628 sc, pdoIndex, 0x0000, 0x00, 7)
629 || ecrt_slave_config_pdo_mapping_add(
630 sc, pdoIndex, pdoEntryIndex, 0x02, 1) // status1
631 || ecrt_slave_config_pdo_mapping_add(
632 sc, pdoIndex, pdoEntryIndex, 0x03, 1) // status2
633 || ecrt_slave_config_pdo_mapping_add(
634 sc, pdoIndex, pdoEntryIndex, 0x04, 1) // status3
635 || ecrt_slave_config_pdo_mapping_add(
636 sc, pdoIndex, pdoEntryIndex, 0x05, 1) // status4
637 || ecrt_slave_config_pdo_mapping_add(
638 sc, pdoIndex, pdoEntryIndex, 0x06, 1) // status5
639 || ecrt_slave_config_pdo_mapping_add(
640 sc, pdoIndex, pdoEntryIndex, 0x07, 1) // status6
641 || ecrt_slave_config_pdo_mapping_add(
642 sc, pdoIndex, pdoEntryIndex, 0x08, 1) // status7
643 || ecrt_slave_config_pdo_mapping_add(
644 sc, pdoIndex, pdoEntryIndex, 0x09, 1)) { // st.8
645 stringstream err;
646 err << "Failed to map PDO entry in " << pdoIndex;
647 throw runtime_error(err.str());
648 }
649
650 inputPdoEntryIndex = pdoEntryIndex;
651 return true;
652 }
653
654 void registerOutputs(ec_slave_config_t *sc, Domain *domainOut) override
655 {
656 domainOut_ = domainOut;
657 offBuzzer = ecrt_slave_config_reg_pdo_entry(
658 sc, outputPdoEntryIndex, 0x01, domainOut->getDomain(), NULL);
659 offLed = ecrt_slave_config_reg_pdo_entry(
660 sc, outputPdoEntryIndex, 0x03, domainOut->getDomain(), NULL);
661 if (offBuzzer < 0 || offLed < 0) {
662 stringstream err;
663 err << "Failed to register IfmDV2131 output PDO entry.";
664 throw runtime_error(err.str());
665 }
666 }
667
668 void registerInputs(ec_slave_config_t *sc, Domain *domainIn) override
669 {
670 domainIn_ = domainIn;
671 offFeedback = ecrt_slave_config_reg_pdo_entry(
672 sc, inputPdoEntryIndex, 0x01, domainIn->getDomain(), NULL);
673 offStatus = ecrt_slave_config_reg_pdo_entry(
674 sc, inputPdoEntryIndex, 0x02, domainIn->getDomain(), NULL);
675 if (offFeedback < 0 || offStatus < 0) {
676 stringstream err;
677 err << "Failed to register IfmDV2131 input PDO entry.";
678 throw runtime_error(err.str());
679 }
680 }
681
682 void registerVariables(pdserv *pdserv, pdtask *task, const string &prefix)
683 override
684 {
685 if (pdserv && task) {
686 pdserv_signal(
687 task, 1, (prefix + "/EnableBuzzer").c_str(), pd_uint8_T,
688 &enableBuzzer, 1, NULL);
689 pdserv_signal(
690 task, 1, (prefix + "/BuzzerStyle").c_str(), pd_uint8_T,
691 &buzzerStyle, 1, NULL);
692 pdserv_signal(
693 task, 1, (prefix + "/LedStates").c_str(), pd_uint8_T,
694 &ledStates, 1, NULL);
695 pdserv_signal(
696 task, 1, (prefix + "/Feedback").c_str(), pd_uint8_T,
697 &feedback, 1, NULL);
698 pdserv_signal(
699 task, 1, (prefix + "/Status").c_str(), pd_uint8_T,
700 &status, 1, NULL);
701 }
702 }
703
704 void updateInputs() override
705 {
706 if (!domainIn_ || !domainIn_->getData()) {
707 return;
708 }
709 feedback = EC_READ_U8(domainIn_->getData() + offFeedback) & 0x01;
710 status = EC_READ_U8(domainIn_->getData() + offStatus);
711 }
712
713 void updateOutputs() override
714 {
715 if (!domainOut_ || !domainOut_->getData()) {
716 return;
717 }
718 EC_WRITE_U8(
719 domainOut_->getData() + offBuzzer,
720 enableBuzzer | (buzzerStyle << 4));
721 EC_WRITE_U8(domainOut_->getData() + offLed, ledStates);
722 }
723
724 const std::vector<ChannelKind> &inputKinds() const override
725 {
726 return inputKinds_;
727 }
728 const std::vector<ChannelKind> &outputKinds() const override
729 {
730 return outputKinds_;
731 }
732
733 protected:
734 ChannelValue getInputAt(size_t i) const override
735 {
736 switch (i) {
737 case 0:
738 return uint16_t(feedback);
739 case 1:
740 return uint16_t(status);
741 default:
742 throw UnknownChannel("IfmDV2131: invalid channel index.");
743 }
744 }
745 void setOutputAt(size_t i, const ChannelValue &value) override
746 {
747 switch (i) {
748 case 0:
749 enableBuzzer = std::get<uint16_t>(value) ? 1 : 0;
750 return;
751 case 1:
752 buzzerStyle = static_cast<uint8_t>(std::get<uint16_t>(value));
753 return;
754 case 2:
755 ledStates = static_cast<uint8_t>(std::get<uint16_t>(value));
756 return;
757 default:
758 throw UnknownChannel("IfmDV2131: invalid channel index.");
759 }
760 }
761
762 private:
763 int offBuzzer = -1;
764 uint8_t enableBuzzer = 0;
765 uint8_t buzzerStyle = 0;
766
767 int offLed = -1;
768 uint8_t ledStates = 0;
769
770 int offFeedback = -1;
771 uint8_t feedback = 0;
772
773 int offStatus = -1;
774 uint8_t status = 0;
775
776 uint16_t outputPdoEntryIndex = 0x0000;
777 uint16_t inputPdoEntryIndex = 0x0000;
778 Domain *domainOut_ = nullptr;
779 Domain *domainIn_ = nullptr;
780
781 std::vector<ChannelKind> inputKinds_ {
782 {"Feedback", 1, uint16_t(0)},
783 {"Status", 1, uint16_t(0)}};
784 std::vector<ChannelKind> outputKinds_ {
785 {"EnableBuzzer", 1, uint16_t(0)},
786 {"BuzzerStyle", 1, uint16_t(0)},
787 {"LedStates", 1, uint16_t(0)}};
788 };
789
790 /****************************************************************************/
791 // ifm SM4000 (flow/temperature sensor, inputs only, single combined
792 // 64-bit process value -- no digital outputs, no device-status byte).
793 /****************************************************************************/
794
795 class IfmSM4000 : public IoLinkDevice
796 {
797 public:
798 void addConfig(ec_slave_config_t *sc, uint16_t sdoIndex) override
799 {
800 completeSdo(
801 sc, sdoIndex, "28009f0200003601000011013200c700000000002300");
802 }
803
804 bool addInputs(
805 ec_slave_config_t *sc,
806 uint16_t pdoIndex,
807 uint16_t pdoEntryIndex,
808 Domain *) override
809 {
810 /*
811 * IO-Link Port1: process data, ARRAY [0..7] OF USINT (64 bit)
812 */
813 if (ecrt_slave_config_pdo_assign_add(sc, 3, pdoIndex)) {
814 stringstream err;
815 err << "Failed to assign PDO " << pdoIndex;
816 throw runtime_error(err.str());
817 }
818 if (ecrt_slave_config_pdo_mapping_clear(sc, pdoIndex)) {
819 stringstream err;
820 err << "Failed to clear PDO mapping of " << pdoIndex;
821 throw runtime_error(err.str());
822 }
823 if (ecrt_slave_config_pdo_mapping_add(
824 sc, pdoIndex, pdoEntryIndex, 0x01, 64)) {
825 stringstream err;
826 err << "Failed to map PDO entry in " << pdoIndex;
827 throw runtime_error(err.str());
828 }
829
830 inputPdoEntryIndex = pdoEntryIndex;
831 return true;
832 }
833
834 void registerInputs(ec_slave_config_t *sc, Domain *domainIn) override
835 {
836 domainIn_ = domainIn;
837 offIn = ecrt_slave_config_reg_pdo_entry(
838 sc, inputPdoEntryIndex, 0x01, domainIn->getDomain(), NULL);
839 if (offIn < 0) {
840 stringstream err;
841 err << "Failed to register IfmSM4000 input PDO entry.";
842 throw runtime_error(err.str());
843 }
844 }
845
846 void registerVariables(pdserv *pdserv, pdtask *task, const string &prefix)
847 override
848 {
849 if (pdserv && task) {
850 pdserv_signal(
851 task, 1, (prefix + "/Flow").c_str(), pd_single_T, &flow,
852 1, NULL);
853 pdserv_signal(
854 task, 1, (prefix + "/Temperature").c_str(), pd_single_T,
855 &temperature, 1, NULL);
856 }
857 }
858
859 void updateInputs() override
860 {
861 /*
862 * ifm-SM4000-20170209-IODD11-de.pdf
863 * Durchfluss: [L/min] (-1999 to 3600) * 0.001
864 * Temperatur: 14 bit (LSB sind OUT1 und OUT2), * 0.1 degC
865 */
866 if (!domainIn_) {
867 return;
868 }
869 int16_t raw = ntohs(EC_READ_S16(domainIn_->getData() + offIn + 4));
870 flow = raw * 0.06f * LitersPerHourToSI;
871 uint16_t uraw = ntohs(EC_READ_U16(domainIn_->getData() + offIn + 6));
872 temperature = static_cast<int16_t>(uraw >> 2) * 0.1f;
873 }
874 void updateOutputs() override {}
875
876 const std::vector<ChannelKind> &inputKinds() const override
877 {
878 return inputKinds_;
879 }
880 const std::vector<ChannelKind> &outputKinds() const override
881 {
882 return outputKinds_;
883 }
884
885 protected:
886 ChannelValue getInputAt(size_t i) const override
887 {
888 switch (i) {
889 case 0:
890 return flow;
891 case 1:
892 return temperature;
893 default:
894 throw UnknownChannel("IfmSM4000: invalid channel index.");
895 }
896 }
897 void setOutputAt(size_t, const ChannelValue &) override
898 {
899 throw UnknownChannel("IfmSM4000 has no output channels.");
900 }
901
902 private:
903 int offIn = -1;
904 float flow = 0.0f; // m^3/s
905 float temperature = 0.0f; // degC
906
907 uint16_t inputPdoEntryIndex = 0x0000;
908 Domain *domainIn_ = nullptr;
909
910 std::vector<ChannelKind> inputKinds_ {
911 {"Flow", 1, float(0)},
912 {"Temperature", 1, float(0)}};
913 std::vector<ChannelKind> outputKinds_;
914 };
915
916 /****************************************************************************/
917
918
4/8
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 8 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 8 times.
✗ Branch 13 not taken.
9 ECAPP_REGISTER_IOLINK_DEVICE("ifm-SU6020", IfmSU6020);
919
4/8
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 8 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 8 times.
✗ Branch 13 not taken.
8 ECAPP_REGISTER_IOLINK_DEVICE("ifm-SU8020", IfmSU8020);
920
4/8
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 8 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 8 times.
✗ Branch 13 not taken.
8 ECAPP_REGISTER_IOLINK_DEVICE("ifm-PV8004", IfmPV8004);
921
4/8
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 8 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 8 times.
✗ Branch 13 not taken.
8 ECAPP_REGISTER_IOLINK_DEVICE("ifm-DV2131", IfmDV2131);
922
4/8
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 8 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 8 times.
✗ Branch 13 not taken.
8 ECAPP_REGISTER_IOLINK_DEVICE("ifm-SM4000", IfmSM4000);
923
924 24 } // namespace EcApp
925
926 /****************************************************************************/
927