# How SmartShift Is Configured in OpenLogi: TOML, UI, and HID++ Protocol

> Discover how SmartShift is configured in OpenLogi using TOML, UI, and HID++ protocol. Learn the three-layer architecture for seamless device communication and dynamic settings.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: internals
- Published: 2026-09-01

---

**SmartShift in OpenLogi is configured through a three-layer architecture: declarative TOML files for static settings, runtime UI/CLI for dynamic changes, and HID++ protocol commands for device communication.**

OpenLogi implements Logitech's **SmartShift** (dynamic ratchet/free-spin wheel mode) as a core device feature in its Rust-based driver stack. This article explains how SmartShift configuration flows from user-facing settings to hardware commands, based on the OpenLogi source code in the `AprilNEA/OpenLogi` repository.

---

## TOML Configuration File Structure

User-defined SmartShift settings live in [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) under each device's `smartshift` table. The example in [`docs/config.example.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/docs/config.example.toml) shows the complete schema:

```toml
[devices."receiver:aabbccdd:slot:1".smartshift]
mode           = "ratchet"      # or "free"

auto_disengage = 16            # threshold (0-255)

tunable_torque = 50           # torque (0-255)

```

The `mode` field accepts two string values:
- `ratchet` — stepped scrolling with tactile feedback
- `free` — smooth spinning with no resistance

`auto_disengage` controls how long the ratchet mode holds before automatic release (higher values = longer hold). `tunable_torque` sets the mechanical resistance strength in ratchet mode (higher values = stronger detents). Both are **u8** values (0-255 range).

---

## Configuration Parsing and Core Types

The TOML deserializes into internal structures defined in `openlogi-core`. In [`crates/openlogi-core/src/hid/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/hid/smartshift.rs), the `SmartShiftStatus` struct mirrors both the configuration format and the HID++ wire protocol:

- `mode` → `SmartShiftMode` enum (`Ratchet` or `Free`)
- `auto_disengage` → `SmartShiftAutoDisengage::Threshold(u8)`
- `tunable_torque` → `TunableTorque(u8)`

This design ensures zero-copy translation between user configuration and device commands.

---

## Device Write Path: From Status to HID++ Commands

When settings change, `openlogi-device` translates `SmartShiftStatus` into hardware commands. The high-level API lives in [`crates/openlogi-device/src/write/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/write/smartshift.rs):

```rust
// Key functions in the write module
pub async fn set_smartshift(route: &DeviceRoute, status: SmartShiftStatus) -> Result<(), WriteError>;
pub async fn toggle_smartshift(route: &DeviceRoute) -> Result<SmartShiftMode, WriteError>;
pub async fn set_smartshift_sensitivity(route: &DeviceRoute, threshold: u8, torque: u8) -> Result<(), WriteError>;

```

These functions open the **SmartShift feature** from `openlogi-hidpp` and dispatch the appropriate sub-commands:
- `set_status` — full configuration write
- `toggle` — mode switch only
- `set_sensitivity` — threshold and torque adjustment

---

## HID++ Protocol Implementation

The wire-format protocol definitions span two feature files in `openlogi-hidpp`:

| Feature ID | File | Device Support |
|-----------|------|---------------|
| 0x2110 | [`crates/openlogi-hidpp/src/feature/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/feature/smartshift.rs) | Standard SmartShift devices |
| 0x2111 | [`crates/openlogi-hidpp/src/feature/smartshift_enhanced.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hidpp/src/feature/smartshift_enhanced.rs) | Newer hardware variants |

The actual I/O dispatch occurs in [`crates/openlogi-hid/src/host.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/host.rs), which exposes async functions that the agent and UI consume:

```rust
use openlogi_hid::host::get_smartshift_status;
use openlogi_core::hid::smartshift::SmartShiftStatus;

async fn print_smartshift(route: &DeviceRoute) -> Result<(), WriteError> {
    let status: SmartShiftStatus = get_smartshift_status(route).await?;
    println!("Mode: {:?}, Auto‑Disengage: {:?}, Torque: {:?}",
             status.mode, status.auto_disengage, status.tunable_torque);
    Ok(())
}

```

---

## Runtime UI and IPC Architecture

The `openlogi-ui` crate communicates with the background agent through tarpc RPC defined in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs):

```rust
// RPC methods for SmartShift control
async fn read_smartshift(device_key: String) -> Result<SmartShiftStatus, IpcError>;
async fn set_smartshift(device_key: String, status: SmartShiftStatus) -> Result<(), IpcError>;

```

The UI workflow:
1. Call `read_smartshift` on device selection to populate state
2. Display toggle button and sensitivity slider (localized via `crates/openlogi-ui/locales/*.toml`)
3. Call `set_smartshift` when user changes settings

CLI users bypass the UI entirely:

```bash
openlogi set_smartshift --device "receiver:aabbccdd:slot:1" --mode free

```

---

## Configuration Persistence and Hot-Reload

OpenLogi supports live configuration updates:
1. File watcher detects [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) changes
2. Agent re-parses affected `SmartShiftStatus` values
3. New settings push to device via the same write path as UI changes

This unifies declarative (file-based) and imperative (runtime) configuration into a single pipeline.

---

## Summary

- **TOML files** provide static SmartShift configuration under `[devices."..."].smartshift` tables
- **`SmartShiftStatus`** in `openlogi-core` bridges user settings and HID++ wire format
- **Write helpers** in [`openlogi-device/src/write/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-device/src/write/smartshift.rs) translate status to device commands
- **HID++ features** 0x2110 and 0x2111 handle protocol-level communication
- **tarpc IPC** enables UI and CLI to control SmartShift without direct hardware access
- **Hot-reload** ensures file changes propagate to connected devices immediately

---

## Frequently Asked Questions

### What are the valid values for SmartShift mode in OpenLogi?

OpenLogi accepts two string values for the `mode` field: `"ratchet"` for stepped scrolling and `"free"` for smooth spinning. These map to the `SmartShiftMode::Ratchet` and `SmartShiftMode::Free` variants in [`crates/openlogi-core/src/hid/smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/hid/smartshift.rs).

### How do I adjust SmartShift sensitivity in OpenLogi?

Use the `auto_disengage` field (0-255) to control how long ratchet mode holds, and `tunable_torque` (0-255) to set mechanical resistance. Higher `auto_disengage` values delay automatic disengagement; higher `tunable_torque` values create stronger tactile feedback. Both can be set via TOML or adjusted at runtime through the UI slider.

### Why does OpenLogi have two HID++ feature IDs for SmartShift?

Feature ID **0x2110** in [`smartshift.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/smartshift.rs) supports standard SmartShift hardware, while **0x2111** in [`smartshift_enhanced.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/smartshift_enhanced.rs) handles newer devices with extended capabilities. The `openlogi-device` crate automatically selects the appropriate feature based on device capability detection.

### Can I toggle SmartShift without editing the configuration file?

Yes. The UI toggle button calls `toggle_smartshift()` via IPC, and the CLI supports `openlogi set_smartshift --mode <ratchet|free>`. Runtime changes persist only until device disconnect unless you also update [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml) or enable auto-save in UI preferences.