# How openlogi-agent-mock Simulates Device Inventory for Development and Testing

> Simulate device inventory with openlogi-agent-mock. Test the OpenLogi GUI without physical hardware using this scripted tarpc Agent implementation. Perfect for development.

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

---

**The `openlogi-agent-mock` binary provides a hardware-free, scripted implementation of the tarpc Agent service that builds an in-memory inventory mimicking real Logitech devices, enabling developers to run the OpenLogi GUI without any physical hardware attached.**

The `openlogi-agent-mock` is a critical development tool in the OpenLogi ecosystem (AprilNEA/OpenLogi) that eliminates hardware dependencies for testing and UI development. By simulating device inventory through a carefully crafted state machine, it allows developers to iterate on the desktop client using realistic, dynamic data that behaves like actual Logitech peripherals.

## Creating the Scripted Device State

The mock agent initializes its entire inventory through `State::new()` in [`crates/openlogi-agent/src/bin/mock_agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/bin/mock_agent.rs). This function pre-populates a `HashMap<u8, DeviceSettings>` that defines capabilities for multiple device types.

The initial state includes:

- A **primary mouse** with DPI and SmartShift settings
- An **offline mouse** to test disconnected-state handling
- A **keyboard** with configurable lighting
- A **directly-attached mouse** for testing non-Bolt connections

Each entry maps a slot number (`u8`) to `DeviceSettings` structs describing the device's configurable parameters. The function also tracks `next_slot` for scripted pairing operations.

```rust
// From the OpenLogi source: State::new (lines 88-138)
// Pre-populates device capabilities without any hardware queries
let state = State::new()?;

```

## Rendering Dynamic Inventory on Demand

The `State::render_inventory()` method (lines 449-454) reconstructs the complete inventory on every RPC call. This ensures the GUI always receives fresh, potentially mutated state.

The rendering process combines multiple inventory sources:

1. **Bolt receiver inventory** — generated by `bolt_inventory()` with simulated battery drain
2. **Scripted pairing additions** — devices added through mock pairing flow
3. **Direct-attached inventory** — standalone mouse reporting

```rust
// Conceptual flow of render_inventory
let mut inventory = Vec::new();
inventory.push(self.bolt_inventory());      // Receiver + paired devices
inventory.extend(self.paired_scripted());   // Newly paired devices
inventory.push(self.direct_inventory());    // Direct USB device

```

## Bolt Receiver Simulation with Battery Dynamics

The `bolt_inventory()` function (lines 559-650) constructs the most complex inventory component. It returns a `DeviceInventory` containing:

| Component | Description |
|-----------|-------------|
| Online mouse | Active device with saw-tooth battery curve |
| Offline mouse | Disconnected device (battery: None) |
| Keyboard | With `lighting: true` capability flag |

The **battery simulation** deserves special attention. The `draining_battery()` helper (lines 443-457) computes a saw-tooth curve that decreases approximately 1% per minute, wrapping back to full charge after depletion. This creates visible UI activity without actual power management.

```rust
// Battery level changes on every poll call
battery_percentage: draining_battery(start_instant),

```

## Standalone Lighting Device

Beyond traditional input devices, the mock implements `standalone_light()` (lines 495-520) representing a Litra Glow video light. This device exposes:

- Power state (on/off)
- Brightness level
- Color temperature

The GUI can control these via `set_light` RPC calls, with the mock immediately updating its internal state and reflecting changes in subsequent inventory renders.

## Simulating Foreground Application Context

Real Logitech software adapts settings per application. The mock replicates this through `State::foreground()` (lines 421-444), which cycles through a hard-coded list every 10 seconds:

- `com.apple.Safari`
- `com.microsoft.VSCode`
- `com.apple.Finder`

This deterministic rotation allows developers to verify the GUI's per-app profile rendering without a window server or actual process monitoring.

```rust
// Foreground switching enables testing app-specific DPI profiles
let app = state.foreground(); // Changes every 10s automatically

```

## RPC Exposure and Pairing Flow

The `Agent` trait implementation forwards `inventory()` calls directly to `State::render_inventory()` (lines 91-93), ensuring the GUI receives identical data structures as from a real agent.

The **scripted pairing flow** demonstrates full state transition:

```

start_pairing() → discovery delay → passkey exchange → pair_device()

```

The mock uses `tokio::time::sleep` constants to simulate realistic timing:

```rust
// Fake device discovery (lines 120-150)
let candidate = CANDIDATE_ADDRESS;          // Hard-coded MAC-like address
tokio::time::sleep(DISCOVERY_DELAY).await;  // Simulated radio discovery
// Passkey verification steps...
state.pair_scripted(new_device);            // Add to inventory (lines 669-688)

```

## Running the Mock Agent

Start the mock server:

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

```

Then launch the GUI against the development socket:

```bash
OPENLOGI_DEV_AGENT=0 cargo run -p openlogi-desktop

```

Programmatic interaction follows the same patterns as live hardware:

```rust
// Example DPI change via RPC
let route = DeviceRoute::Bolt {
    receiver_uid: RECEIVER_UID,
    slot: MOUSE_SLOT,
};
agent.set_dpi(context, route, Dpi::new(2000)).await?;

```

## Key Source Files

| File | Purpose |
|------|---------|
| [`crates/openlogi-agent/src/bin/mock_agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/bin/mock_agent.rs) | Core mock implementation, inventory construction, RPC handlers |
| [`crates/openlogi-core/src/device.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/device.rs) | `DeviceInventory`, `PairedDevice`, `BatteryInfo` definitions |
| [`crates/openlogi-ipc/src/transport.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/transport.rs) | Shared IPC transport for real and mock agents |

## Summary

- **`State::new()`** initializes a scripted `HashMap` of device capabilities without hardware queries
- **`render_inventory()`** dynamically assembles fresh inventory on every RPC call, combining Bolt receiver, scripted pairings, and direct-attached devices
- **`draining_battery()`** provides a saw-tooth battery curve (≈1%/minute) for live UI updates
- **Foreground cycling** (10s intervals) enables per-application profile testing without window server integration
- **Scripted pairing** simulates complete discovery → passkey → device addition flow using `tokio::time` delays
- The mock exposes identical RPC interfaces to the real agent, making it interchangeable for GUI development and CI testing

## Frequently Asked Questions

### Does the mock agent require any Logitech hardware to function?

No. The `openlogi-agent-mock` operates entirely in software. It never opens USB or HID interfaces; all device data originates from the scripted `State` struct. This design allows CI pipelines and developers without compatible hardware to build and test the full OpenLogi stack.

### How does the battery simulation work in the mock agent?

The `draining_battery()` function computes a saw-tooth based on elapsed time since mock start. Battery percentage decreases linearly (≈1% per minute), then wraps to 100% upon reaching zero. This recomputation happens fresh on each `render_inventory()` call, creating the appearance of live power monitoring without system power APIs.

### Can the mock simulate device pairing and removal?

Yes. The `start_pairing` RPC initiates a scripted flow: artificial discovery delay, fake candidate generation (`CANDIDATE_ADDRESS`), passkey exchange, and final device addition via `pair_scripted()`. The paired device immediately appears in subsequent inventory renders at the `next_slot` position. Simulated removal occurs by setting device presence to `false` in the underlying state.

### What device types does the mock inventory include?

The default `State::new()` populates four categories: online Bolt mouse (with DPI/SmartShift), offline Bolt mouse, keyboard with lighting support, and a directly-attached USB mouse. Additionally, `standalone_light()` provides a Litra Glow lighting device. Developers can extend this by modifying the mock source or adding runtime `pair_scripted` calls.