Motion Master 6.0.0-alpha.86
Next-generation motion control software
Loading...
Searching...
No Matches
device_parameter.h
Go to the documentation of this file.
1#pragma once
2
3#include <atomic>
4#include <bit>
5#include <cstdint>
6#include <cstring>
7#include <expected>
8#include <format>
9#include <nlohmann/json_fwd.hpp>
10#include <optional>
11#include <span>
12#include <string>
13#include <string_view>
14#include <type_traits>
15#include <variant>
16#include <vector>
17
18namespace mm::node {
19
20// Values are stored and exchanged as little-endian wire bytes and read back by memcpy, here
21// and in decodeSdoBytes. A big-endian host would need every one of those to byte-swap, so it
22// must fail here rather than silently return reversed numbers.
23static_assert(std::endian::native == std::endian::little,
24 "parameter values are stored as little-endian wire bytes");
25
30using DeviceParameterValue = std::variant<int8_t, int16_t, int32_t, int64_t, //
31 uint8_t, uint16_t, uint32_t, uint64_t, //
32 float, double, //
33 std::string, std::vector<uint8_t>>;
34
38constexpr uint32_t makeParameterKey(uint16_t index, uint8_t subindex) {
39 return (static_cast<uint32_t>(index) << 8) | static_cast<uint32_t>(subindex);
40}
41
62template <typename T>
64 uint16_t index{};
65 uint8_t subindex{};
66};
67
73
81bool isScalarDataType(uint16_t dataType);
82
87size_t scalarByteWidth(uint16_t dataType);
88
90uint64_t packLeBits(std::span<const uint8_t> bytes);
91
93void unpackLeBits(uint64_t bits, std::span<uint8_t> out);
94
99std::optional<double> numericValue(const DeviceParameterValue& value);
100
106enum class SyncState : uint8_t {
107 Unknown,
108 Synced,
109 Pending,
110};
111
113std::string_view syncStateName(SyncState state);
114
122enum class ParameterOrigin : uint8_t {
124 Sii,
125};
126
129
155 mutable uint64_t value{0};
156
157 ScalarCell() = default;
158 ~ScalarCell() = default;
159 ScalarCell(const ScalarCell& other) : value(other.load()) {}
160 ScalarCell(ScalarCell&& other) noexcept : value(other.load()) {}
162 if (this != &other) {
163 store(other.load());
164 }
165 return *this;
166 }
167 ScalarCell& operator=(ScalarCell&& other) noexcept {
168 store(other.load());
169 return *this;
170 }
171
173 uint64_t load() const { return std::atomic_ref<uint64_t>(value).load(std::memory_order_relaxed); }
175 void store(uint64_t v) const {
176 std::atomic_ref<uint64_t>(value).store(v, std::memory_order_relaxed);
177 }
178
179 // The two properties the cell's whole contract rests on. Lock-freedom is what makes a read safe
180 // from the RT loop at all; the alignment precondition is atomic_ref's, and a platform where a
181 // uint64_t member does not satisfy it must fail here rather than degrade silently.
182 static_assert(std::atomic_ref<uint64_t>::is_always_lock_free,
183 "the parameter cell must be lock-free so the RT path never blocks");
184 static_assert(alignof(uint64_t) >= std::atomic_ref<uint64_t>::required_alignment,
185 "the parameter cell must satisfy std::atomic_ref's alignment requirement");
186};
187
195 uint16_t index{};
196 uint8_t subindex{};
197 std::string name;
198 uint16_t objectCode{};
199 uint16_t dataType{};
200 uint16_t bitLength{};
201 uint16_t access{};
203
204 // --- value -------------------------------------------------------------------------------
205 // The last-known value, stored as its raw little-endian wire bytes — the same encoding an SDO
206 // transfer carries. @c isScalarDataType(dataType) picks which of the two fields holds it; zero
207 // (an empty @c rawValue) is the type-appropriate default before the first read.
208 //
209 // A cyclic task reads the scalar through @c loadBits: one relaxed atomic load, no lock, no
210 // allocation, and no way to tell whether the RT exchange or a background SDO poll put the value
211 // there. Why that is a @c ScalarCell rather than a bare @c uint64_t is documented on the type.
213 std::vector<uint8_t> rawValue{};
215 std::optional<uint32_t> unit;
216 std::optional<DeviceParameterValue> defaultValue;
217 std::optional<DeviceParameterValue> minValue;
218 std::optional<DeviceParameterValue> maxValue;
219
221 uint32_t key() const { return makeParameterKey(index, subindex); }
222
226 uint64_t loadBits() const { return cell.load(); }
227
229 void storeBits(uint64_t v) const { cell.store(v); }
230
241
251 template <typename T>
252 std::optional<T> scalar() const {
253 static_assert(std::is_arithmetic_v<T>, "the cell holds arithmetic types only");
254 static_assert(sizeof(T) <= sizeof(uint64_t), "the cell holds at most eight bytes");
255 // Constructing the type's zero is how the alternative is named; for every scalar it is a
256 // register-sized value, and for the string/bytes cases an empty container — no allocation
257 // either way, so this stays RT-safe.
258 if (!std::holds_alternative<T>(defaultValueForDataType(dataType))) {
259 return std::nullopt;
260 }
261 const uint64_t v = loadBits();
262 T out{};
263 std::memcpy(&out, &v, sizeof(T));
264 return out;
265 }
266
275 std::vector<uint8_t> rawValueBytes() const;
276
282 void setRawValue(std::span<const uint8_t> bytes);
283
291 template <typename T>
292 std::expected<T, std::string> getValue() const {
294 if (const auto* p = std::get_if<T>(&v)) {
295 return *p;
296 }
297 return std::unexpected(
298 std::format("parameter 0x{:04X}:{:02X} holds a different type", index, subindex));
299 }
300
307 std::expected<double, std::string> numeric() const;
308
318 std::expected<void, std::string> setValue(const DeviceParameterValue& v);
319
321 template <typename T>
322 std::expected<void, std::string> setValue(const T& v) {
324 }
325
330 bool isReadable() const { return (access & 0x07) != 0; }
331
336 bool inRange(const DeviceParameterValue& v) const;
337
343};
344
351std::expected<DeviceParameterValue, std::string> decodeSdoBytes(uint16_t dataType,
352 std::span<const uint8_t> bytes);
353
367std::expected<std::vector<uint8_t>, std::string> encodeSdoBytes(uint16_t dataType,
368 const DeviceParameterValue& value);
369
374void to_json(nlohmann::json& j, const DeviceParameter& p);
375
376} // namespace mm::node
uint8_t subindex
Definition esi_entry.cc:283
EsiEntrySource origin
Definition esi_entry.cc:62
Definition bus_health_source.h:7
std::string_view syncStateName(SyncState state)
Returns the lowercase string form of state ("unknown" etc.).
Definition device_parameter.cc:257
std::expected< std::vector< uint8_t >, std::string > encodeSdoBytes(uint16_t dataType, const DeviceParameterValue &value)
Serialises a DeviceParameterValue to raw SDO bytes — the inverse of decodeSdoBytes.
Definition device_parameter.cc:195
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
std::expected< DeviceParameterValue, std::string > decodeSdoBytes(uint16_t dataType, std::span< const uint8_t > bytes)
Decodes a raw SDO byte sequence according to an ETG.1020 data type code.
Definition device_parameter.cc:149
uint64_t packLeBits(std::span< const uint8_t > bytes)
Packs up to eight raw little-endian wire bytes into a uint64_t (zero-extended).
Definition device_parameter.cc:134
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
ParameterOrigin
Where a parameter's definition (schema/metadata) came from.
Definition device_parameter.h:122
@ Sii
Derived from the SII EEPROM PDO categories (no CoE mailbox).
@ ObjectDictionary
Enumerated over the CoE object dictionary (SDO-Info).
size_t scalarByteWidth(uint16_t dataType)
Width in bytes of a value of dataType, or 0 when it is not a scalar.
Definition device_parameter.cc:119
std::optional< double > numericValue(const DeviceParameterValue &value)
Coerces a DeviceParameterValue to a double, when it holds a number.
Definition device_parameter.cc:244
void unpackLeBits(uint64_t bits, std::span< uint8_t > out)
Unpacks bits into out, little-endian. The inverse of packLeBits.
Definition device_parameter.cc:143
std::string_view parameterOriginName(ParameterOrigin origin)
Returns the string form of origin ("objectDictionary" / "sii").
Definition device_parameter.cc:269
bool isScalarDataType(uint16_t dataType)
Whether a value of dataType is stored in DeviceParameter::bits.
Definition device_parameter.cc:94
SyncState
Tracks how a cached parameter value relates to the device.
Definition device_parameter.h:106
@ Pending
value was set locally while offline / after a failed write.
@ Synced
value matches the device (last successful read or write).
@ Unknown
Never read; value is the type-appropriate default.
constexpr uint32_t makeParameterKey(uint16_t index, uint8_t subindex)
Packs an object dictionary index and subindex into a single 32-bit key.
Definition device_parameter.h:38
A single object dictionary entry held by a Device.
Definition device_parameter.h:194
uint16_t access
ObjAccess bitfield (read/write per-state flags).
Definition device_parameter.h:201
bool inRange(const DeviceParameterValue &v) const
Whether v lies within [minValue, maxValue].
Definition device_parameter.cc:371
SyncState syncState
Freshness of the value relative to the device.
Definition device_parameter.h:214
std::optional< DeviceParameterValue > defaultValue
Slave-reported default, when available.
Definition device_parameter.h:216
ScalarCell cell
Scalar value, LSB-aligned little-endian, zero-extended.
Definition device_parameter.h:212
std::expected< void, std::string > setValue(const DeviceParameterValue &v)
Sets value, coercing v into the parameter's declared data type.
Definition device_parameter.cc:322
uint64_t loadBits() const
Loads the scalar cell. Lock-free, non-allocating — safe from the RT loop.
Definition device_parameter.h:226
std::vector< uint8_t > rawValueBytes() const
Returns the value's raw wire bytes at the object's declared width.
Definition device_parameter.cc:296
uint16_t objectCode
OTYPE_VAR / OTYPE_ARRAY / OTYPE_RECORD (ETG.1000.6 §5).
Definition device_parameter.h:198
std::optional< uint32_t > unit
ETG.1004 unit code, when reported.
Definition device_parameter.h:215
std::string name
Textual description from ecx_readOE.
Definition device_parameter.h:197
void setRawValue(std::span< const uint8_t > bytes)
Replaces the stored value with bytes, its raw little-endian wire encoding.
Definition device_parameter.cc:306
uint16_t bitLength
Bit length of the value.
Definition device_parameter.h:200
ParameterOrigin origin
Where this definition came from.
Definition device_parameter.h:202
uint16_t index
CoE object index.
Definition device_parameter.h:195
std::optional< DeviceParameterValue > maxValue
Slave-reported maximum, when available.
Definition device_parameter.h:218
std::expected< void, std::string > setValue(const T &v)
Strongly-typed setValue overload; wraps v and coerces as above.
Definition device_parameter.h:322
DeviceParameterValue currentValue() const
Returns the current value as a DeviceParameterValue, built on the spot.
Definition device_parameter.cc:279
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
DeviceParameterValue clampToRange(const DeviceParameterValue &v) const
Returns v clamped to [minValue, maxValue].
Definition device_parameter.cc:389
uint8_t subindex
CoE object subindex.
Definition device_parameter.h:196
uint16_t dataType
ETG.1020 data type code (e.g. 0x0007 = UNSIGNED32).
Definition device_parameter.h:199
std::expected< T, std::string > getValue() const
Returns the value as the requested type T.
Definition device_parameter.h:292
bool isReadable() const
Whether the object is readable in any state (ETG.1000.6 ObjAccess read bits 0-2).
Definition device_parameter.h:330
std::vector< uint8_t > rawValue
Non-scalar value (string / byte array) as wire bytes.
Definition device_parameter.h:213
std::optional< DeviceParameterValue > minValue
Slave-reported minimum, when available.
Definition device_parameter.h:217
uint32_t key() const
Returns the packed (index, subindex) key used in the parameter map.
Definition device_parameter.h:221
std::optional< T > scalar() const
Reads the value as T straight out of the cell. Lock-free, non-allocating.
Definition device_parameter.h:252
std::expected< double, std::string > numeric() const
Returns the value coerced to a double, for any numeric parameter.
Definition device_parameter.cc:314
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
One scalar value, and the only part of a DeviceParameter that two threads touch at once.
Definition device_parameter.h:154
uint64_t value
Definition device_parameter.h:155
uint64_t load() const
Reads the value. Lock-free, non-allocating — safe from the RT loop.
Definition device_parameter.h:173
ScalarCell & operator=(ScalarCell &&other) noexcept
Definition device_parameter.h:167
ScalarCell(const ScalarCell &other)
Definition device_parameter.h:159
ScalarCell(ScalarCell &&other) noexcept
Definition device_parameter.h:160
ScalarCell & operator=(const ScalarCell &other)
Definition device_parameter.h:161
void store(uint64_t v) const
Writes the value. Lock-free, non-allocating — safe from the RT loop.
Definition device_parameter.h:175