How to Use the OpenLogi Mock Agent (openlogi-agent-mock) for GUI Development Without Hardware

The openlogi-agent-mock binary emulates a complete Logitech HID++ device environment through scripted state machines and deterministic timers, allowing developers to run the full OpenLogi desktop GUI and test pairing flows, DPI adjustments, lighting control, and battery indicators without any physical hardware attached.

OpenLogi ships a dedicated mock agent that eliminates hardware dependencies for UI/UX development. The openlogi-agent-mock crate implements the same Tarpc IPC contract as the production agent while generating realistic device states through in-memory simulation, enabling rapid iteration on the openlogi-desktop interface without requiring Logitech peripherals.

What is the OpenLogi Mock Agent?

The mock agent is a Rust binary located at crates/openlogi-agent/src/bin/mock_agent.rs that creates a fully functional back-end for GUI development. Unlike the production agent that communicates with physical HID++ devices, this implementation maintains an in-memory inventory consisting of:

  • A Bolt receiver with an online mouse, offline mouse, and keyboard
  • A directly attached mouse (DeviceRoute::Direct)
  • A standalone Litra light

According to the source code in mock_agent.rs:20-33, the entry point builds a MockAgent from a scripted State and starts a Tokio runtime that binds to the IPC socket. The implementation stubs all hardware-touching RPCs—such as set_dpi, set_lighting, and set_light—to return Ok(()) immediately while logging the calls for inspection.

Setting Up the Mock Agent Environment

Running the Mock Agent Binary

The mock agent defaults to the dev profile to ensure it communicates over the development socket rather than the production one. As implemented in mock_agent.rs:75-86, the default_to_dev_profile() function forces OPENLOGI_PROFILE=dev unless explicitly overridden.

Start the mock agent from the repository root:

cargo run -p openlogi-agent --bin openlogi-agent-mock

This initializes the scripted state defined in mock_agent.rs:85-115, constructing per-device DeviceSettings including DPI ranges, SmartShift torque values, and lighting capabilities stored in a HashMap.

Connecting the Desktop GUI

With the mock agent running and holding the agent.lock file, launch the desktop GUI in a separate terminal:

OPENLOGI_DEV_AGENT=0 cargo run -p openlogi-desktop

The OPENLOGI_DEV_AGENT=0 environment variable instructs the GUI not to spawn a real agent process, allowing it to connect to the existing mock socket. The GUI will display the full simulated inventory and respond to state changes as if physical devices were connected.

Simulated Device Inventory and State Management

Scripted Hardware Inventory

The mock agent rebuilds its device inventory on every snapshot cycle through the render_inventory() function at mock_agent.rs:398-425. This method constructs:

  • Battery simulation: A draining_battery helper generates saw-tooth battery levels that deplete over time and recharge, providing realistic power indicators in the GUI
  • Connection states: Mixtures of online, offline, and directly-connected devices to test various UI layouts
  • Capability flags: Each device reports support for features like DPI adjustment, SmartShift, and RGB lighting

Dynamic State Simulation

All mutable device properties cycle through predetermined values using deterministic timers. The observe() function at mock_agent.rs:996-1008 periodically checks for state changes and increments a generation counter to trigger GUI re-renders, matching the behavior of the real agent's observation loop.

Testing GUI Features with the Mock Agent

Pairing Flow Development

The mock agent scripts the complete Bluetooth pairing process without radio hardware. The implementation at mock_agent.rs:728-795 provides:

  • start_pairing(): Initiates discovery mode with timed delays via tokio::sleep
  • Scripted discovery: Reports the "ERGO K860" keyboard after a simulated scan period
  • Passkey entry: Generates random pairing codes that the GUI must display
  • pair_device() / cancel_pairing(): Completes or aborts the flow

Test the pairing sequence programmatically:

use openlogi_ipc::{AgentClient, PairingCommandError, PairingPhase};
use tarpc::client::Config;

#[tokio::main]
async fn main() -> Result<(), PairingCommandError> {
    let client = AgentClient::new(Config::default(), transport::connect().await.unwrap());
    
    client.start_pairing(tarpc::context::current(), Default::default()).await?;
    
    loop {
        if let Some(update) = client.next_pairing(tarpc::context::current()).await? {
            println!("Pairing update: {:?}", update);
            if matches!(update, openlogi_ipc::PairingUpdate::Paired { .. }) {
                break;
            }
        }
        tokio::time::sleep(std::time::Duration::from_millis(200)).await;
    }
    Ok(())
}

Foreground Application Simulation

To test application-specific device profiles, the mock cycles through three macOS bundle identifiers defined in SCRIPTED_APPS every 10 seconds. The foreground() function generates ForegroundApps events that trigger the GUI's context-aware settings panels, allowing developers to verify that DPI and lighting profiles switch correctly based on the active application.

Interacting with the Mock Agent Programmatically

Inspect the current inventory directly via the IPC interface for debugging or automated testing:

use openlogi_ipc::transport;
use openlogi_ipc::AgentClient;
use tarpc::client::Config;

#[tokio::main]
async fn main() {
    let transport = transport::connect().await.unwrap();
    let client = AgentClient::new(Config::default(), transport);
    let inventory = client.inventory(tarpc::context::current()).await.unwrap();
    println!("{:#?}", inventory);
}

This outputs the complete device tree including the Bolt receiver, direct mouse, and Litra light with their current scripted states.

Summary

  • The openlogi-agent-mock binary provides a hardware-free development environment by implementing the full Tarpc IPC contract with in-memory state
  • Deterministic timers drive battery levels, foreground app changes, and device availability to create realistic UI test scenarios
  • The scripted pairing flow (start_pairing, next_pairing) simulates Bluetooth discovery, passkey entry, and device registration without radio hardware
  • Profile selection (dev vs. prod) ensures the mock communicates over the correct socket for packaged or development GUI builds
  • All hardware-side-effect RPCs are safely stubbed to return success while logging calls, preventing accidental device modifications during testing

Frequently Asked Questions

Can the mock agent simulate hardware failures or low-battery conditions?

Yes. The render_inventory() function in mock_agent.rs:398-425 controls battery simulation through the draining_battery helper, which generates saw-tooth patterns for charge levels. You can modify the scripted state to inject specific battery percentages, disconnect devices, or simulate offline states by adjusting the device construction logic before the Tokio runtime starts.

How does the mock agent handle profile switching between development and production?

The agent calls default_to_dev_profile() at lines 75-86 of mock_agent.rs during initialization, which sets OPENLOGI_PROFILE=dev unless already defined. To test against production socket paths or packaged GUI builds, explicitly set OPENLOGI_PROFILE=prod when launching the mock. This ensures the mock binds to the same IPC endpoint that production GUI clients expect.

Is it possible to add custom virtual devices to the mock inventory?

Currently, the inventory is hardcoded in the State::new() constructor at lines 85-115 of mock_agent.rs. To add custom devices, modify the render_inventory() method to include additional DeviceSettings entries in the HashMap with your desired capabilities (DPI ranges, SmartShift support, lighting zones). Recompile the binary to see your custom devices appear in the GUI inventory.

Does the mock agent support all Logitech HID++ commands?

The mock stubs all hardware-touching RPCs—such as set_dpi, set_smartshift, and set_light—to return Ok(()) immediately as shown at mock_agent.rs:891-904. While the GUI receives success confirmations for these calls, no actual device state changes occur. Query commands (inventory, settings, battery) return the scripted values from the internal State struct, providing realistic read-only data for interface development.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →