OS Memory Bridge
The OS memory bridge pattern maps memory transactions to host-side Python
functions so standard RemoteVariable access can target non-hardware data
sources.
The bridge is typically built from two parts used together:
OsCommandMemorySlave: a memorySlavethat maps addresses to Python command callbacks.OsMemMaster: an exampleDevicethat exposes typedRemoteVariableNodes at those same offsets.
This pairing is useful for prototyping register maps, exposing host metrics inside a tree, and testing memory-control paths before firmware is available.
OsCommandMemorySlave
OsCommandMemorySlave maps read/write
transactions to Python callables keyed by address.
How mapping works:
Write and
Posttransactions are decoded with the registeredbaseModel and passed to the callback asarg.Read transactions call the same callback with
arg=Noneand encode the return value with that same Model before returning it to the transaction.
Commands are registered with @self.command(addr=..., base=...).
Method And Mapping Overview
@self.command(addr=..., base=...)registers one address callback.addrdefines the transaction-mapped register offset.basedefines encode/decode model for read/write payload conversion.Callback
argbehavior: -argisNoneon read transactions. -argcontains decoded write value on write andPosttransactions.
import os
import pathlib
import time
import pyrogue as pr
import pyrogue.interfaces as pri
class MyCmdSlave(pri.OsCommandMemorySlave):
def __init__(self):
super().__init__(minWidth=4, maxSize=256)
@self.command(addr=0x00, base=pr.Float(32))
def Uptime(self, arg):
# Read-only command: ignore writes.
if arg is None:
return float(time.monotonic())
return float(time.monotonic())
@self.command(addr=0x04, base=pr.Float(32))
def SysLoad1Min(self, arg):
if arg is None:
return float(os.getloadavg()[0])
return float(os.getloadavg()[0])
@self.command(addr=0x08, base=pr.Float(32))
def SysLoad5Min(self, arg):
if arg is None:
return float(os.getloadavg()[1])
return float(os.getloadavg()[1])
@self.command(addr=0x0C, base=pr.Float(32))
def SysLoad15Min(self, arg):
if arg is None:
return float(os.getloadavg()[2])
return float(os.getloadavg()[2])
@self.command(addr=0x10, base=pr.UInt(32))
def FileTest(self, arg):
# RW path matching OsMemMaster.FileTest.
if arg is not None:
pathlib.Path('/tmp/osCmdTest.txt').write_text(f'{int(arg)}\n')
return int(arg)
file_path = pathlib.Path('/tmp/osCmdTest.txt')
if file_path.exists():
return int(file_path.read_text().strip())
return 0
@self.command(addr=0x14, base=pr.Float(32))
def CpuTempC(self, arg):
# Optional Linux thermal-zone example (millidegrees C -> C).
if arg is None:
thermal = pathlib.Path('/sys/class/thermal/thermal_zone0/temp')
if thermal.exists():
return float(thermal.read_text().strip()) / 1000.0
return -1.0
return -1.0
OsMemMaster Device Pattern
OsMemMaster is an example Device that defines typed
RemoteVariable Nodes at offsets serviced by an OsCommandMemorySlave.
In the example flow:
OsMemMasterdefines offsets/types forUptime,SysLoad*, andFileTest.MyCmdSlave(subclass ofOsCommandMemorySlave) implements matching callbacks at those offsets. It can also expose extra commands such asCpuTempCat another address.Variable reads/writes are executed as memory transactions through
memBase=os_slave.
import pyrogue as pr
class OsMemMaster(pr.Device):
def __init__(self, name='OsMemMaster', description='OS Memory Master Device', **kwargs):
super().__init__(name=name, description=description, **kwargs)
self.add(pr.RemoteVariable(
name='Uptime',
description='System Uptime In Seconds',
offset=0x00,
bitSize=32,
base=pr.Float,
mode='RO',
pollInterval=1,
))
self.add(pr.RemoteVariable(
name='SysLoad1Min',
description='1 Minute System Load',
offset=0x04,
bitSize=32,
base=pr.Float,
mode='RO',
pollInterval=1,
))
self.add(pr.RemoteVariable(
name='SysLoad5Min',
description='5 Minute System Load',
offset=0x08,
bitSize=32,
base=pr.Float,
mode='RO',
pollInterval=1,
))
self.add(pr.RemoteVariable(
name='SysLoad15Min',
description='15 Minute System Load',
offset=0x0C,
bitSize=32,
base=pr.Float,
mode='RO',
pollInterval=1,
))
self.add(pr.RemoteVariable(
name='FileTest',
description='File Read Write Test',
offset=0x10,
bitSize=32,
base=pr.UInt,
mode='RW',
))
self.add(pr.RemoteVariable(
name='CpuTempC',
description='CPU Temperature In Degrees C (if available)',
offset=0x14,
bitSize=32,
base=pr.Float,
mode='RO',
pollInterval=1,
))
class ExampleRoot(pr.Root):
def __init__(self, **kwargs):
super().__init__(**kwargs)
os_slave = MyCmdSlave()
os_slave.setName('OsSlave')
self.addInterface(os_slave)
self.add(OsMemMaster(memBase=os_slave))
Operational Notes
Poll intervals on read-only variables drive periodic reads through
memBase.Address offsets and
baseModel types must match across the master and the commandSlave.This pattern is ideal for integration and prototyping work, not for high-rate paths.
API Reference
Python: OsCommandMemorySlave Device RemoteVariable