GCC Code Coverage Report


Directory: ./
File: src/devices/Festo.cpp
Date: 2026-09-03 16:05:57
Exec Total Coverage
Lines: 74 113 65.5%
Branches: 49 286 17.1%

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 pdserv *pdserv,
72 pdtask *task,
73 const std::string &prefix,
74 Master *master,
75 uint16_t alias,
76 uint16_t position,
77 Domain *domainOut) :
78 serv(pdserv),
79 task(task),
80 prefix(prefix),
81 channelCountSet(false),
82 sc(NULL),
83 domainOut(domainOut),
84 2 offOut(-1)
85 {
86
1/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if (!master) {
87 2 return;
88 }
89
90 sc = ecrt_master_slave_config(
91 master->getMaster(), alias, position, 0x0000001d, 0x0008bc8c);
92 if (!sc) {
93 stringstream err;
94 err << "Failed to get slave configuration for Festo.";
95 throw runtime_error(err.str());
96 }
97
98 if (ecrt_slave_config_pdos(
99 sc, EC_END, SwappedSync<mode>(festo_syncs).getSync())) {
100 stringstream err;
101 err << "Failed to configure PDOs of Festo.";
102 throw runtime_error(err.str());
103 }
104
105 offOut = ecrt_slave_config_reg_pdo_entry(
106 sc, 0x7000, 0x01, domainOut->getDomain(), NULL);
107 if (offOut < 0) {
108 stringstream err;
109 err << "Failed to register Festo output PDO entry.";
110 throw runtime_error(err.str());
111 }
112 }
113
114 /****************************************************************************/
115
116 template <Mode mode>
117 3 void Festo<mode>::setChannelCount(unsigned int count)
118 {
119
2/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 if (channelCountSet) {
120 throw logic_error(
121 "Festo::setChannelCount() was already called; pdserv "
122
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.");
123 }
124 2 channelCountSet = true;
125
126
2/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if (count > MaxChannels) {
127
1/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
2 stringstream err;
128
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
129
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
130 << ".";
131
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());
132 }
133
134
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 input.assign(count, 0);
135
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 prevInput.assign(count, 0);
136
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 toggle.assign(count, 0);
137
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 prevToggle.assign(count, 0);
138
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 autoValue.assign(count, 0);
139
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 enableForcing.assign(count, 0);
140
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 forcedValue.assign(count, 0);
141
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 output.assign(count, 0);
142
143
1/8
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
1 if (serv && task) {
144 pdserv_signal(
145 task, 1, (prefix + "/Output").c_str(), pd_boolean_T,
146 output.data(), count, NULL);
147 pdserv_parameter(
148 serv, (prefix + "/EnableForcing").c_str(), 0666, pd_boolean_T,
149 enableForcing.data(), count, NULL, NULL, NULL);
150 pdserv_parameter(
151 serv, (prefix + "/ForcedValue").c_str(), 0666, pd_boolean_T,
152 forcedValue.data(), count, NULL, NULL, NULL);
153 pdserv_parameter(
154 serv, (prefix + "/Toggle").c_str(), 0666, pd_uint32_T,
155 toggle.data(), count, NULL, NULL, NULL);
156 }
157 1 }
158
159 /****************************************************************************/
160
161 template <Mode mode>
162 2 void Festo<mode>::setOutput(unsigned int channel, bool value)
163 {
164
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 if (channel < input.size()) {
165 2 input[channel] = value;
166 }
167 2 }
168
169 /****************************************************************************/
170
171 template <Mode mode>
172 bool Festo<mode>::getOutput(unsigned int channel) const
173 {
174 if (channel < output.size()) {
175 return output[channel];
176 }
177
178 return false;
179 }
180
181 /****************************************************************************/
182
183 template <Mode mode>
184 1 void Festo<mode>::updateOutputs()
185 {
186
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++) {
187 // toggle logic
188
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];
189 24 const bool inputUnchanged = input[i] == prevInput[i];
190 24 prevInput[i] = input[i];
191
192 24 const bool toggleChanged = toggle[i] != prevToggle[i];
193 24 prevToggle[i] = toggle[i];
194
195 24 const bool toggleXorState = toggleChanged != autoValue[i];
196
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;
197
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;
198
199
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
24 if (enableForcing[i]) {
200 output[i] = forcedValue[i];
201 }
202 else {
203 24 output[i] = autoValue[i];
204 }
205
206
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
24 if (domainOut->getData()) {
207 unsigned int byteOffset = i / 8;
208 unsigned int bitOffset = i % 8;
209 EC_WRITE_BIT(
210 domainOut->getData() + offOut + byteOffset, bitOffset,
211 output[i]);
212 }
213 }
214 1 }
215
216 /****************************************************************************/
217
218 template <Mode mode>
219 unsigned int Festo<mode>::numChannels() const
220 {
221 return output.size();
222 }
223
224 /****************************************************************************/
225
226 template class Festo<Mode::Control>;
227 template class Festo<Mode::Simulation>;
228
229 /****************************************************************************/
230 // Generic SubDevice + ConfigurableChannelCount adapter. Festo has no
231 // input channels at all; its output channel list is empty until
232 // setChannelCount() has been called (see ConfigurableChannelCount).
233 /****************************************************************************/
234
235 template <Mode mode>
236 4 class FestoAdapter : public SubDevice, public ConfigurableChannelCount
237 {
238 public:
239 2 explicit FestoAdapter(const SlaveContext &ctx) :
240 2 device(ctx.pdServ,
241 2 ctx.task,
242 ctx.prefix,
243 2 ctx.master,
244 2 ctx.alias,
245 2 ctx.position,
246
1/4
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
10 ctx.domainOut)
247 2 {}
248
249 1 void updateInputs() override {}
250 1 void updateOutputs() override { device.updateOutputs(); }
251
252 2 const std::vector<ChannelKind> &inputKinds() const override
253 {
254 2 return inputKinds_;
255 }
256 9 const std::vector<ChannelKind> &outputKinds() const override
257 {
258 9 return outputKinds_;
259 }
260
261 3 void setChannelCount(unsigned int count) override
262 {
263 3 device.setChannelCount(count);
264
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 17 taken 1 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 1 times.
✗ Branch 21 not taken.
✓ Branch 22 taken 1 times.
✓ Branch 23 taken 1 times.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
1 outputKinds_ = {{"Output", count, false}};
265 1 }
266
267 protected:
268 ChannelValue getInputAt(size_t) const override
269 {
270 throw UnknownChannel("Festo has no input channels.");
271 }
272 2 void setOutputAt(size_t i, const ChannelValue &value) override
273 {
274 2 device.setOutput(static_cast<unsigned int>(i), std::get<bool>(value));
275 2 }
276
277 private:
278 Festo<mode> device;
279 std::vector<ChannelKind> inputKinds_;
280 std::vector<ChannelKind> outputKinds_;
281 };
282
283 /****************************************************************************/
284
285 namespace {
286
287 8 DeviceFactoryFn makeFestoFactory()
288 {
289 2 return [](const SlaveContext &ctx) -> unique_ptr<SubDevice> {
290
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ctx.mode == Mode::Control) {
291 return make_unique<FestoAdapter<Mode::Control>>(ctx);
292 }
293 else {
294 2 return make_unique<FestoAdapter<Mode::Simulation>>(ctx);
295 }
296
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 };
297 }
298
299 struct FestoRegistration
300 {
301 8 FestoRegistration()
302 {
303
4/8
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 8 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 8 times.
✗ Branch 14 not taken.
24 Registry::instance().add(
304 {"Festo", __FILE__, 0x0000001d, 0x0008bc8c, AnyRevision,
305
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
16 wrapFactory(makeFestoFactory())});
306 8 }
307 8 } festoRegistration;
308
309 } // namespace
310
311 24 } // namespace EcApp
312
313 /****************************************************************************/
314