| Directory: | ./ |
|---|---|
| File: | src/GenericSubDevice.cpp |
| Date: | 2026-09-23 16:22:15 |
| Exec | Total | Coverage | |
|---|---|---|---|
| Lines: | 61 | 141 | 43.3% |
| Branches: | 61 | 380 | 16.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 "ecapp/GenericSubDevice.h" | ||
| 24 | |||
| 25 | #include "ecapp/Domain.h" | ||
| 26 | #include "ecapp/Master.h" | ||
| 27 | #include "ecapp/SwappedSync.h" | ||
| 28 | |||
| 29 | #include <cstring> | ||
| 30 | #include <memory> | ||
| 31 | #include <sstream> | ||
| 32 | #include <stdexcept> | ||
| 33 | #include <type_traits> | ||
| 34 | #include <variant> | ||
| 35 | |||
| 36 | /****************************************************************************/ | ||
| 37 | |||
| 38 | using std::make_unique; | ||
| 39 | using std::runtime_error; | ||
| 40 | using std::size_t; | ||
| 41 | using std::stringstream; | ||
| 42 | using std::unique_ptr; | ||
| 43 | |||
| 44 | namespace EcApp { | ||
| 45 | |||
| 46 | namespace { | ||
| 47 | |||
| 48 | /** Bit length of a channel's type on the wire -- bool is packed into a | ||
| 49 | * single bit like every other digital signal in this library; every | ||
| 50 | * other alternative gets its natural memory width. Same helper as | ||
| 51 | * src/devices/EL6692.cpp's bitLenOf(); duplicated rather than shared | ||
| 52 | * since neither file is meant to depend on the other. */ | ||
| 53 | ✗ | unsigned int bitLenOf(const ChannelValue &v) | |
| 54 | { | ||
| 55 | ✗ | return std::visit( | |
| 56 | ✗ | [](auto &&value) -> unsigned int { | |
| 57 | using T = std::decay_t<decltype(value)>; | ||
| 58 | if constexpr (std::is_same_v<T, bool>) { | ||
| 59 | ✗ | return 1; | |
| 60 | } | ||
| 61 | else { | ||
| 62 | ✗ | return sizeof(T) * 8; | |
| 63 | } | ||
| 64 | }, | ||
| 65 | ✗ | v); | |
| 66 | } | ||
| 67 | |||
| 68 | /** Raw little-endian bytes of a ChannelValue's active alternative, for | ||
| 69 | * ecrt_slave_config_sdo() (see GenericSdo) -- bool is written as a | ||
| 70 | * single 0/1 byte (matching ecrt_slave_config_sdo8()'s uint8_t; plain | ||
| 71 | * `bool`'s own size is implementation-defined, so it is not memcpy'd | ||
| 72 | * directly), every other alternative as its natural in-memory | ||
| 73 | * representation, which is already little-endian on every platform this | ||
| 74 | * library targets (same assumption the EC_READ_.../EC_WRITE_... macros | ||
| 75 | * already make). */ | ||
| 76 | ✗ | std::vector<uint8_t> sdoBytes(const ChannelValue &v) | |
| 77 | { | ||
| 78 | return std::visit( | ||
| 79 | ✗ | [](auto &&value) -> std::vector<uint8_t> { | |
| 80 | using T = std::decay_t<decltype(value)>; | ||
| 81 | if constexpr (std::is_same_v<T, bool>) { | ||
| 82 | ✗ | return {static_cast<uint8_t>(value ? 1 : 0)}; | |
| 83 | } | ||
| 84 | else { | ||
| 85 | ✗ | std::vector<uint8_t> bytes(sizeof(T)); | |
| 86 | ✗ | std::memcpy(bytes.data(), &value, sizeof(T)); | |
| 87 | ✗ | return bytes; | |
| 88 | } | ||
| 89 | }, | ||
| 90 | ✗ | v); | |
| 91 | } | ||
| 92 | |||
| 93 | /** Reads the domain data at (offset, bit) into a ChannelValue holding the | ||
| 94 | * same alternative as `prototype`. */ | ||
| 95 | ChannelValue | ||
| 96 | ✗ | readRaw(const uint8_t *data, | |
| 97 | int offset, | ||
| 98 | unsigned int bit, | ||
| 99 | const ChannelValue &prototype) | ||
| 100 | { | ||
| 101 | return std::visit( | ||
| 102 | ✗ | [&](auto &&proto) -> ChannelValue { | |
| 103 | using T = std::decay_t<decltype(proto)>; | ||
| 104 | if constexpr (std::is_same_v<T, bool>) { | ||
| 105 | ✗ | return static_cast<bool>(EC_READ_BIT(data + offset, bit)); | |
| 106 | } | ||
| 107 | else if constexpr (std::is_same_v<T, uint8_t>) { | ||
| 108 | ✗ | return EC_READ_U8(data + offset); | |
| 109 | } | ||
| 110 | else if constexpr (std::is_same_v<T, int8_t>) { | ||
| 111 | ✗ | return EC_READ_S8(data + offset); | |
| 112 | } | ||
| 113 | else if constexpr (std::is_same_v<T, uint16_t>) { | ||
| 114 | ✗ | return EC_READ_U16(data + offset); | |
| 115 | } | ||
| 116 | else if constexpr (std::is_same_v<T, int16_t>) { | ||
| 117 | ✗ | return EC_READ_S16(data + offset); | |
| 118 | } | ||
| 119 | else if constexpr (std::is_same_v<T, uint32_t>) { | ||
| 120 | ✗ | return EC_READ_U32(data + offset); | |
| 121 | } | ||
| 122 | else if constexpr (std::is_same_v<T, int32_t>) { | ||
| 123 | ✗ | return EC_READ_S32(data + offset); | |
| 124 | } | ||
| 125 | else if constexpr (std::is_same_v<T, float>) { | ||
| 126 | ✗ | return EC_READ_REAL(data + offset); | |
| 127 | } | ||
| 128 | else { // double | ||
| 129 | ✗ | return EC_READ_LREAL(data + offset); | |
| 130 | } | ||
| 131 | }, | ||
| 132 | ✗ | prototype); | |
| 133 | } | ||
| 134 | |||
| 135 | /** Writes `value` to the domain data at (offset, bit). */ | ||
| 136 | ✗ | void writeRaw( | |
| 137 | uint8_t *data, | ||
| 138 | int offset, | ||
| 139 | unsigned int bit, | ||
| 140 | const ChannelValue &value) | ||
| 141 | { | ||
| 142 | ✗ | std::visit( | |
| 143 | ✗ | [&](auto &&v) { | |
| 144 | using T = std::decay_t<decltype(v)>; | ||
| 145 | if constexpr (std::is_same_v<T, bool>) { | ||
| 146 | ✗ | EC_WRITE_BIT(data + offset, bit, v); | |
| 147 | } | ||
| 148 | else if constexpr (std::is_same_v<T, uint8_t>) { | ||
| 149 | ✗ | EC_WRITE_U8(data + offset, v); | |
| 150 | } | ||
| 151 | else if constexpr (std::is_same_v<T, int8_t>) { | ||
| 152 | ✗ | EC_WRITE_S8(data + offset, v); | |
| 153 | } | ||
| 154 | else if constexpr (std::is_same_v<T, uint16_t>) { | ||
| 155 | ✗ | EC_WRITE_U16(data + offset, v); | |
| 156 | } | ||
| 157 | else if constexpr (std::is_same_v<T, int16_t>) { | ||
| 158 | ✗ | EC_WRITE_S16(data + offset, v); | |
| 159 | } | ||
| 160 | else if constexpr (std::is_same_v<T, uint32_t>) { | ||
| 161 | ✗ | EC_WRITE_U32(data + offset, v); | |
| 162 | } | ||
| 163 | else if constexpr (std::is_same_v<T, int32_t>) { | ||
| 164 | ✗ | EC_WRITE_S32(data + offset, v); | |
| 165 | } | ||
| 166 | else if constexpr (std::is_same_v<T, float>) { | ||
| 167 | ✗ | EC_WRITE_REAL(data + offset, v); | |
| 168 | } | ||
| 169 | else { // double | ||
| 170 | ✗ | EC_WRITE_LREAL(data + offset, v); | |
| 171 | } | ||
| 172 | ✗ | }, | |
| 173 | value); | ||
| 174 | } | ||
| 175 | |||
| 176 | /** Throws if two entries in `entries` share a name -- ecapp cannot | ||
| 177 | * address them unambiguously afterwards (see | ||
| 178 | * SubDevice::inputIndex()/outputIndex()). ecapp-generate already | ||
| 179 | * deduplicates names before emitting GenericEntry tables, so this should | ||
| 180 | * never trigger for generated code; kept as a defense-in-depth check for | ||
| 181 | * hand-written callers too, same as EL6692's checkUniqueNames(). */ | ||
| 182 | 9 | void checkUniqueNames( | |
| 183 | const std::string &direction, | ||
| 184 | const std::vector<GenericEntry> &entries) | ||
| 185 | { | ||
| 186 |
2/2✓ Branch 1 taken 7 times.
✓ Branch 2 taken 8 times.
|
15 | for (size_t i = 0; i < entries.size(); i++) { |
| 187 |
2/2✓ Branch 1 taken 3 times.
✓ Branch 2 taken 6 times.
|
9 | for (size_t j = i + 1; j < entries.size(); j++) { |
| 188 |
2/2✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
|
3 | if (entries[i].name == entries[j].name) { |
| 189 |
1/2✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | stringstream err; |
| 190 | err << "GenericSubDevice: " << direction << " channel '" | ||
| 191 |
5/10✓ 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 11 taken 1 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 1 times.
✗ Branch 15 not taken.
|
1 | << entries[i].name << "' declared more than once."; |
| 192 |
2/4✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
|
1 | throw runtime_error(err.str()); |
| 193 | } | ||
| 194 | } | ||
| 195 | } | ||
| 196 | 8 | } | |
| 197 | |||
| 198 | /** Throws if an input and an output entry share a name -- both would | ||
| 199 | * register the same pdserv signal path (see EL6692's | ||
| 200 | * checkNoCrossDirectionCollision(), same rationale). */ | ||
| 201 | 4 | void checkNoCrossDirectionCollision( | |
| 202 | const std::vector<GenericEntry> &outputEntries, | ||
| 203 | const std::vector<GenericEntry> &inputEntries) | ||
| 204 | { | ||
| 205 |
2/2✓ Branch 3 taken 3 times.
✓ Branch 4 taken 3 times.
|
6 | for (const auto &out : outputEntries) { |
| 206 |
2/2✓ Branch 3 taken 5 times.
✓ Branch 4 taken 2 times.
|
7 | for (const auto &in : inputEntries) { |
| 207 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
|
5 | if (in.name == out.name) { |
| 208 |
1/2✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | stringstream err; |
| 209 | err << "GenericSubDevice: channel '" << in.name | ||
| 210 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | << "' declared as both an input and an output -- " |
| 211 | "both would register the same pdserv signal " | ||
| 212 |
2/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | "path."; |
| 213 |
2/4✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
|
1 | throw runtime_error(err.str()); |
| 214 | } | ||
| 215 | } | ||
| 216 | } | ||
| 217 | 3 | } | |
| 218 | |||
| 219 | } // namespace | ||
| 220 | |||
| 221 | /****************************************************************************/ | ||
| 222 | |||
| 223 | template <Mode mode> | ||
| 224 | 5 | GenericSubDevice<mode>::GenericSubDevice( | |
| 225 | Master *master, | ||
| 226 | uint16_t alias, | ||
| 227 | uint16_t position, | ||
| 228 | uint32_t vendorId, | ||
| 229 | uint32_t productCode, | ||
| 230 | Domain *domainOut, | ||
| 231 | Domain *domainIn, | ||
| 232 | const ec_sync_info_t *syncs, | ||
| 233 | const std::vector<GenericEntry> &outputEntries, | ||
| 234 | const std::vector<GenericEntry> &inputEntries, | ||
| 235 | const std::string &prefix, | ||
| 236 | pdserv *pdServ, | ||
| 237 | pdtask *task, | ||
| 238 | const std::vector<GenericSdo> &sdos) : | ||
| 239 | SubDevice(domainOut, domainIn, pdServ, task, prefix), | ||
| 240 | vendorId_(vendorId), | ||
| 241 | productCode_(productCode), | ||
| 242 | domainOut_(domainOut), | ||
| 243 |
1/4✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
|
7 | domainIn_(domainIn) |
| 244 | { | ||
| 245 |
3/8✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 1 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
|
6 | checkUniqueNames("output", outputEntries); |
| 246 |
2/8✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
|
4 | checkUniqueNames("input", inputEntries); |
| 247 |
2/4✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
4 | checkNoCrossDirectionCollision(outputEntries, inputEntries); |
| 248 | |||
| 249 |
2/4✓ Branch 3 taken 2 times.
✓ Branch 4 taken 3 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
5 | for (const auto &e : outputEntries) { |
| 250 |
3/12✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
2 | outputKinds_.push_back({e.name, 1, e.type}); |
| 251 | 2 | OutChannel ch; | |
| 252 |
1/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
2 | ch.value = e.type; |
| 253 |
1/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
2 | outputs_.push_back(ch); |
| 254 | } | ||
| 255 |
2/4✓ Branch 3 taken 2 times.
✓ Branch 4 taken 3 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
5 | for (const auto &e : inputEntries) { |
| 256 |
3/12✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
2 | inputKinds_.push_back({e.name, 1, e.type}); |
| 257 | 2 | InChannel ch; | |
| 258 |
1/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
2 | ch.value = e.type; |
| 259 |
1/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
2 | inputs_.push_back(ch); |
| 260 | } | ||
| 261 | |||
| 262 | // ChannelValue-typed registration (channel types aren't fixed at | ||
| 263 | // compile time here, same situation as EL6692's VariablePdo | ||
| 264 | // channels) -- also what makes every channel alias-able through | ||
| 265 | // SubDevice::setAlias(). | ||
| 266 |
2/4✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
5 | for (size_t i = 0; i < outputEntries.size(); i++) { |
| 267 |
1/4✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
2 | registerOutputChannel(outputEntries[i].name, outputs_[i].value); |
| 268 | } | ||
| 269 |
2/4✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
5 | for (size_t i = 0; i < inputEntries.size(); i++) { |
| 270 |
1/4✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
2 | registerInputChannel(inputEntries[i].name, inputs_[i].value); |
| 271 | } | ||
| 272 | |||
| 273 |
1/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
3 | if (!master) { |
| 274 | 3 | return; // no bus, e.g. unit tests without hardware | |
| 275 | } | ||
| 276 | |||
| 277 | ✗ | sc_ = ecrt_master_slave_config( | |
| 278 | master->getMaster(), alias, position, vendorId, productCode); | ||
| 279 | ✗ | if (!sc_) { | |
| 280 | ✗ | throw runtime_error("Failed to get slave configuration."); | |
| 281 | } | ||
| 282 | |||
| 283 | // SDOs are configured before the PDO mapping -- some slaves need an | ||
| 284 | // SDO write (e.g. an operating mode) before their PDO layout even | ||
| 285 | // makes sense, and this matches the order typical hand-written | ||
| 286 | // device classes use (SDO setup, then ecrt_slave_config_pdos()). | ||
| 287 | // Same call application code uses later, from outside the | ||
| 288 | // constructor (see configureSdo() below) -- sdos passed here is | ||
| 289 | // just a convenience for values already known at construction time. | ||
| 290 | ✗ | for (const auto &sdo : sdos) { | |
| 291 | ✗ | configureSdo(sdo.index, sdo.subindex, sdo.value); | |
| 292 | } | ||
| 293 | |||
| 294 | // SwappedSync<Mode::Control> only ever stores `syncs` itself (never | ||
| 295 | // mutates it); SwappedSync<Mode::Simulation> copies it into its own | ||
| 296 | // heap array before swapping directions there -- either way `syncs` | ||
| 297 | // itself is never written through, so the const_cast here is safe | ||
| 298 | // (see ecapp/SwappedSync.h/.cpp). | ||
| 299 | ✗ | if (ecrt_slave_config_pdos( | |
| 300 | sc_, EC_END, | ||
| 301 | SwappedSync<mode>(const_cast<ec_sync_info_t *>(syncs)) | ||
| 302 | .getSync())) { | ||
| 303 | ✗ | throw runtime_error("Failed to configure PDOs."); | |
| 304 | } | ||
| 305 | |||
| 306 | ✗ | for (size_t i = 0; i < outputEntries.size(); i++) { | |
| 307 | ✗ | const auto &e = outputEntries[i]; | |
| 308 | ✗ | unsigned int bitLen = bitLenOf(e.type); | |
| 309 | ✗ | outputs_[i].offset = ecrt_slave_config_reg_pdo_entry( | |
| 310 | ✗ | sc_, e.index, e.subindex, domainOut->getDomain(), | |
| 311 | ✗ | bitLen == 1 ? &outputs_[i].bit : NULL); | |
| 312 | ✗ | if (outputs_[i].offset < 0) { | |
| 313 | ✗ | stringstream err; | |
| 314 | ✗ | err << "Failed to register generic output PDO entry '" << e.name | |
| 315 | ✗ | << "'."; | |
| 316 | ✗ | throw runtime_error(err.str()); | |
| 317 | } | ||
| 318 | } | ||
| 319 | ✗ | for (size_t i = 0; i < inputEntries.size(); i++) { | |
| 320 | ✗ | const auto &e = inputEntries[i]; | |
| 321 | ✗ | unsigned int bitLen = bitLenOf(e.type); | |
| 322 | ✗ | inputs_[i].offset = ecrt_slave_config_reg_pdo_entry( | |
| 323 | ✗ | sc_, e.index, e.subindex, domainIn->getDomain(), | |
| 324 | ✗ | bitLen == 1 ? &inputs_[i].bit : NULL); | |
| 325 | ✗ | if (inputs_[i].offset < 0) { | |
| 326 | ✗ | stringstream err; | |
| 327 | ✗ | err << "Failed to register generic input PDO entry '" << e.name | |
| 328 | ✗ | << "'."; | |
| 329 | ✗ | throw runtime_error(err.str()); | |
| 330 | } | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | /****************************************************************************/ | ||
| 335 | |||
| 336 | template <Mode mode> | ||
| 337 | 1 | void GenericSubDevice<mode>::configureSdo( | |
| 338 | uint16_t index, | ||
| 339 | uint8_t subindex, | ||
| 340 | const ChannelValue &value) | ||
| 341 | { | ||
| 342 |
1/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1 | if (!sc_) { |
| 343 | 1 | return; // no bus, e.g. unit tests without hardware | |
| 344 | } | ||
| 345 | ✗ | std::vector<uint8_t> bytes = sdoBytes(value); | |
| 346 | ✗ | if (ecrt_slave_config_sdo( | |
| 347 | ✗ | sc_, index, subindex, bytes.data(), bytes.size())) { | |
| 348 | ✗ | stringstream err; | |
| 349 | ✗ | err << "Failed to configure SDO 0x" << std::hex << index << ":" | |
| 350 | ✗ | << static_cast<unsigned int>(subindex) << "."; | |
| 351 | ✗ | throw runtime_error(err.str()); | |
| 352 | } | ||
| 353 | } | ||
| 354 | |||
| 355 | /****************************************************************************/ | ||
| 356 | |||
| 357 | template <Mode mode> | ||
| 358 | 2 | void GenericSubDevice<mode>::updateInputs() | |
| 359 | { | ||
| 360 | 2 | const uint8_t *data = domainIn_->getData(); | |
| 361 |
1/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
2 | if (!data) { |
| 362 | 2 | return; | |
| 363 | } | ||
| 364 | ✗ | for (auto &ch : inputs_) { | |
| 365 | ✗ | ch.value = readRaw(data, ch.offset, ch.bit, ch.value); | |
| 366 | } | ||
| 367 | } | ||
| 368 | |||
| 369 | /****************************************************************************/ | ||
| 370 | |||
| 371 | template <Mode mode> | ||
| 372 | 3 | void GenericSubDevice<mode>::updateOutputs() | |
| 373 | { | ||
| 374 | 3 | uint8_t *data = domainOut_->getData(); | |
| 375 |
1/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
3 | if (!data) { |
| 376 | 3 | return; | |
| 377 | } | ||
| 378 | ✗ | for (auto &ch : outputs_) { | |
| 379 | ✗ | writeRaw(data, ch.offset, ch.bit, ch.value); | |
| 380 | } | ||
| 381 | } | ||
| 382 | |||
| 383 | /****************************************************************************/ | ||
| 384 | |||
| 385 | template <Mode mode> | ||
| 386 | 1 | ChannelValue GenericSubDevice<mode>::getInputAt(size_t position) const | |
| 387 | { | ||
| 388 | 1 | return inputs_.at(position).value; | |
| 389 | } | ||
| 390 | |||
| 391 | /****************************************************************************/ | ||
| 392 | |||
| 393 | template <Mode mode> | ||
| 394 | 3 | void GenericSubDevice<mode>::setOutputAt( | |
| 395 | size_t position, | ||
| 396 | const ChannelValue &value) | ||
| 397 | { | ||
| 398 | 3 | auto &ch = outputs_.at(position); | |
| 399 | // Same same-index-assignment contract as EL6692::setOutput() -- see | ||
| 400 | // its comment for why: preserves the address already handed to | ||
| 401 | // pdserv for this channel. | ||
| 402 |
2/4✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
3 | if (value.index() != ch.value.index()) { |
| 403 | 1 | throw std::bad_variant_access(); | |
| 404 | } | ||
| 405 | 2 | ch.value = value; | |
| 406 | 2 | } | |
| 407 | |||
| 408 | /****************************************************************************/ | ||
| 409 | |||
| 410 | template class GenericSubDevice<Mode::Control>; | ||
| 411 | template class GenericSubDevice<Mode::Simulation>; | ||
| 412 | |||
| 413 | /****************************************************************************/ | ||
| 414 | |||
| 415 | 5 | unique_ptr<SubDevice> makeGenericSubDevice( | |
| 416 | Mode mode, | ||
| 417 | Master *master, | ||
| 418 | uint16_t alias, | ||
| 419 | uint16_t position, | ||
| 420 | uint32_t vendorId, | ||
| 421 | uint32_t productCode, | ||
| 422 | Domain *domainOut, | ||
| 423 | Domain *domainIn, | ||
| 424 | const ec_sync_info_t *syncs, | ||
| 425 | const std::vector<GenericEntry> &outputEntries, | ||
| 426 | const std::vector<GenericEntry> &inputEntries, | ||
| 427 | const std::string &prefix, | ||
| 428 | pdserv *pdServ, | ||
| 429 | pdtask *task, | ||
| 430 | const std::vector<GenericSdo> &sdos) | ||
| 431 | { | ||
| 432 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (mode == Mode::Control) { |
| 433 | ✗ | return make_unique<GenericSubDevice<Mode::Control>>( | |
| 434 | master, alias, position, vendorId, productCode, domainOut, | ||
| 435 | domainIn, syncs, outputEntries, inputEntries, prefix, pdServ, | ||
| 436 | ✗ | task, sdos); | |
| 437 | } | ||
| 438 | else { | ||
| 439 | 8 | return make_unique<GenericSubDevice<Mode::Simulation>>( | |
| 440 | master, alias, position, vendorId, productCode, domainOut, | ||
| 441 | domainIn, syncs, outputEntries, inputEntries, prefix, pdServ, | ||
| 442 | 3 | task, sdos); | |
| 443 | } | ||
| 444 | } | ||
| 445 | |||
| 446 | } // namespace EcApp | ||
| 447 | |||
| 448 | /****************************************************************************/ | ||
| 449 |