Device

Device Definition

A pyrogue.Device is the primary composition unit in a PyRogue tree. Devices can contain:

  • child devices

  • variables (local, remote, link)

  • commands (local, remote)

Most user-facing hardware abstractions are implemented as Device subclasses. At runtime, a device also participates in memory routing behavior through the Rogue memory hub stack.

import pyrogue as pr

class MyDevice(pr.Device):
    def __init__(self, **kwargs):
        super().__init__(description='Example device', **kwargs)
        self.add(pr.LocalVariable(name='Mode', mode='RW', value=0))
        self.add(pr.LocalCommand(name='Reset', function=self._reset))

    def _reset(self):
        pass

Key Attributes

In addition to inherited Node attributes, common device-level attributes include:

  • offset / address

  • memBase

  • enable

The enable variable allows tree-level logic to disable a full device subtree for hardware access while keeping node metadata available.

Relationship to Hub

Conceptually, a device behaves as a hub in the memory routing stack:

  • variable/block transactions are addressed relative to the device base

  • child devices can inherit base/memory routing from parent devices

  • a child device can also be attached to an independent memory path when needed

Key Methods

Commonly used methods:

Managed Interfaces: addInterface() and addProtocol()

Devices can own stream/memory/protocol helper objects that need coordinated startup/shutdown with the device tree.

Use pyrogue.Device.addInterface() (or alias pyrogue.Device.addProtocol()) to register:

  • Rogue memory or stream interface objects

  • protocol servers/clients

  • any custom object that implements _start() and/or _stop()

At runtime:

  • pyrogue.Device._start() calls _start() on each managed object if the method exists

  • pyrogue.Device._stop() calls _stop() on each managed object if the method exists

  • both then recurse to child devices

This is why top-level interfaces are commonly added at root scope using root.addInterface(...) (Root is a Device subclass).

Lifecycle Override Points for Subclasses

The following methods are intended override points when you need custom behavior around startup/shutdown or transaction sequencing.

Start/Stop hooks

  • pyrogue.Device._start(): Called recursively from pyrogue.Root.start(). Typical use: open sockets/threads/resources, then call super()._start().

  • pyrogue.Device._stop(): Called recursively from pyrogue.Root.stop(). Typical use: stop custom resources and call super()._stop() to preserve managed interface and child-device shutdown.

  • pyrogue.Device._rootAttached(): Called during root startup before _finishInit and before runtime threads. Typical use: finalize path/root-dependent setup after calling super()._rootAttached(parent, root).

Operational hooks

These are commonly overridden to implement device-specific control behavior.

Read/write sequencing hooks

Override these when the default transaction ordering needs pre/post side effects or custom sequencing.

Device Read/Write Operations

Bulk config/read/write operations traverse the tree and issue block transactions through device block APIs.

Typical write flow:

  • update variable shadow value

  • enqueue write transaction

  • optionally enqueue verify transaction

  • check completion and publish updates

Typical read flow:

  • enqueue read transaction

  • check completion

  • return/publish updated values

Implementation Boundary (Python and C++)

From a Python API perspective, you use pyrogue.Device methods such as readBlocks and writeBlocks.

Under the hood, pyrogue.Device is built on the Rogue memory interface hub/master/slave stack. In particular, a device participates in memory routing as a Hub, and forwards block transactions toward downstream memory slaves.

Where Hub fits in transaction flow

The C++ Hub implementation (src/rogue/interfaces/memory/Hub.cpp):

  • applies local address offset when forwarding transactions downstream

  • can split large transactions into sub-transactions based on downstream max-access limits

  • allows custom transaction translation by overriding _doTransaction in Python/C++ subclasses

Conceptual transaction path:

  • variable operation -> block transaction -> device/hub routing -> downstream slave access -> completion/check -> variable update notify

For deeper memory-stack behavior, see:

Custom Read/Write Operations

If a device needs sequencing around default block operations, override:

class SequencedDevice(pyrogue.Device):
    def writeBlocks(self, *, force=False, recurse=True, variable=None, checkEach=False, index=-1):
        # Pre-transaction behavior
        super().writeBlocks(
            force=force,
            recurse=recurse,
            variable=variable,
            checkEach=checkEach,
            index=index,
        )
        # Post-transaction behavior

Device Command Decorators

Device supports decorators that create pyrogue.LocalCommand nodes. You can use decorators on local functions created in __init__.

@pyrogue.command(name='ReadConfig', value='', description='Load config file')
def _readConfig(self, arg):
    self.root.loadYaml(name=arg, writeEach=False, modes=['RW', 'WO'])

Special Device Subclasses

There are several specialized device classes available:

Device Class Documentation

See Device for generated API details.