|
Motion Master 6.0.0-alpha.86
Next-generation motion control software
|
Classes | |
| struct | CycleContext |
| Per-cycle timing context passed to every CyclicTask::execute(). More... | |
| class | CyclicTask |
| Interface for work executed once per game loop cycle. More... | |
| class | CyclicTimer |
| Precision cyclic timer for fixed-period loops. More... | |
| struct | RtSetupResult |
| Outcome of setRealtimePriority() — which of the three best-effort steps took. More... | |
| class | SingleInstanceLock |
| RAII holder of the process-wide single-instance lock. More... | |
| struct | SystemInfo |
| class | UserCache |
| A flat, user-writable file store rooted at Motion Master's per-user cache directory. More... | |
| struct | UserCacheFile |
One file in the user cache, as reported by UserCache::list. More... | |
Functions | |
| std::string | base64Encode (std::span< const uint8_t > bytes) |
Encodes bytes as base64 with padding, on one line. | |
| std::expected< std::vector< uint8_t >, std::string > | base64Decode (std::string_view text) |
Decodes base64 text. | |
| std::filesystem::path | exeDir () |
| Return the directory that contains the running executable. | |
| std::filesystem::path | userCacheDir () |
| Return Motion Master's per-user cache directory. | |
| std::string | errnoMessage (int err) |
Describe an errno value, safely from any thread. | |
| void | openInBrowser (const std::string &url) |
| Open the given URL in the system default browser. | |
| std::expected< SingleInstanceLock, std::string > | acquireSingleInstanceLock () |
| Acquire the process-wide lock that permits only one running Motion Master. | |
| RtSetupResult | setRealtimePriority (int cpuAffinity=-1) |
| Prepares the calling thread for real-time execution. | |
| SystemInfo | collectSystemInfo () |
| void | to_json (nlohmann::json &j, const SystemInfo &info) |
| void | to_json (nlohmann::json &j, const UserCacheFile &f) |
Serialises a listing entry as it appears in GET /api/user-cache. | |
| bool | isUrlSafeId (std::string_view s) |
Whether s is a valid URL-safe identifier usable as a path segment and pub/sub topic. | |
| template<typename T > | |
| std::optional< T > | parseHexOrDec (std::string_view s) |
| Parses an unsigned integer from a string in decimal or hexadecimal notation. | |
| std::string | toHex (std::span< const uint8_t > bytes, std::string_view separator={}) |
| Formats bytes as uppercase hexadecimal. | |
| std::optional< std::vector< uint8_t > > | fromHex (std::string_view hex) |
Decodes a continuous hexadecimal string into bytes — the inverse of toHex. | |
| template<typename T > | |
| std::array< uint8_t, sizeof(T)> | toBytes (T value, std::endian order=std::endian::little) |
| Encodes an integer into a fixed-width byte array in the given byte order. | |
| template<typename T > | |
| T | fromBytes (std::span< const uint8_t > bytes, std::endian order=std::endian::little) |
| Decodes an integer from a raw byte buffer interpreted in the given byte order. | |
Variables | |
| constexpr int | kRtThreadPriority = 80 |
| Priority requested for the real-time cyclic thread. | |
| std::expected< SingleInstanceLock, std::string > mm::core::acquireSingleInstanceLock | ( | ) |
Acquire the process-wide lock that permits only one running Motion Master.
Non-blocking. Returns the held lock when no other instance holds it; fails when one already does. The lock guards the whole process — acquired before the EtherCAT NIC is claimed and before the servers bind — so a second master can never drive the same bus alongside the first. The exclusive port bind is only a backstop: it fires too late (the NIC is already claimed by then) and misses instances configured on different ports, so the lock, not the port, is the real guard.
flock(LOCK_EX|LOCK_NB) on <temp_dir>/motion-master.lock (machine-wide).| std::expected< std::vector< uint8_t >, std::string > mm::core::base64Decode | ( | std::string_view | text | ) |
Decodes base64 text.
Line breaks are tolerated wherever they appear, because a base64 payload that went through a text editor or a PEM-shaped tool is routinely wrapped. Everything else is rejected: characters outside the alphabet, and a length that is not a whole number of four-character quanta. Strictness is deliberate — a silently mis-decoded firmware image would be written to a drive.
text. | std::string mm::core::base64Encode | ( | std::span< const uint8_t > | bytes | ) |
Encodes bytes as base64 with padding, on one line.
OpenSSL's encoder wraps at 64 characters (it is built for PEM); the newlines are stripped, since this produces one JSON string value rather than a PEM block.
| SystemInfo mm::core::collectSystemInfo | ( | ) |
| std::string mm::core::errnoMessage | ( | int | err | ) |
Describe an errno value, safely from any thread.
The drop-in replacement for std::strerror, which returns a pointer into a single static buffer that a concurrent call may overwrite — a real hazard here, because the failures being described happen on the 32-worker HTTP pool and on the procedure threads, not only during single-threaded startup. std::system_category().message() returns an owned string and is implemented over the reentrant strerror_r / strerror_s, so two threads reporting different errors cannot corrupt each other's message.
| err | An errno value. Capture it into a local before any intervening call, since almost anything can overwrite errno. |
err. | std::filesystem::path mm::core::exeDir | ( | ) |
Return the directory that contains the running executable.
argv[0] is not used — it can be a relative path, a bare command name resolved via PATH, or a symlink.
Platform behaviour
/proc/self/exe via std::filesystem::canonical._NSGetExecutablePath (no /proc).GetModuleFileNameW. | T mm::core::fromBytes | ( | std::span< const uint8_t > | bytes, |
| std::endian | order = std::endian::little |
||
| ) |
Decodes an integer from a raw byte buffer interpreted in the given byte order.
Reads up to sizeof(T) bytes. A short buffer is treated as if zero-padded (the missing most-significant bytes read as zero) — defensive against a slave returning fewer bytes than the type implies; excess bytes are ignored. Accumulation is done in the unsigned representation so the shift is well-defined for signed T. Defaults to little-endian (EtherCAT/CoE wire order).
| T | Integral result type whose size fixes how many bytes are consumed. |
| bytes | Source buffer (a std::vector<uint8_t> binds implicitly). |
| order | Byte order to interpret bytes in (default little-endian). |
|
inline |
Decodes a continuous hexadecimal string into bytes — the inverse of toHex.
This is the xs:hexBinary XML encoding, which both ETG document formats use for a raw byte string. Either case of hex digit is accepted, because a real file is not consistent even within itself. An empty string yields no bytes, which is a valid hexBinary value.
The bytes come back in the order they are written. Neither this nor toHex takes a view on what that order means — an ESI writes a value least-significant byte first, and so does an ENI, but that is the format's convention rather than this function's.
| hex | Source string, an even number of hex digits. |
std::nullopt for an odd digit count or a character that is not a hex digit.
|
inline |
Whether s is a valid URL-safe identifier usable as a path segment and pub/sub topic.
Accepts a non-empty string of at most 64 characters drawn from [A-Za-z0-9._-] — the unreserved subset that needs no percent-encoding in a URL path segment and carries no special meaning to the uWebSockets topic parser. Rejecting '/' is doubly load-bearing: the id is a single REST path segment (e.g. GET /api/monitorings/{topic}), so a '/' would spill into extra segments and miss the route; and uWebSockets treats it as a topic hierarchy delimiter. Comparison is case-sensitive, so "Motor" and "motor" are distinct identifiers.
| s | Candidate identifier. |
true if every character is allowed and the length is in [1, 64]. | void mm::core::openInBrowser | ( | const std::string & | url | ) |
Open the given URL in the system default browser.
Non-blocking — returns immediately after spawning the browser process. Uses xdg-open on Linux, open on macOS, and ShellExecute on Windows.
| url | URL to open. |
| std::optional< T > mm::core::parseHexOrDec | ( | std::string_view | s | ) |
Parses an unsigned integer from a string in decimal or hexadecimal notation.
Accepts a 0x / 0X prefix (C-style) or a #x / #X prefix (as EtherCAT ESI/XML files write hex constants, e.g. "#x00000201") to select hexadecimal; any other input is parsed as decimal. Trailing characters and out-of-range values are rejected.
| T | Unsigned integer type to parse into (e.g. uint16_t, uint8_t). |
| s | Input string view. |
std::nullopt if s is not a valid decimal or hexadecimal representation of a T value. | RtSetupResult mm::core::setRealtimePriority | ( | int | cpuAffinity = -1 | ) |
Prepares the calling thread for real-time execution.
Raises it to SCHED_FIFO at kRtThreadPriority so the cycle is never preempted by ordinary SCHED_OTHER work, then locks all current and future pages (mlockall) so a mid-cycle page fault cannot inject an unbounded latency spike.
Optionally also pins it to a single core (cpu >= 0). This matters wherever the kernel booted with isolcpus: that removes a core from the scheduler's reach, so a thread runs there only if it asks to, and an isolated core otherwise sits idle. Affinity is per thread, which is the whole point — pinning the entire process instead (taskset, systemd's CPUAffinity=) drags every non-real-time thread onto the isolated core to contend with this one, and leaves more than one runnable task there, which is exactly what stops nohz_full from taking effect.
All steps are best effort and independent: no failure is fatal, and a failed SCHED_FIFO skips neither the mlockall nor the pinning, so a process lacking CAP_SYS_NICE / CAP_IPC_LOCK still runs — just non-deterministically. Nothing is logged here (this layer has no logger); the caller reports whichever step the returned result says was missed.
Unlike the other two, the pinning needs no privilege at all: a thread may always set its own affinity, and CAP_SYS_NICE is required only to change another process's. What does defeat it is a mask restricted from outside — a cgroup cpuset, a container, systemd's CPUAffinity=, or an inherited taskset — which makes a core outside that mask fail with EINVAL no matter what capabilities the process holds.
Scheduling policy applies to the calling thread alone, so only the thread that calls this becomes real-time. The policy is requested with SCHED_RESET_ON_FORK where the platform defines it, so any child this thread forks starts at SCHED_OTHER/nice 0 with the flag cleared — the guarantee then holds by construction rather than by no one happening to fork from here. (mlockall(MCL_FUTURE) is process-wide and survives fork regardless; an exec clears it.)
Windows has no equivalent to any of the steps: the call is a no-op returning an all-false result.
| cpuAffinity | Core to pin the calling thread to, or a negative value (the default) to leave affinity untouched. Ignored off Linux — macOS exposes no per-thread affinity API. An out-of-range core simply fails the step and reports cpuPinned == false. |
| void mm::core::to_json | ( | nlohmann::json & | j, |
| const SystemInfo & | info | ||
| ) |
| void mm::core::to_json | ( | nlohmann::json & | j, |
| const UserCacheFile & | f | ||
| ) |
Serialises a listing entry as it appears in GET /api/user-cache.
| std::array< uint8_t, sizeof(T)> mm::core::toBytes | ( | T | value, |
| std::endian | order = std::endian::little |
||
| ) |
Encodes an integer into a fixed-width byte array in the given byte order.
The width is sizeof(T), so toBytes<uint8_t> yields one byte, uint16_t two, and so on. Signed values are encoded through their unsigned representation (two's complement), so the shift is always well-defined. Defaults to little-endian (EtherCAT/CoE wire order); pass std::endian::big for the reverse. The returned array is the natural argument for a byte-span sink such as FieldbusDriver::writeSdo — a temporary result lives to the end of the enclosing call.
| T | Integral type whose size fixes the byte count. |
| value | Value to encode. |
| order | Byte order of the output (default little-endian). |
std::array of sizeof(T) bytes in order.
|
inline |
Formats bytes as uppercase hexadecimal.
For the two shapes this codebase needs: a continuous string (the ESI hexBinary XML encoding) and a separated one (a log line meant to be read against a specification's byte table).
| bytes | Source buffer (a std::vector<uint8_t> binds implicitly). |
| separator | Placed between bytes; empty (the default) for a continuous string. |
"0A1B2C", or "0A 1B 2C" given a " " separator. Empty for empty input. | std::filesystem::path mm::core::userCacheDir | ( | ) |
Return Motion Master's per-user cache directory.
The platform's standard cache location with a motion-master subdirectory, so cached data survives restarts (unlike the temp directory) without needing an install-wide or privileged path.
Platform behaviour
LOCALAPPDATA%\motion-master.$HOME/Library/Caches/motion-master.$XDG_CACHE_HOME/motion-master, or $HOME/.cache/motion-master when unset.Falls back to a motion-master subdirectory of the OS temp directory when the platform's home/cache environment variable is unset (a service account, a stripped container). The directory is not created here — the caller creates it on first write.
|
inlineconstexpr |
Priority requested for the real-time cyclic thread.
Well clear of the 90+ band the kernel's own migration/watchdog threads occupy, and above the ~50 that CONFIG_PREEMPT_RT gives threaded IRQs. That last relation is the one to verify per target before relying on hard synchronisation: on a stock kernel softirqs run in interrupt context and preempt SCHED_FIFO at any priority, so the number cannot starve the NIC, but under PREEMPT_RT this thread does outrank the interface's IRQ thread. Confirm against the target with ps -eo pid,cls,rtprio,comm | grep irq rather than assuming.