Motion Master 6.0.0-alpha.86
Next-generation motion control software
Loading...
Searching...
No Matches
device.h
Go to the documentation of this file.
1#pragma once
2
3#include <cstdint>
4#include <cstring>
5#include <deque>
6#include <expected>
7#include <format>
8#include <map>
9#include <memory>
10#include <mutex>
11#include <nlohmann/json_fwd.hpp>
12#include <optional>
13#include <span>
14#include <string>
15#include <type_traits>
16#include <unordered_map>
17#include <utility>
18#include <variant>
19#include <vector>
20
23#include "node/pdo_mapping.h"
24
25namespace mm::node {
26
30struct ProcessData;
31
35class ParameterCache;
36
42
67
71 uint16_t index = 0;
72 std::map<uint8_t, DeviceParameterValue> values;
73
77 template <typename T>
78 std::expected<T, std::string> get(uint8_t subindex) const {
79 auto it = values.find(subindex);
80 if (it == values.end()) {
81 return std::unexpected(
82 std::format("object 0x{:04X} has no readable subindex {:02X}", index, subindex));
83 }
84 if (const auto* p = std::get_if<T>(&it->second)) {
85 return *p;
86 }
87 return std::unexpected(
88 std::format("parameter 0x{:04X}:{:02X} holds a different type", index, subindex));
89 }
90};
91
116class Device {
117 public:
130 ProcessData* processData = nullptr, const ParameterCache* parameterCache = nullptr);
131
133 uint16_t slavePosition() const;
134
139 const std::string& name() const;
140
147 std::string productName() const;
148
150 uint32_t vendorId() const;
151
153 uint32_t productCode() const;
154
156 uint32_t revisionNumber() const;
157
159 uint32_t serialNumber() const;
160
167 bool isCia402() const;
168
179 bool mailboxActive() const;
180
188 bool supportsCoe() const;
189
197 bool exchangesProcessData() const;
198
207 std::expected<std::vector<uint8_t>, std::string> readSdo(uint16_t index, uint8_t subindex) const;
208
219 std::expected<void, std::string> writeSdo(uint16_t index, uint8_t subindex,
220 std::span<const uint8_t> data) const;
221
229 std::expected<std::vector<uint8_t>, mm::comm::FoeError> readFile(
230 const std::string& filename) const;
231
237 std::expected<void, mm::comm::FoeError> writeFile(const std::string& filename,
238 std::span<const uint8_t> data) const;
239
247 std::expected<void, std::string> readRegister(uint16_t address, std::span<uint8_t> data) const;
248
256 std::expected<void, std::string> writeRegister(uint16_t address,
257 std::span<const uint8_t> data) const;
258
266 std::expected<std::vector<uint8_t>, std::string> readSii() const;
267
277 std::expected<void, std::string> writeSii(std::span<const uint8_t> data) const;
278
299 std::expected<void, std::string> initializeParameters(bool readValues = false,
300 bool useCompleteAccess = true);
301
316 std::expected<void, std::string> readFlatPdoMapping();
317
319 const FlatPdoMapping& flatPdoMapping() const;
320
329 std::expected<PdoMapping, std::string> readPdoMapping();
330
360 std::expected<void, std::string> writePdoMapping(const PdoMapping& mapping);
361
374 std::expected<DeviceParameterValue, std::string> setValueFromBytes(
375 uint16_t index, uint8_t subindex, std::span<const uint8_t> bytes);
376
383 bool hasParameters() const;
384
396 bool parametersUnavailable() const;
397
403 std::vector<DeviceParameter> parametersOrdered() const;
404
417 std::optional<DeviceParameter> parameter(uint16_t index, uint8_t subindex) const;
418
433 std::optional<DeviceParameterValue> parameterValue(uint16_t index, uint8_t subindex) const;
434
444 std::optional<uint16_t> dataType(uint16_t index, uint8_t subindex) const;
445
461 std::expected<DeviceParameterValue, std::string> readParameter(uint16_t index, uint8_t subindex);
462
482 std::expected<void, std::string> readAllParameters(bool useCompleteAccess = true);
483
504 std::expected<ObjectValues, std::string> readObject(uint16_t index,
505 bool useCompleteAccess = true);
506
526 std::expected<void, std::string> writeParameter(uint16_t index, uint8_t subindex,
527 const DeviceParameterValue& newValue);
528
542 template <typename T>
543 std::expected<void, std::string> writeValue(uint16_t index, uint8_t subindex, T newValue) {
544 return writeParameter(index, subindex, DeviceParameterValue{newValue});
545 }
546
558 template <typename T>
559 std::expected<T, std::string> readValue(uint16_t index, uint8_t subindex) {
560 auto v = readParameter(index, subindex);
561 if (!v) {
562 return std::unexpected(v.error());
563 }
564 if (const auto* p = std::get_if<T>(&*v)) {
565 return *p;
566 }
567 return std::unexpected(
568 std::format("parameter 0x{:04X}:{:02X} holds a different type", index, subindex));
569 }
570
575 template <typename T>
576 std::expected<T, std::string> readValue(ObjectAddress<T> address) {
577 return readValue<T>(address.index, address.subindex);
578 }
579
581 template <typename T>
582 std::expected<void, std::string> writeValue(ObjectAddress<T> address, T newValue) {
583 return writeValue<T>(address.index, address.subindex, std::move(newValue));
584 }
585
608 template <typename T>
609 std::expected<T, std::string> readCachedValue(uint16_t index, uint8_t subindex) {
610 {
611 std::lock_guard<std::mutex> lock(*parametersMutex_);
612 if (const DeviceParameter* p = findParameter(index, subindex);
613 p && p->syncState == SyncState::Synced) {
614 return p->getValue<T>();
615 }
616 }
617 return readValue<T>(index, subindex); // never read: fetch once — readValue marks it Synced.
618 }
619
634 DeviceParameter* findParameter(uint16_t index, uint8_t subindex);
635
637 const DeviceParameter* findParameter(uint16_t index, uint8_t subindex) const;
638
666 template <typename T>
667 std::optional<T> value(uint16_t index, uint8_t subindex) const {
668 const DeviceParameter* p = findParameter(index, subindex);
669 if (p == nullptr) {
670 return std::nullopt;
671 }
672 return p->scalar<T>();
673 }
674
700 template <typename T>
701 bool setValue(uint16_t index, uint8_t subindex, T newValue) {
702 static_assert(std::is_arithmetic_v<T>, "the cell holds arithmetic types only");
703 static_assert(sizeof(T) <= sizeof(uint64_t), "the cell holds at most eight bytes");
704 const DeviceParameter* p = findParameter(index, subindex);
705 if (p == nullptr || !std::holds_alternative<T>(defaultValueForDataType(p->dataType))) {
706 return false;
707 }
708 uint64_t bits = 0;
709 std::memcpy(&bits, &newValue, sizeof(T));
710 p->storeBits(bits);
711 return true;
712 }
713
718 template <typename T>
719 std::optional<T> value(ObjectAddress<T> address) const {
720 return value<T>(address.index, address.subindex);
721 }
722
724 template <typename T>
725 bool setValue(ObjectAddress<T> address, T newValue) {
726 return setValue<T>(address.index, address.subindex, newValue);
727 }
728
729 private:
747 void publishParameters(std::unordered_map<uint32_t, DeviceParameter>&& built);
748
757 void readParameterValues(std::vector<DeviceParameter>& defs, bool useCompleteAccess);
758
768 bool recordDeclaredSubindexRead(bool ok);
769
773 bool declaredSubindexReadsWorthTrying() const;
774
792 bool readObjectComplete(uint16_t index, std::span<const uint8_t> subindices,
793 bool useCompleteAccess);
794
803 std::expected<std::vector<PdoMappingObject>, std::string> readPdoAssignment(
804 uint16_t assignmentIndex);
805
816 std::expected<FlatPdoMapping, std::string> readSiiPdoMapping();
817
828 std::expected<std::vector<DeviceParameter>, std::string> buildSiiParameterDefinitions();
829
830 uint16_t slavePosition_;
832 // Live process-data runtime, or nullptr for SDO-only operation. Injected by DeviceManager so
833 // read/writeParameter can prefer PDO over SDO while exchanging. Non-owning; the owner
834 // (DeviceManager) outlives every Device it created. A raw pointer keeps Device
835 // move-constructible.
836 ProcessData* processData_ = nullptr;
837 // On-disk parameter-definition cache, or nullptr to always enumerate live. Non-owning; owned by
838 // DeviceManager, which outlives every Device. Consulted only by initializeParameters.
839 const ParameterCache* parameterCache_ = nullptr;
840 std::string name_;
841 uint32_t vendorId_;
842 uint32_t productCode_;
843 uint32_t revisionNumber_;
844 uint32_t serialNumber_;
845 // EEPROM mailbox-protocol bits, copied at construction. Immutable like the identity fields above,
846 // and cached for the same reason the constructor explains: reading it from the driver on demand
847 // would take the control-plane mutex and so block behind whatever bus operation holds it.
848 uint16_t mailboxProtocols_ = 0;
849 // Guards parameters_ (and caSupport_) against the off-RT monitoring threads (the refresher
850 // refreshes cached values, the sampler reads them) racing the control-plane thread.
851 //
852 // **Never held across bus I/O.** Every method that reads or writes the bus — readParameter,
853 // writeParameter, readObjectComplete, readAllParameters, initializeParameters — takes it, decides
854 // what to transfer, releases it for the transfer, and re-takes it to commit. That is not a
855 // micro-optimisation: a mailbox transfer queues behind the driver's control-plane mutex, which an
856 // FoE file transfer holds for its whole multi-second duration, so a lock held across "one mailbox
857 // round-trip" is in fact held for as long as any unrelated bus traffic takes. With a background
858 // parameter refresh and a user on the FoE page — the ordinary configuration, not a rare one —
859 // that stalled every cached read of the device.
860 //
861 // The rule this imposes on callers: a DeviceParameter* must never be carried across the release,
862 // because initializeParameters replaces the whole map. Re-find after the transfer, and treat a
863 // miss (or a changed dataType) as "re-enumerated mid-transfer" rather than assuming it cannot
864 // happen.
865 //
866 // Held by unique_ptr because std::mutex is neither movable nor copyable, and Device is moved
867 // into DeviceManager's std::vector<Device> (which relocates on growth). The indirection keeps
868 // Device move-constructible (the pointer moves); a Device is never copied, only moved.
869 std::unique_ptr<std::mutex> parametersMutex_;
870
873 std::expected<void, std::string> readParameterDefinitions(bool readValues,
874 bool useCompleteAccess);
875
880 bool parametersUnavailable_ = false;
881 // The cells themselves, and they are never destroyed while this Device lives. A re-enumeration
882 // rebuilds the *map* above, reuses every cell whose declaration is unchanged, and appends a cell
883 // for anything new; it never erases one. So a DeviceParameter* held by a published process image
884 // stays valid across a re-enumeration — which is the point, because buildProcessImage resolves
885 // that pointer once and the RT decode dereferences it every cycle without re-checking anything.
886 // A cyclic task's own lookups are valid for the body of one cycle, not across cycles: the Device
887 // dies with its DeviceSet once the last holder releases it.
888 //
889 // A deque because the standard says an insertion at either end invalidates iterators but leaves
890 // *references* to existing elements valid ([deque.modifiers]) — and pointers into it are exactly
891 // what everything above holds. It allocates in blocks, so a cell costs no allocation of its own.
892 //
893 // std::vector is not an option and reserve() does not rescue it: the count is unknown until the
894 // dictionary is read, a later enumeration can add objects a firmware update introduced, and the
895 // one growth past capacity would dangle every pointer at once, silently and far from the cause.
896 // std::list and vector<unique_ptr<DeviceParameter>> are address-stable too, at one allocation per
897 // cell and an extra indirection, for nothing gained: nothing indexes this container, and no code
898 // on the RT path iterates it (the decode walks the image's entries instead).
899 //
900 // Behind a unique_ptr so that moving a Device — DeviceManager builds its vector<Device> by
901 // emplacing — never moves the deque object itself, and the question of whether a moved deque
902 // preserves element addresses never has to be asked.
903 std::unique_ptr<std::deque<DeviceParameter>> cells_;
904 // (index, subindex) -> the cell that holds that object's value. Replaced wholesale by
905 // publishParameters; the cells it points at outlive every replacement.
906 std::unordered_map<uint32_t, DeviceParameter*> parameters_;
907 // Maps kept alive because the RT cycle did not drain before they were replaced. A cyclic task may
908 // still be walking one, and freeing it then is a use-after-free. They are only ever added
909 // to, which is bounded by how many times a drain fails — an emergency that is logged each time.
910 std::vector<std::unordered_map<uint32_t, DeviceParameter*>> retiredMaps_;
911 FlatPdoMapping flatPdoMapping_;
912 // Discovered Complete Access support (the probe outcome), shared by every grouped read for the
913 // device's lifetime. Read and written only under parametersMutex_.
915 // Whether this device serves a subindex above its object's stated count -- see
916 // DeclaredSubindexReads. Learned by the read sweeps and kept for the device's lifetime. Read and
917 // written only under parametersMutex_.
919};
920
930void to_json(nlohmann::json& j, const Device& d);
931
949std::expected<int, std::string> reconcileDetectedModules(const Device& device);
950
951} // namespace mm::node
Abstract interface for an EtherCAT fieldbus driver.
Definition fieldbus_driver.h:404
Represents a single node on the fieldbus.
Definition device.h:116
bool setValue(uint16_t index, uint8_t subindex, T newValue)
Sets a scalar parameter's value as T. Lock-free, non-allocating.
Definition device.h:701
std::expected< void, std::string > writeSdo(uint16_t index, uint8_t subindex, std::span< const uint8_t > data) const
Writes an object dictionary entry to the device (CoE SDO download).
Definition device.cc:109
std::expected< DeviceParameterValue, std::string > readParameter(uint16_t index, uint8_t subindex)
Reads a parameter value, keeping the cached store in sync.
Definition device.cc:1073
std::expected< void, std::string > readRegister(uint16_t address, std::span< uint8_t > data) const
Reads bytes from an ESC register on this device.
Definition device.cc:124
DeviceParameter * findParameter(uint16_t index, uint8_t subindex)
Parameter lookup by (index, subindex). O(1); nullptr if absent. Takes no lock.
Definition device.cc:1063
std::expected< void, std::string > initializeParameters(bool readValues=false, bool useCompleteAccess=true)
Enumerates the device's CoE object dictionary and populates parameters().
Definition device.cc:142
std::optional< DeviceParameterValue > parameterValue(uint16_t index, uint8_t subindex) const
Returns a copy of a parameter's last known value. No bus access.
Definition device.cc:1036
std::expected< T, std::string > readCachedValue(uint16_t index, uint8_t subindex)
Read-once typed accessor for objects the caller knows are immutable.
Definition device.h:609
uint32_t serialNumber() const
Serial number from EEPROM.
Definition device.cc:68
std::expected< void, std::string > writeParameter(uint16_t index, uint8_t subindex, const DeviceParameterValue &newValue)
Writes a parameter value, always updating the cache first.
Definition device.cc:1346
std::expected< std::vector< uint8_t >, std::string > readSdo(uint16_t index, uint8_t subindex) const
Reads an object dictionary entry from the device (CoE SDO upload).
Definition device.cc:104
std::expected< void, std::string > readAllParameters(bool useCompleteAccess=true)
Refreshes the cached value of every readable parameter, keeping the list intact.
Definition device.cc:1137
bool hasParameters() const
Whether the object dictionary is enumerated (thread-safe; no bus access).
Definition device.cc:584
bool supportsCoe() const
Whether the slave advertises a CoE mailbox — a fixed capability, not a live check.
Definition device.cc:92
std::expected< void, std::string > writePdoMapping(const PdoMapping &mapping)
Writes a new PDO mapping to the device via SDO, then reads it back to verify.
Definition device.cc:936
std::expected< void, std::string > writeRegister(uint16_t address, std::span< const uint8_t > data) const
Writes bytes to an ESC register on this device.
Definition device.cc:129
const std::string & name() const
Human-readable node name from SII EEPROM.
Definition device.cc:56
std::expected< void, std::string > writeValue(uint16_t index, uint8_t subindex, T newValue)
Typed convenience wrapper for writeParameter.
Definition device.h:543
bool setValue(ObjectAddress< T > address, T newValue)
setValue<T>() addressed by an ObjectAddress. Lock-free, non-allocating.
Definition device.h:725
std::optional< DeviceParameter > parameter(uint16_t index, uint8_t subindex) const
Looks up one parameter by (index, subindex) and returns a copy of it. O(1).
Definition device.cc:1045
uint32_t revisionNumber() const
Revision number from EEPROM.
Definition device.cc:67
const FlatPdoMapping & flatPdoMapping() const
Returns the device's PDO mapping. Empty until readFlatPdoMapping() succeeds.
Definition device.cc:836
bool exchangesProcessData() const
Whether the device is in a process-data-exchanging state (SAFE-OP or OP, error bit clear).
Definition device.cc:96
std::expected< PdoMapping, std::string > readPdoMapping()
Reads the device's PDO mapping grouped by mapping object (0x16xx / 0x1Axx).
Definition device.cc:690
std::vector< DeviceParameter > parametersOrdered() const
Returns all parameters sorted ascending by (index, subindex).
Definition device.cc:1024
bool parametersUnavailable() const
Whether any initializeParameters call on this device failed since it was scanned (thread-safe; no bus...
Definition device.cc:589
std::expected< void, std::string > readFlatPdoMapping()
Reads the device's PDO mapping from its assignment and mapping objects.
Definition device.cc:790
std::expected< std::vector< uint8_t >, mm::comm::FoeError > readFile(const std::string &filename) const
Reads a file from this device via File over EtherCAT (FoE).
Definition device.cc:114
std::expected< DeviceParameterValue, std::string > setValueFromBytes(uint16_t index, uint8_t subindex, std::span< const uint8_t > bytes)
Sets the parameter's value from its raw on-the-wire bytes (the bytes-domain setter).
Definition device.cc:1005
std::expected< ObjectValues, std::string > readObject(uint16_t index, bool useCompleteAccess=true)
Reads every readable sub-entry of one object and returns the decoded values.
Definition device.cc:1293
std::expected< T, std::string > readValue(ObjectAddress< T > address)
readValue<T>() addressed by an ObjectAddress. Blocking; off the RT thread only.
Definition device.h:576
bool mailboxActive() const
Whether the device's CoE/SDO mailbox is currently active (AL state PRE-OP, SAFE-OP,...
Definition device.cc:80
uint32_t productCode() const
Product code from EEPROM.
Definition device.cc:66
std::expected< void, std::string > writeSii(std::span< const uint8_t > data) const
Writes a raw SII (EEPROM) image to this device.
Definition device.cc:138
std::expected< T, std::string > readValue(uint16_t index, uint8_t subindex)
Typed convenience wrapper for readParameter.
Definition device.h:559
std::expected< void, std::string > writeValue(ObjectAddress< T > address, T newValue)
writeValue<T>() addressed by an ObjectAddress. Blocking; off the RT thread only.
Definition device.h:582
bool isCia402() const
Whether this device implements the CiA402 drive profile.
Definition device.cc:70
uint16_t slavePosition() const
Returns the 1-based position of this node on the fieldbus.
Definition device.cc:55
std::string productName() const
Canonical product name, independent of the SII EEPROM contents.
Definition device.cc:57
std::optional< uint16_t > dataType(uint16_t index, uint8_t subindex) const
Returns a parameter's declared ETG.1020 data-type code, thread-safely (cache lock).
Definition device.cc:1054
std::optional< T > value(uint16_t index, uint8_t subindex) const
Reads a scalar parameter's current value as T. Lock-free, non-allocating.
Definition device.h:667
uint32_t vendorId() const
Vendor ID from EEPROM.
Definition device.cc:65
std::optional< T > value(ObjectAddress< T > address) const
value<T>() addressed by an ObjectAddress. Lock-free, non-allocating.
Definition device.h:719
std::expected< void, mm::comm::FoeError > writeFile(const std::string &filename, std::span< const uint8_t > data) const
Writes a file to this device via File over EtherCAT (FoE).
Definition device.cc:119
std::expected< std::vector< uint8_t >, std::string > readSii() const
Reads this device's raw Slave Information Interface (SII / EEPROM) image.
Definition device.cc:134
On-disk cache of CoE parameter definitions, keyed by device identity.
Definition parameter_cache.h:53
uint8_t subindex
Definition esi_entry.cc:283
Definition bus_health_source.h:7
std::variant< int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, uint64_t, float, double, std::string, std::vector< uint8_t > > DeviceParameterValue
Decoded value of a single device parameter (CoE object dictionary entry).
Definition device_parameter.h:33
DeviceParameterValue defaultValueForDataType(uint16_t dataType)
Returns a zero-equivalent value for the given ETG.1020 dataType.
Definition device_parameter.cc:58
void to_json(nlohmann::json &j, const Cia402Status &s)
Serialises a Cia402Status. Emits state/modeName as human-readable strings alongside the numeric statu...
Definition cia402_drive.cc:13
DeclaredSubindexReads
Whether a device serves the subindices an array or a record declares but does not hold.
Definition device.h:66
@ Synced
value matches the device (last successful read or write).
CompleteAccessSupport
Runtime discovery state of CoE Complete Access support. CA is optional in CoE and the EEPROM capabili...
Definition device.h:41
std::expected< int, std::string > reconcileDetectedModules(const Device &device)
Reconciles a device's Configured Module Ident List with its Detected list.
Definition device.cc:1434
A structured FoE failure. String-like where a caller only forwards it (operator<<,...
Definition foe_error.h:109
A single object dictionary entry held by a Device.
Definition device_parameter.h:194
SyncState syncState
Freshness of the value relative to the device.
Definition device_parameter.h:214
void storeBits(uint64_t v) const
Stores the scalar cell. Lock-free, non-allocating, relaxed — safe from the RT loop.
Definition device_parameter.h:229
uint16_t dataType
ETG.1020 data type code (e.g. 0x0007 = UNSIGNED32).
Definition device_parameter.h:199
std::optional< T > scalar() const
Reads the value as T straight out of the cell. Lock-free, non-allocating.
Definition device_parameter.h:252
A device's complete PDO mapping across both directions.
Definition pdo_mapping.h:62
The address of one object-dictionary entry, carrying the type that entry holds.
Definition device_parameter.h:63
uint16_t index
CoE object index.
Definition device_parameter.h:64
uint8_t subindex
CoE object subindex.
Definition device_parameter.h:65
Decoded values of one object's readable sub-entries, as returned by Device::readObject.
Definition device.h:70
std::map< uint8_t, DeviceParameterValue > values
Decoded value per readable subindex.
Definition device.h:72
uint16_t index
CoE object index the values belong to.
Definition device.h:71
std::expected< T, std::string > get(uint8_t subindex) const
Returns the value of subindex as T — type-exact, like DeviceParameter::getValue.
Definition device.h:78
A device's desired PDO configuration to write and assign — the write-side input to Device::writePdoMa...
Definition pdo_mapping.h:89
The live process-data runtime: the published image, the cross-thread exchange buffers,...
Definition process_data.h:36