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

> Develop your OpenLogi GUI without hardware. Use openlogi-agent-mock to emulate HID++ devices, test pairing, DPI, lighting, and battery indicators efficiently.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: how-to-guide
- Published: 2026-08-26

---

**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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent/src/bin/mock_agent.rs#L20-L33), 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`](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent/src/bin/mock_agent.rs#L75-L86), the `default_to_dev_profile()` function forces `OPENLOGI_PROFILE=dev` unless explicitly overridden.

Start the mock agent from the repository root:

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

```

This initializes the scripted state defined in [`mock_agent.rs:85-115`](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent/src/bin/mock_agent.rs#L85-L115), 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:

```bash
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`](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent/src/bin/mock_agent.rs#L398-L425). 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`](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent/src/bin/mock_agent.rs#L996-L1008) 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`](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent/src/bin/mock_agent.rs#L728-L795) 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:

```rust
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:

```rust
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`](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent/src/bin/mock_agent.rs#L398-L425) 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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/main/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`](https://github.com/AprilNEA/OpenLogi/blob/master/crates/openlogi-agent/src/bin/mock_agent.rs#L891-L904). 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.