GCC Code Coverage Report


Directory: ./
File: src/devices/Festo.cpp
Date: 2026-09-23 16:22:15
Exec Total Coverage
Lines: 74 101 73.3%
Branches: 62 318 19.5%

Line Branch Exec Source
1 /*****************************************************************************
2 *
3 * This file is part of the ecapp library (EtherCAT application devices).
4 *
5 * Copyright (C) 2026 Florian Pose <fp@igh.de>
6 *
7 * The ecapp library is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public License
9 * as published by the Free Software Foundation, version 3 of the
10 * License.
11 *
12 * The ecapp library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with the ecapp library. If not, see
19 * <https://www.gnu.org/licenses/>.
20 *
21 ****************************************************************************/
22
23 #include "devices/Festo.h"
24
25 #include "SlaveContext.h"
26 #include "ecapp/ConfigurableChannelCount.h"
27 #include "ecapp/Domain.h"
28 #include "ecapp/Exceptions.h"
29 #include "ecapp/Factory.h"
30 #include "ecapp/Master.h"
31 #include "ecapp/SwappedSync.h"
32
33 #include <memory>
34 #include <sstream>
35 #include <stdexcept>
36
37 using std::logic_error;
38 using std::make_unique;
39 using std::out_of_range;
40 using std::runtime_error;
41 using std::size_t;
42 using std::stringstream;
43 using std::unique_ptr;
44
45 /****************************************************************************/
46
47 namespace EcApp {
48
49 static ec_pdo_entry_info_t festo_pdo_entries[] = {
50 {0x7000, 0x01, 8},
51 {0x7000, 0x02, 8},
52 {0x7000, 0x03, 8},
53 {0x7000, 0x04, 8},
54 };
55
56 static ec_pdo_info_t festo_pdos[] = {
57 {0x1600, 4, festo_pdo_entries + 0}, /* RxPDO 1 mapping */
58 };
59
60 static ec_sync_info_t festo_syncs[] = {
61 {0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
62 {1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
63 {2, EC_DIR_OUTPUT, 1, festo_pdos + 0, EC_WD_ENABLE},
64 {3, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
65 {0xff}};
66
67 /****************************************************************************/
68
69 template <Mode mode>
70 2 Festo<mode>::Festo(
71 Master *master,
72 uint16_t alias,
73 uint16_t position,
74 Domain *domainOut) :
75 2 channelCountSet(false), sc(NULL), domainOut(domainOut), offOut(-1)
76 {
77
1/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if (!master) {
78 2 return;
79 }
80
81 ✗ sc = ecrt_master_slave_config(
82 master->getMaster(), alias, position, 0x0000001d, 0x0008bc8c);
83 ✗ if (!sc) {
84 ✗ stringstream err;
85 ✗ err << "Failed to get slave configuration for Festo.";
86 ✗ throw runtime_error(err.str());
87 }
88
89 ✗ if (ecrt_slave_config_pdos(
90 sc, EC_END, SwappedSync<mode>(festo_syncs).getSync())) {
91 ✗ stringstream err;
92 ✗ err << "Failed to configure PDOs of Festo.";
93 ✗ throw runtime_error(err.str());
94 }
95
96 ✗ offOut = ecrt_slave_config_reg_pdo_entry(
97 sc, 0x7000, 0x01, domainOut->getDomain(), NULL);
98 ✗ if (offOut < 0) {
99 ✗ stringstream err;
100 ✗ err << "Failed to register Festo output PDO entry.";
101 ✗ throw runtime_error(err.str());
102 }
103 }
104
105 /****************************************************************************/
106
107 template <Mode mode>
108 3 void Festo<mode>::setChannelCount(unsigned int count)
109 {
110
2/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 if (channelCountSet) {
111 throw logic_error(
112 "Festo::setChannelCount() was already called; pdserv "
113
1/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
1 "does not support registering the same signal twice.");
114 }
115 2 channelCountSet = true;
116
117
2/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if (count > MaxChannels) {
118
1/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
2 stringstream err;
119
3/12
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
2 err << "Festo supports at most " << MaxChannels
120
2/8
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
1 << " channels (32 bits reserved in the PDO), requested " << count
121 << ".";
122
2/8
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
1 throw out_of_range(err.str());
123 }
124
125
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 input.assign(count, 0);
126
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 prevInput.assign(count, 0);
127
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 toggle.assign(count, 0);
128
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 prevToggle.assign(count, 0);
129
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 autoValue.assign(count, 0);
130
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 enableForcing.assign(count, 0);
131
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 forcedValue.assign(count, 0);
132
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 output.assign(count, 0);
133
134 // pdserv publishing happens in FestoAdapter::setChannelCount()
135 // (via SubDevice::registerOutputChannel()/registerParameter()),
136 // right after this call -- it needs these vectors already sized,
137 // which is exactly what just happened above.
138 1 }
139
140 /****************************************************************************/
141
142 template <Mode mode>
143 2 void Festo<mode>::setOutput(unsigned int channel, bool value)
144 {
145
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 if (channel < input.size()) {
146 2 input[channel] = value;
147 }
148 2 }
149
150 /****************************************************************************/
151
152 template <Mode mode>
153 ✗ bool Festo<mode>::getOutput(unsigned int channel) const
154 {
155 ✗ if (channel < output.size()) {
156 ✗ return output[channel];
157 }
158
159 ✗ return false;
160 }
161
162 /****************************************************************************/
163
164 template <Mode mode>
165 1 void Festo<mode>::updateOutputs()
166 {
167
2/4
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
25 for (unsigned int i = 0; i < input.size(); i++) {
168 // toggle logic
169
3/8
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 22 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
24 const bool inputRising = input[i] && !prevInput[i];
170 24 const bool inputUnchanged = input[i] == prevInput[i];
171 24 prevInput[i] = input[i];
172
173 24 const bool toggleChanged = toggle[i] != prevToggle[i];
174 24 prevToggle[i] = toggle[i];
175
176 24 const bool toggleXorState = toggleChanged != autoValue[i];
177
3/8
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
24 const bool toggledState = inputUnchanged && toggleXorState;
178
3/8
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
24 autoValue[i] = inputRising || toggledState;
179
180
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
24 if (enableForcing[i]) {
181 ✗ output[i] = forcedValue[i];
182 }
183 else {
184 24 output[i] = autoValue[i];
185 }
186
187
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
24 if (domainOut->getData()) {
188 ✗ unsigned int byteOffset = i / 8;
189 ✗ unsigned int bitOffset = i % 8;
190 ✗ EC_WRITE_BIT(
191 domainOut->getData() + offOut + byteOffset, bitOffset,
192 output[i]);
193 }
194 }
195 1 }
196
197 /****************************************************************************/
198
199 template <Mode mode>
200 ✗ unsigned int Festo<mode>::numChannels() const
201 {
202 ✗ return output.size();
203 }
204
205 /****************************************************************************/
206
207 template class Festo<Mode::Control>;
208 template class Festo<Mode::Simulation>;
209
210 /****************************************************************************/
211 // Generic SubDevice + ConfigurableChannelCount adapter. Festo has no
212 // input channels at all; its output channel list is empty until
213 // setChannelCount() has been called (see ConfigurableChannelCount).
214 /****************************************************************************/
215
216 template <Mode mode>
217 4 class FestoAdapter : public SubDevice, public ConfigurableChannelCount
218 {
219 public:
220 2 explicit FestoAdapter(const SlaveContext &ctx) :
221 2 SubDevice(ctx.domainOut, nullptr, ctx.pdServ, ctx.task, ctx.prefix),
222
2/8
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
4 device(ctx.master, ctx.alias, ctx.position, ctx.domainOut)
223 2 {}
224
225 1 void updateInputs() override {}
226 1 void updateOutputs() override { device.updateOutputs(); }
227
228 2 const std::vector<ChannelKind> &inputKinds() const override
229 {
230 2 return inputKinds_;
231 }
232 10 const std::vector<ChannelKind> &outputKinds() const override
233 {
234 10 return outputKinds_;
235 }
236
237 3 void setChannelCount(unsigned int count) override
238 {
239 3 device.setChannelCount(count);
240
4/16
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 18 taken 1 times.
✗ Branch 19 not taken.
✓ Branch 21 taken 1 times.
✗ Branch 22 not taken.
✓ Branch 23 taken 1 times.
✓ Branch 24 taken 1 times.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
1 outputKinds_ = {{"Output", count, false}};
241
242 // device.input (not device.output, the post-forcing effective
243 // value) is what the generic (kind, index) API's "Output"
244 // channel resolves to (see setOutputAt() below), hence
245 // registered under that same name; device.output keeps its
246 // own pdserv-only name since it has no matching ChannelKind --
247 // see the identical reasoning in Ex4xxxAdapter/Ex20xxAdapter.
248
2/8
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 14 taken 1 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 1 times.
✗ Branch 18 not taken.
1 registerOutputChannel("Output", device.input, AsBoolean {});
249
2/8
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 14 taken 1 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 1 times.
✗ Branch 18 not taken.
1 registerOutputChannel("EffectiveOutput", device.output, AsBoolean {});
250
3/12
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 22 taken 1 times.
✗ Branch 23 not taken.
✓ Branch 26 taken 1 times.
✗ Branch 27 not taken.
✓ Branch 29 taken 1 times.
✗ Branch 30 not taken.
1 registerParameter(
251 "EnableForcing", "Output", device.enableForcing,
252 AsBoolean {});
253
3/12
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 22 taken 1 times.
✗ Branch 23 not taken.
✓ Branch 26 taken 1 times.
✗ Branch 27 not taken.
✓ Branch 29 taken 1 times.
✗ Branch 30 not taken.
1 registerParameter(
254 "ForcedValue", "Output", device.forcedValue, AsBoolean {});
255
3/12
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 21 taken 1 times.
✗ Branch 22 not taken.
✓ Branch 25 taken 1 times.
✗ Branch 26 not taken.
✓ Branch 28 taken 1 times.
✗ Branch 29 not taken.
1 registerParameter("Toggle", "Output", device.toggle);
256 1 }
257
258 protected:
259 ✗ ChannelValue getInputAt(size_t) const override
260 {
261 ✗ throw UnknownChannel("Festo has no input channels.");
262 }
263 2 void setOutputAt(size_t i, const ChannelValue &value) override
264 {
265 2 device.setOutput(static_cast<unsigned int>(i), std::get<bool>(value));
266 2 }
267
268 private:
269 Festo<mode> device;
270 std::vector<ChannelKind> inputKinds_;
271 std::vector<ChannelKind> outputKinds_;
272 };
273
274 /****************************************************************************/
275
276 namespace {
277
278 12 DeviceFactoryFn makeFestoFactory()
279 {
280 2 return [](const SlaveContext &ctx) -> unique_ptr<SubDevice> {
281
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ctx.mode == Mode::Control) {
282 ✗ return make_unique<FestoAdapter<Mode::Control>>(ctx);
283 }
284 else {
285 2 return make_unique<FestoAdapter<Mode::Simulation>>(ctx);
286 }
287
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 };
288 }
289
290 struct FestoRegistration
291 {
292 12 FestoRegistration()
293 {
294
4/8
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 12 times.
✗ Branch 14 not taken.
36 Registry::instance().add(
295 {"Festo-CTEU", __FILE__, 0x0000001d, 0x0008bc8c, AnyRevision,
296
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
24 wrapFactory(makeFestoFactory())});
297 12 }
298 12 } festoRegistration;
299
300 } // namespace
301
302 36 } // namespace EcApp
303
304 /****************************************************************************/
305