GCC Code Coverage Report


Directory: ./
File: ecapp/GenericSubDevice.h
Date: 2026-09-23 16:22:15
Exec Total Coverage
Lines: 11 11 100.0%
Branches: 1 2 50.0%

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 #ifndef ECAPP_GENERICSUBDEVICE_H
24 #define ECAPP_GENERICSUBDEVICE_H
25
26 /****************************************************************************/
27
28 #include "ecapp/GenericDevice.h"
29 #include "ecapp/Mode.h"
30 #include "ecapp/SubDevice.h" // ChannelValue
31 #include "ecapp_export.h"
32
33 #include <cstdint>
34 #include <ecrt.h> // EtherCAT realtime interface
35 #include <memory>
36 #include <string>
37 #include <vector>
38
39 /****************************************************************************/
40
41 namespace EcApp {
42
43 class Master;
44 class Domain;
45
46 /** One "real" (non-Gap) PDO entry of a GenericSubDevice, in wire order.
47 * index/subindex address it exactly as ecrt_slave_config_reg_pdo_entry()
48 * expects -- the slave's own CoE object dictionary indices, not an
49 * ecapp-invented scheme (unlike EcApp::VariablePdo, see below). name
50 * becomes this entry's ChannelKind name and must be unique per direction
51 * (see SubDevice::inputIndex()/outputIndex()); type is a
52 * default-constructed value of the desired channel type, exactly like
53 * PdoChannel::type in VariablePdo.h -- its width (1 bit for bool, 8/16/32
54 * bit otherwise) must match the entry's actual bit_length. */
55
1/2
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
24 struct GenericEntry
56 {
57 uint16_t index;
58 uint8_t subindex;
59 std::string name;
60 ChannelValue type;
61 };
62
63 /** One SDO (CoE mailbox configuration value) to write during slave
64 * configuration, before ecrt_slave_config_pdos() -- e.g. an operating
65 * mode or range setting a slave needs set up before it can be brought
66 * into OP, which a real device's own hand-written class would otherwise
67 * bake in as a fixed ecrt_slave_config_sdo*() call. value's active
68 * alternative decides the width written on the wire (1 byte for bool,
69 * matching ecrt_slave_config_sdo8()'s uint8_t; 8/16/32/64 bit otherwise,
70 * each written with ecrt_slave_config_sdo()'s raw byte form -- see
71 * GenericSubDevice's constructor). There is no read-back: like every
72 * ecrt_slave_config_sdo*() call, this only queues a value to be written
73 * once while the slave is brought into OP, not a live/cyclic channel --
74 * unrelated to inputKinds()/outputKinds(). */
75 3 struct GenericSdo
76 {
77 uint16_t index;
78 uint8_t subindex;
79 ChannelValue value;
80 };
81
82 /** SubDevice implementation for a slave whose PDO layout was not known to
83 * this build of ecapp. Unlike EcApp::VariablePdo (the EL6692
84 * EtherCAT-Bridge, whose RxPDO/TxPDO mapping objects start out empty and
85 * only ever accept application-chosen entries under an ecapp-invented
86 * indexing scheme), GenericSubDevice's entire sync-manager/PDO/entry
87 * structure -- the slave's own, real one -- is supplied wholesale by the
88 * caller at construction: normally generated code, built by
89 * ecapp-generate from a live "ethercat pdos --json" snapshot (see the
90 * generated project's Generic.h/Generic.cpp). One channel per named
91 * entry, count 1 each -- like VariablePdo's declared channels, not like a
92 * fixed terminal's homogeneous count>1 kind.
93 *
94 * Also implements EcApp::GenericDevice, so application code can recognize
95 * "this device runs generically" and learn its vendor/product identity.
96 *
97 * Application code should not name this class directly -- construct
98 * through makeGenericSubDevice() below, which also picks the right Mode
99 * specialization. */
100 template <Mode mode>
101 6 class ECAPP_EXPORT GenericSubDevice : public SubDevice, public GenericDevice
102 {
103 public:
104 /** domainOut/domainIn, syncs and the two entry lists may describe a
105 * device with no process data at all (empty vectors, syncs
106 * containing only mailbox sync managers and/or the {0xff}
107 * terminator) -- a valid, if useless, end state for a pure
108 * infrastructure slave (e.g. a bus coupler), not an error. syncs
109 * must stay valid only for the duration of this call (see
110 * ecrt_slave_config_pdos(), which copies it); outputEntries/
111 * inputEntries are consumed here too, nothing is retained beyond
112 * their names/types. sdos (may be empty) are written in the order
113 * given, after the slave configuration is obtained but before
114 * ecrt_slave_config_pdos() -- see GenericSdo. */
115 GenericSubDevice(
116 Master *master,
117 uint16_t alias,
118 uint16_t position,
119 uint32_t vendorId,
120 uint32_t productCode,
121 Domain *domainOut,
122 Domain *domainIn,
123 const ec_sync_info_t *syncs,
124 const std::vector<GenericEntry> &outputEntries,
125 const std::vector<GenericEntry> &inputEntries,
126 const std::string &prefix,
127 pdserv *pdServ,
128 pdtask *task,
129 const std::vector<GenericSdo> &sdos = {});
130
131 void updateInputs() override;
132 void updateOutputs() override;
133
134 6 const std::vector<ChannelKind> &inputKinds() const override
135 {
136 6 return inputKinds_;
137 }
138 8 const std::vector<ChannelKind> &outputKinds() const override
139 {
140 8 return outputKinds_;
141 }
142
143 2 uint32_t vendorId() const override { return vendorId_; }
144 2 uint32_t productCode() const override { return productCode_; }
145
146 /** See EcApp::GenericDevice::configureSdo() -- callable any time
147 * after construction, e.g. from RtMain's constructor body, before
148 * EcApp::Master::activate(). */
149 void configureSdo(
150 uint16_t index,
151 uint8_t subindex,
152 const ChannelValue &value) override;
153
154 protected:
155 ChannelValue getInputAt(std::size_t position) const override;
156 void
157 setOutputAt(std::size_t position, const ChannelValue &value) override;
158
159 private:
160 3 struct InChannel
161 {
162 int offset = -1;
163 unsigned int bit = 0;
164 ChannelValue value;
165 };
166
167 3 struct OutChannel
168 {
169 int offset = -1;
170 unsigned int bit = 0;
171 ChannelValue value;
172 };
173
174 uint32_t vendorId_;
175 uint32_t productCode_;
176 Domain *domainOut_;
177 Domain *domainIn_;
178 ec_slave_config_t *sc_ = nullptr; // null if constructed without a
179 // Master (no bus); configureSdo()
180 // is then a no-op
181
182 std::vector<InChannel> inputs_;
183 std::vector<OutChannel> outputs_;
184 std::vector<ChannelKind> inputKinds_;
185 std::vector<ChannelKind> outputKinds_;
186 };
187
188 /** Dispatches to GenericSubDevice<Mode::Control> or
189 * GenericSubDevice<Mode::Simulation> -- exactly what every registered
190 * device family's own factory does internally (see e.g.
191 * makeEL6692Factory() in src/devices/EL6692.cpp), just callable directly
192 * instead of through EcApp::Registry/createSubDevice(): GenericSubDevice
193 * is deliberately not registered there, since its PDO tables are
194 * generated per application (into the generated project's Generic.h/
195 * Generic.cpp), not compiled into this library -- there is nothing
196 * library-wide a Registry entry could hold, and self-registering under
197 * the slave's own vendor/product from the generated project would only
198 * risk an ambiguous collision should a future ecapp version ship a real
199 * implementation for the same vendor/product.
200 *
201 * This is the only entry point generated (or hand-written) code needs;
202 * the class template above is an implementation detail. */
203 ECAPP_EXPORT std::unique_ptr<SubDevice> makeGenericSubDevice(
204 Mode mode,
205 Master *master,
206 uint16_t alias,
207 uint16_t position,
208 uint32_t vendorId,
209 uint32_t productCode,
210 Domain *domainOut,
211 Domain *domainIn,
212 const ec_sync_info_t *syncs,
213 const std::vector<GenericEntry> &outputEntries,
214 const std::vector<GenericEntry> &inputEntries,
215 const std::string &prefix,
216 pdserv *pdServ,
217 pdtask *task,
218 const std::vector<GenericSdo> &sdos = {});
219
220 } // namespace EcApp
221
222 #endif // ECAPP_GENERICSUBDEVICE_H
223
224 /****************************************************************************/
225