Motion Master 6.0.0-alpha.86
Next-generation motion control software
Loading...
Searching...
No Matches
mm::core Namespace Reference

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 >
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.
 

Function Documentation

◆ acquireSingleInstanceLock()

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.

  • Linux/macOS: flock(LOCK_EX|LOCK_NB) on <temp_dir>/motion-master.lock (machine-wide).
  • Windows: a named mutex in the session-local namespace (per interactive session — a standard user cannot create objects in the machine-global namespace).
    Returns
    The held lock on success; an error string describing the conflict on failure.

◆ base64Decode()

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.

Returns
The decoded bytes, or a message naming what was wrong with text.

◆ base64Encode()

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.

Returns
The encoded text; empty for empty input.

◆ collectSystemInfo()

SystemInfo mm::core::collectSystemInfo ( )

◆ errnoMessage()

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.

Parameters
errAn errno value. Capture it into a local before any intervening call, since almost anything can overwrite errno.
Returns
The platform's description of err.

◆ exeDir()

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

  • Linux: resolves /proc/self/exe via std::filesystem::canonical.
  • macOS: queries the path via _NSGetExecutablePath (no /proc).
  • Windows: queries the path via GetModuleFileNameW.
    Returns
    Absolute path to the executable's parent directory.

◆ fromBytes()

template<typename T >
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).

Template Parameters
TIntegral result type whose size fixes how many bytes are consumed.
Parameters
bytesSource buffer (a std::vector<uint8_t> binds implicitly).
orderByte order to interpret bytes in (default little-endian).
Returns
The decoded value.

◆ fromHex()

std::optional< std::vector< uint8_t > > mm::core::fromHex ( std::string_view  hex)
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.

Parameters
hexSource string, an even number of hex digits.
Returns
The bytes, or std::nullopt for an odd digit count or a character that is not a hex digit.

◆ isUrlSafeId()

bool mm::core::isUrlSafeId ( std::string_view  s)
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.

Parameters
sCandidate identifier.
Returns
true if every character is allowed and the length is in [1, 64].

◆ openInBrowser()

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.

Parameters
urlURL to open.

◆ parseHexOrDec()

template<typename T >
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.

Template Parameters
TUnsigned integer type to parse into (e.g. uint16_t, uint8_t).
Parameters
sInput string view.
Returns
The parsed value on success, or std::nullopt if s is not a valid decimal or hexadecimal representation of a T value.

◆ setRealtimePriority()

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.

Parameters
cpuAffinityCore 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.

◆ to_json() [1/2]

void mm::core::to_json ( nlohmann::json &  j,
const SystemInfo info 
)

◆ to_json() [2/2]

void mm::core::to_json ( nlohmann::json &  j,
const UserCacheFile f 
)

Serialises a listing entry as it appears in GET /api/user-cache.

◆ toBytes()

template<typename T >
std::array< uint8_t, sizeof(T)> mm::core::toBytes ( 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.

Template Parameters
TIntegral type whose size fixes the byte count.
Parameters
valueValue to encode.
orderByte order of the output (default little-endian).
Returns
A std::array of sizeof(T) bytes in order.

◆ toHex()

std::string mm::core::toHex ( std::span< const uint8_t >  bytes,
std::string_view  separator = {} 
)
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).

Parameters
bytesSource buffer (a std::vector<uint8_t> binds implicitly).
separatorPlaced between bytes; empty (the default) for a continuous string.
Returns
"0A1B2C", or "0A 1B 2C" given a " " separator. Empty for empty input.

◆ userCacheDir()

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

  • Windows: LOCALAPPDATA%\motion-master.
  • macOS: $HOME/Library/Caches/motion-master.
  • Linux: $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.

Returns
Absolute path to the cache directory (never empty).

Variable Documentation

◆ kRtThreadPriority

constexpr int mm::core::kRtThreadPriority = 80
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.