Motion Master 6.0.0-alpha.111
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 Stale,
111};
112
114std::string_view syncStateName(SyncState state);
115
123enum class ParameterOrigin : uint8_t {
125 Sii,
126};
127
130
156 mutable uint64_t value{0};
157
158 ScalarCell() = default;
159 ~ScalarCell() = default;
160 ScalarCell(const ScalarCell& other) : value(other.load()) {}
161 ScalarCell(ScalarCell&& other) noexcept : value(other.load()) {}
163 if (this != &other) {
164 store(other.load());
165 }
166 return *this;
167 }
168 ScalarCell& operator=(ScalarCell&& other) noexcept {
169 store(other.load());
170 return *this;
171 }
172
174 uint64_t load() const { return std::atomic_ref<uint64_t>(value).load(std::memory_order_relaxed); }
176 void store(uint64_t v) const {
177 std::atomic_ref<uint64_t>(value).store(v, std::memory_order_relaxed);
178 }
179
180 // The two properties the cell's whole contract rests on. Lock-freedom is what makes a read safe
181 // from the RT loop at all; the alignment precondition is atomic_ref's, and a platform where a
182 // uint64_t member does not satisfy it must fail here rather than degrade silently.
183 static_assert(std::atomic_ref<uint64_t>::is_always_lock_free,
184 "the parameter cell must be lock-free so the RT path never blocks");
185 static_assert(alignof(uint64_t) >= std::atomic_ref<uint64_t>::required_alignment,
186 "the parameter cell must satisfy std::atomic_ref's alignment requirement");
187};
188
196 uint16_t index{};
197 uint8_t subindex{};
198 std::string name;
199 uint16_t objectCode{};
200 uint16_t dataType{};
201 uint16_t bitLength{};
202 uint16_t access{};
204
205 // --- value -------------------------------------------------------------------------------
206 // The last-known value, stored as its raw little-endian wire bytes — the same encoding an SDO
207 // transfer carries. @c isScalarDataType(dataType) picks which of the two fields holds it; zero
208 // (an empty @c rawValue) is the type-appropriate default before the first read.
209 //
210 // A cyclic task reads the scalar through @c loadBits: one relaxed atomic load, no lock, no
211 // allocation, and no way to tell whether the RT exchange or a background SDO poll put the value
212 // there. Why that is a @c ScalarCell rather than a bare @c uint64_t is documented on the type.
214 std::vector<uint8_t> rawValue{};
216 std::optional<uint32_t> unit;
217 std::optional<DeviceParameterValue> defaultValue;
218 std::optional<DeviceParameterValue> minValue;
219 std::optional<DeviceParameterValue> maxValue;
220
222 uint32_t key() const { return makeParameterKey(index, subindex); }
223
227 uint64_t loadBits() const { return cell.load(); }
228
230 void storeBits(uint64_t v) const { cell.store(v); }
231
242
252 template <typename T>
253 std::optional<T> scalar() const {
254 static_assert(std::is_arithmetic_v<T>, "the cell holds arithmetic types only");
255 static_assert(sizeof(T) <= sizeof(uint64_t), "the cell holds at most eight bytes");
256 // Constructing the type's zero is how the alternative is named; for every scalar it is a
257 // register-sized value, and for the string/bytes cases an empty container — no allocation
258 // either way, so this stays RT-safe.
259 if (!std::holds_alternative<T>(defaultValueForDataType(dataType))) {
260 return std::nullopt;
261 }
262 const uint64_t v = loadBits();
263 T out{};
264 std::memcpy(&out, &v, sizeof(T));
265 return out;
266 }
267
276 std::vector<uint8_t> rawValueBytes() const;
277
283 void setRawValue(std::span<const uint8_t> bytes);
284
295 }
296 }
297
305 template <typename T>
306 std::expected<T, std::string> getValue() const {
308 if (const auto* p = std::get_if<T>(&v)) {
309 return *p;
310 }
311 return std::unexpected(
312 std::format("parameter 0x{:04X}:{:02X} holds a different type", index, subindex));
313 }
314
321 std::expected<double, std::string> numeric() const;
322
334 std::expected<std::vector<uint8_t>, std::string> encode(const DeviceParameterValue& v) const;
335
340 std::expected<void, std::string> setValue(const DeviceParameterValue& v);
341
343 template <typename T>
344 std::expected<void, std::string> setValue(const T& v) {
346 }
347
352 bool isReadable() const { return (access & 0x07) != 0; }
353};
354
361std::expected<DeviceParameterValue, std::string> decodeSdoBytes(uint16_t dataType,
362 std::span<const uint8_t> bytes);
363
377std::expected<std::vector<uint8_t>, std::string> encodeSdoBytes(uint16_t dataType,
378 const DeviceParameterValue& value);
379
384void to_json(nlohmann::json& j, const DeviceParameter& p);
385
386} // 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:259
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:197
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:60
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:151
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:136
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:123
@ 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:121
std::optional< double > numericValue(const DeviceParameterValue &value)
Coerces a DeviceParameterValue to a double, when it holds a number.
Definition device_parameter.cc:246
void unpackLeBits(uint64_t bits, std::span< uint8_t > out)
Unpacks bits into out, little-endian. The inverse of packLeBits.
Definition device_parameter.cc:145
std::string_view parameterOriginName(ParameterOrigin origin)
Returns the string form of origin ("objectDictionary" / "sii").
Definition device_parameter.cc:273
bool isScalarDataType(uint16_t dataType)
Whether a value of dataType is stored in DeviceParameter::bits.
Definition device_parameter.cc:96
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.
@ Stale
value came from an earlier read or write; the latest read of the object failed.
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
std::size_t bytes
Definition spoe_connection.cc:24
A single object dictionary entry held by a Device.
Definition device_parameter.h:195
uint16_t access
ObjAccess bitfield (read/write per-state flags).
Definition device_parameter.h:202
SyncState syncState
Freshness of the value relative to the device.
Definition device_parameter.h:215
void markReadFailed()
Records that a read of this object failed. Keeps the value.
Definition device_parameter.h:292
std::optional< DeviceParameterValue > defaultValue
Slave-reported default, when available.
Definition device_parameter.h:217
ScalarCell cell
Scalar value, LSB-aligned little-endian, zero-extended.
Definition device_parameter.h:213
std::expected< void, std::string > setValue(const DeviceParameterValue &v)
Sets the value to v, encoded as encode does. Does not touch syncState; the caller decides that.
Definition device_parameter.cc:421
uint64_t loadBits() const
Loads the scalar cell. Lock-free, non-allocating — safe from the RT loop.
Definition device_parameter.h:227
std::vector< uint8_t > rawValueBytes() const
Returns the value's raw wire bytes at the object's declared width.
Definition device_parameter.cc:300
uint16_t objectCode
OTYPE_VAR / OTYPE_ARRAY / OTYPE_RECORD (ETG.1000.6 §5).
Definition device_parameter.h:199
std::optional< uint32_t > unit
ETG.1004 unit code, when reported.
Definition device_parameter.h:216
std::string name
Textual description from ecx_readOE.
Definition device_parameter.h:198
void setRawValue(std::span< const uint8_t > bytes)
Replaces the stored value with bytes, its raw little-endian wire encoding.
Definition device_parameter.cc:310
uint16_t bitLength
Bit length of the value.
Definition device_parameter.h:201
ParameterOrigin origin
Where this definition came from.
Definition device_parameter.h:203
uint16_t index
CoE object index.
Definition device_parameter.h:196
std::optional< DeviceParameterValue > maxValue
Slave-reported maximum, when available.
Definition device_parameter.h:219
std::expected< void, std::string > setValue(const T &v)
Strongly-typed setValue overload; wraps v and converts as above.
Definition device_parameter.h:344
DeviceParameterValue currentValue() const
Returns the current value as a DeviceParameterValue, built on the spot.
Definition device_parameter.cc:283
void storeBits(uint64_t v) const
Stores the scalar cell. Lock-free, non-allocating, relaxed — safe from the RT loop.
Definition device_parameter.h:230
uint8_t subindex
CoE object subindex.
Definition device_parameter.h:197
uint16_t dataType
ETG.1020 data type code (e.g. 0x0007 = UNSIGNED32).
Definition device_parameter.h:200
std::expected< T, std::string > getValue() const
Returns the value as the requested type T.
Definition device_parameter.h:306
std::expected< std::vector< uint8_t >, std::string > encode(const DeviceParameterValue &v) const
Encodes v as the wire bytes of the parameter's declared data type. Stores nothing.
Definition device_parameter.cc:370
bool isReadable() const
Whether the object is readable in any state (ETG.1000.6 ObjAccess read bits 0-2).
Definition device_parameter.h:352
std::vector< uint8_t > rawValue
Non-scalar value (string / byte array) as wire bytes.
Definition device_parameter.h:214
std::optional< DeviceParameterValue > minValue
Slave-reported minimum, when available.
Definition device_parameter.h:218
uint32_t key() const
Returns the packed (index, subindex) key used in the parameter map.
Definition device_parameter.h:222
std::optional< T > scalar() const
Reads the value as T straight out of the cell. Lock-free, non-allocating.
Definition device_parameter.h:253
std::expected< double, std::string > numeric() const
Returns the value coerced to a double, for any numeric parameter.
Definition device_parameter.cc:318
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:155
uint64_t value
Definition device_parameter.h:156
uint64_t load() const
Reads the value. Lock-free, non-allocating — safe from the RT loop.
Definition device_parameter.h:174
ScalarCell & operator=(ScalarCell &&other) noexcept
Definition device_parameter.h:168
ScalarCell(const ScalarCell &other)
Definition device_parameter.h:160
ScalarCell(ScalarCell &&other) noexcept
Definition device_parameter.h:161
ScalarCell & operator=(const ScalarCell &other)
Definition device_parameter.h:162
void store(uint64_t v) const
Writes the value. Lock-free, non-allocating — safe from the RT loop.
Definition device_parameter.h:176