Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Rattlesnake Codebase Overview

1Rattlesnake Codebase Overview

This document provides a high-level overview of the Rattlesnake codebase for new developers, advanced users, and maintainers. Its purpose is to explain the major subsystems of the software, how they interact, and where to begin when extending or debugging the framework.

Rattlesnake is a MIMO Vibration Controller that supports both graphical use through the Qt-based user interface, and headless/programmatic use through the Python API.

Because the software is heavily process-oriented and modular, it is useful to understand the architecture before attempting to add new hardware backends, environments, control laws, or headless workflows.

1.1High-Level Architecture

At a high level, Rattlesnake is composed of four major layers:

  1. Controller / Orchestration — the RattlesnakeController, its helper managers, and the logic that coordinates startup, shutdown, acquisition, environments, and profiles.

  2. Hardware Abstraction — a common interface for acquisition and output hardware, with concrete implementations for both physical and virtual systems.

  3. Environment Logic — the test-specific logic for Random, Sine, Transient, SDS, Modal, Time History Generation, and Read Data environments.

  4. User Interface — the Qt-based application, per-environment UI classes, dialogs, plots, and interaction workflows.

These layers communicate primarily through:

A simplified architectural view is shown below.

1.2Main Entry Points

There are several important runtime entry points in the codebase.

For most headless workflows, RattlesnakeController is the primary object that user code will interact with.

1.3The RattlesnakeController

The RattlesnakeController, defined in engine.py, is the central coordination object of the software.

It is responsible for:

1.3.1Controller state machine

Rattlesnake uses the RattlesnakeState enumeration to describe its current operating state.

These states are derived from:

1.3.2Important collaborators

The controller owns or coordinates several major helper objects.

1.3.3Important public methods

Some of the most important controller methods are:

Together, these form the main headless API surface.

1.4Managers

1.4.1EnvironmentManager

EnvironmentManager, defined in environment_manager.py, manages the lifecycle of environment processes.

Its responsibilities include:

Conceptually, EnvironmentManager maps user-facing environment names to internal queue/process identities.

1.4.2ProfileManager

ProfileManager, defined in profile_manager.py, manages timed test-profile execution.

Its responsibilities include:

A profile event is essentially a scheduled command with:

1.5Interprocess Structure

Rattlesnake is deliberately split across multiple processes (or threads, in threaded mode) to separate concerns and keep time-sensitive operations responsive.

1.5.1Always-present controller processes

These processes exist independently of which environments are active.

1.5.2Environment processes

Each initialized environment gets its own process, launched by the EnvironmentManager.

Examples include:

1.5.3Environment-owned subprocesses

Many environments, especially system-ID-based ones, also start helper subprocesses such as:

The exact set depends on the environment.

For example:

1.6Queues and Events

Rattlesnake uses a queue-and-event architecture extensively.

1.6.1Queues

Queues carry:

The main queue container is QueueContainer, defined in utilities.py.

It stores:

1.6.2VerboseMessageQueue

VerboseMessageQueue, also defined in utilities.py, is a queue wrapper that automatically logs queue operations. This makes command routing and process debugging much easier.

1.6.3Events

Events are used for synchronization and state tracking. These are stored in EventContainer, also in utilities.py.

Important examples include:

The UI, controller, and scenario/documentation tooling all rely on these events heavily.

1.7Hardware Layer

The hardware subsystem is built around abstract interfaces plus a registry.

1.7.1Core abstractions

Defined in abstract_hardware.py:

1.7.2Hardware registry

Defined in hardware_registry.py, this maps each HardwareType to:

1.7.3Hardware types

The HardwareType enumeration, defined in hardware_utilities.py, currently includes:

1.7.4Channel model

The Channel class defines the per-channel metadata used throughout the software, including:

Many parts of the system derive meaning from the channel table, so it is one of the most fundamental data structures in the codebase.

1.7.5Virtual hardware

Several virtual hardware implementations make it possible to exercise environments, UI flows, and documentation scenarios without real DAQ hardware.

Examples include:

These are especially useful for:

1.8Environment Layer

Each environment encapsulates a specific testing workflow.

1.8.1Core abstractions

Defined in abstract_environment.py:

1.8.2Metadata vs Instructions

This is an important conceptual separation in the codebase.

1.8.2.1EnvironmentMetadata

Environment metadata describes the configured environment. It usually includes things like:

This data is relatively persistent and is typically associated with the Environment Definition tab in the UI.

1.8.2.2EnvironmentInstructions

Environment instructions describe a specific run request. They usually include things like:

This data is often ephemeral and changes from run to run.

1.8.3Environment registry

Defined in environment_registry.py, this maps each EnvironmentType to:

1.8.4Environment types

The EnvironmentType enumeration, defined in environment_utilities.py, currently includes:

1.9System-ID Environment Layer

Many environments derive from SysIdEnvironment, defined in abstract_sysid_environment.py.

These environments share common system-identification machinery and therefore have a richer lifecycle than simpler environments like Time or Read.

1.9.1Key abstractions

1.9.2SysIdMetadata

SysIdMetadata describes how the system identification run is performed. Important fields include:

1.9.3SysIdDataPackage

SysIdDataPackage stores the actual system identification results, including:

This package is what gets:

1.9.4SysID-capable environments

Currently, the system-ID-capable environments include:

1.10User Interface Layer

The UI is built with Qt and organized around one main application shell plus per-environment UI classes.

1.10.1Main UI

Defined in user_interface.py:

The main UI owns the top-level tabs and manages:

1.10.2Per-environment UIs

Each environment has a dedicated UI class. Examples include:

These are registered in ui_registry.py.

1.10.3Shared UI abstraction classes

Defined in:

These provide shared logic for:

1.10.4GUI update flow

Subprocesses send messages into the gui_update_queue. The RattlesnakeUI updater receives them and routes them either:

This is how:

flow back into the graphical interface.

1.11Profile / Timed Event System

Profiles are scheduled test procedures built from ProfileEvent objects.

Each ProfileEvent stores:

Examples include:

1.11.1ProfileManager

The ProfileManager, defined in profile_manager.py, is responsible for:

This timed-event system is one of the main mechanisms that bridges:

1.12Streaming and File I/O

1.12.1Streaming

The streaming.py module writes time-domain data and metadata to netCDF files during acquisition.

It is responsible for:

1.12.2Metadata and template loading

The load_utilities.py module handles:

1.12.3Environment-specific persistence

Most environments implement methods such as:

These methods are important extension points when adding new environments.

1.12.4System identification persistence

System identification packages can be saved to and loaded from multiple formats, including:

This makes it possible to:

1.13Data Flow Walkthroughs

This section sketches a few common controller flows.

1.13.1Hardware initialization flow

  1. A UI or headless caller builds a HardwareMetadata object.

  2. RattlesnakeController.initialize_hardware(...) validates it.

  3. The metadata is sent to:

    • acquisition process,

    • output process,

    • and any existing environment processes.

  4. Ready events confirm the configuration was received.

1.13.2Environment initialization flow

  1. A UI or headless caller builds one EnvironmentMetadata object per environment.

  2. RattlesnakeController.initialize_environments(...) validates them.

  3. EnvironmentManager starts any needed environment processes.

  4. Metadata is sent to:

    • acquisition,

    • output,

    • and the environment process itself.

1.13.3System identification flow

  1. A caller builds SysIdMetadata.

  2. The controller sends it to the target environment.

  3. Acquisition starts.

  4. Noise measurement runs.

  5. Transfer-function measurement runs.

  6. A SysIdDataPackage is produced.

  7. The UI and/or environment use the resulting data for prediction or control.

1.13.4Environment run flow

  1. A caller builds EnvironmentInstructions.

  2. The controller sends START_ENVIRONMENT.

  3. The output process activates that environment.

  4. The environment process starts its signal/control loop.

  5. Acquisition collects data and routes it back to the active environment.

  6. GUI updates and control updates are produced.

  7. Stop/shutdown eventually clears active events and drains queues.

1.13.5UI update flow

  1. A subprocess sends a GUI update message to gui_update_queue.

  2. RattlesnakeUI receives the message.

  3. It routes the message either:

    • to itself, or

    • to the appropriate environment UI.

  4. The receiving UI updates:

    • plots,

    • widgets,

    • dialogs,

    • or tables.

1.14Headless / API Usage

Headless use means interacting with Rattlesnake through Python code rather than through the GUI.

The same internal architecture is still used:

The main difference is that the caller is now responsible for:

A typical headless flow looks like:

  1. create a RattlesnakeController,

  2. initialize hardware,

  3. initialize environments,

  4. initialize system identification if needed,

  5. start acquisition,

  6. start one or more environments,

  7. wait for completion or stop them explicitly,

  8. stop acquisition,

  9. shut the controller down.

1.15Extending the Codebase

1.15.1Adding new hardware

Adding new hardware usually requires:

If the hardware should be available through the GUI, it also needs associated UI registration in ui_registry.py.

See Chapter 11 for more information.

1.15.2Adding a new environment

Adding a new environment usually requires:

If the environment is user-facing, it also needs a UI class and registration in ui_registry.py.

See Chapter 20 for more information.

1.15.3Adding a new system-ID environment

If the environment depends on system identification, it will generally be easier to derive from:

rather than starting from the plain environment base classes.

These environments typically also reuse:

1.15.4Adding a custom control law

Custom control laws depend on the environment:

Some environments also support interactive control laws via:

which allow a custom UI component to exchange parameters and results with the environment process.

1.15.5Adding UI documentation scenarios

The UI documentation generation system relies on:

If new UI pages or dialogs are added, documentation generation may need:

1.16Suggested Reading Order for New Developers

A good order to read the code is:

  1. main.py — to see how the application starts.

  2. engine.py — to understand the controller and lifecycle.

  3. controller.py — to understand how global commands are routed.

  4. environment_manager.py — to understand environment process ownership and validation.

  5. abstract_environment.py — to understand environment metadata/instruction/process abstractions.

  6. abstract_sysid_environment.py — to understand how system-ID-capable environments extend the base model.

  7. abstract_user_interface.py — to understand the general UI/controller interface.

  8. abstract_sys_id_user_interface.py — to understand shared system-ID UI flows.

  9. One simple environment, such as:

    • time_environment.py, or

    • read_environment.py

  10. One system-ID environment, such as:

  1. One virtual hardware backend, such as:

That reading order gives a good progression from orchestration to abstractions to concrete examples.

1.17Closing Remarks

Rattlesnake is a modular but process-oriented codebase. Understanding the queues, events, metadata objects, and environment lifecycle is more important than memorizing any single file.

A good mental model is:

This document is intended as a starting point for contributors and maintainers, and should evolve as the codebase evolves.