Skip to content

indina853/NariMeter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

65 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

NariMeter

Because a 600 MB software suite with background telemetry, auto-updating services, and a RGB lighting SDK shouldn't be the only way to know if your headset is dying.

A lightweight Windows system tray application that displays the real-time battery level of the Razer Nari wireless headset β€” built entirely through USB protocol reverse engineering, with zero dependency on Razer software.

Windows 10+ .NET 8 License: MIT Portable


The Problem with Razer Synapse

The Razer Nari is a capable wireless headset. Its companion software, Razer Synapse, is not.

Synapse installs a constellation of background services β€” RazerNahimic, RazerGameScannerService, RazerCentralService, among others β€” that collectively consume anywhere from 300 to 600 MB of RAM at idle, register themselves for autostart without asking, and maintain persistent network connections to Razer's telemetry infrastructure. This means your headset usage patterns, audio settings, and hardware identifiers are being transmitted to external servers as a condition of knowing your battery percentage.

The interface itself buries the battery indicator under multiple clicks inside a large, slow-loading overlay. For a single piece of information β€” how much battery does my headset have? β€” the friction is remarkable.

NariMeter answers that question with a glanceable tray icon, ~345 KB on disk, and no network activity whatsoever.


Features

  • Battery percentage displayed directly in the Windows system tray
  • Color-coded icons reflecting charge level at a glance
  • Dedicated charging icon when the USB cable is connected
  • Fully charged indicator when the cable is connected and battery is at 100%
  • Headphone icon when the headset is powered off or disconnected
  • Hover tooltip with current status
  • Low battery notifications β€” configurable warn and critical thresholds
  • Fully charged notification β€” alerts when the headset reaches 100%
  • Run at Startup toggle in the right-click menu (via Windows registry, no installer required)
  • Fully portable β€” single .exe, no installation, no registry pollution beyond the optional startup entry

Tray Icon Reference

Icon State
🟒 Green battery Connected and discharging β€” battery > 50%
🟑 Yellow battery Connected and discharging β€” battery > 20%
πŸ”΄ Red battery Connected and discharging β€” battery ≀ 20%
⚑ Charging battery USB cable connected, actively charging
🟒 Green battery USB cable connected, 100% fully charged
🎧 Headphone Headset powered off or dongle disconnected

Requirements

  • Windows 10 or later (x64)
  • .NET 8 Runtime β€” required, free, one-click install
  • Razer Nari wireless headset with USB dongle connected
  • WinUSB driver installed on Interface 5 of the dongle (see setup below)

For developers building from source: .NET 8 SDK is required instead of the runtime.


Setup

1. Install the .NET 8 Runtime

Download and run the installer from dotnet.microsoft.com. Many users will already have this installed.

2. Install the WinUSB driver

NariMeter communicates with the Nari dongle at the USB protocol level, bypassing Razer's driver stack entirely. For this to work, the WinUSB generic driver must be bound to Interface 5 of the dongle.

  1. Download and run Zadig
  2. In the menu, select Options β†’ List All Devices
  3. Locate Razer Nari in the dropdown β€” select the entry corresponding to Interface 5
  4. Set the target driver to WinUSB
  5. Click Replace Driver

This does not affect the headset's audio functionality. Only the HID interface used for battery reporting is replaced. Razer Synapse will lose the ability to communicate with the dongle on this interface, which is entirely the point.

3. Run NariMeter

Download NariMeter.exe from the Releases page and run it. No installation required. The tray icon will appear within a few seconds of the dongle being recognized.


How It Works β€” Reverse Engineering the Protocol

NariMeter was built without access to any Razer documentation. The battery reporting protocol was discovered entirely through USB traffic analysis.

Step 1 β€” Identifying the device

The Razer Nari dongle presents itself to Windows as a USB composite device with multiple interfaces. Using USBPcap and Wireshark, all HID traffic between Razer Synapse and the dongle was captured during normal operation.

The device identifiers are:

Vendor ID:  0x1532  (Razer Inc.)
Product ID: 0x051C  (Nari dongle)
Interface:  5

Interface 5 is the HID interface responsible for device status reporting, separate from the audio and standard HID interfaces used for button input.

Step 2 β€” Capturing the handshake

Synapse communicates with Interface 5 via USB HID control transfers β€” SET_REPORT followed by GET_REPORT β€” a standard HID pattern for querying device state.

The captured SET_REPORT payload that triggers a battery response is:

FF 0A 00 FD 04 12 F1 02 05 00 00 ... (64 bytes total)

This is sent as a Class request to the interface:

bmRequestType: 0x21  (Host β†’ Device, Class, Interface)
bRequest:      0x09  (SET_REPORT)
wValue:        0x03FF
wIndex:        5     (Interface number)
wLength:       64

Step 3 β€” Reading the response

A subsequent GET_REPORT request retrieves the 64-byte response from the device:

bmRequestType: 0xA1  (Device β†’ Host, Class, Interface)
bRequest:      0x01  (GET_REPORT)
wValue:        0x03FF
wIndex:        5
wLength:       64

Within the response buffer, the relevant fields are:

Byte offset Value Meaning
[1] 0x01 Device idle / headset not active
[2] 0x00 Confirms idle state
[9] 0x05 USB cable connected (charging)
[9] 0x06 USB cable connected, battery fully charged
[9] 0x03 USB cable disconnected (discharging)
[12:13] uint16 big-endian Battery voltage in millivolts β€” primary source for percentage calculation
[14] uint8 Battery percentage reported by device firmware β€” used as sanity check only

Step 4 β€” Battery percentage calculation

The firmware value at response[14] is not used as the primary battery percentage. Empirical testing revealed that the Nari firmware updates this value in large discrete jumps β€” for example, holding at 80% for several hours before dropping directly to 50% on a change of only ~8 mV. This is a firmware calibration limitation, not a reflection of actual charge level.

NariMeter instead derives the battery percentage from the raw millivolt reading at response[12:13], using a linear interpolation between the minimum and maximum observed voltages for the device:

percent = (mv - minMv) / (maxMv - minMv) Γ— 100

The voltage bounds (minMv, maxMv) are calibrated adaptively per device β€” they start with conservative defaults and are refined automatically as the app observes readings near the low and high extremes during normal use. Calibrated values are persisted across sessions in NariMeter.state.json.

The firmware value at response[14] is retained as a sanity check: if the voltage-derived percentage and the firmware-reported percentage diverge by more than 40 percentage points, the reading is considered anomalous and the last known good value is preserved instead.

Battery percentage is displayed in steps of 5% and transitions smoothly β€” the displayed value moves at most 5% per poll cycle regardless of how large the underlying change is, preventing sudden jumps in the tray icon.

Step 5 β€” Charge state detection

The device reports charge state natively via response[9]:

isCharging    = response[9] == 0x05;
isFullyCharged = response[9] == 0x06;

isFullyCharged short-circuits all stabilization and percentage calculation paths in BatteryReader β€” it is checked first and treated as authoritative. The tray icon and tooltip also treat Charging && BatteryPercent >= 100 as Fully Charged to cover the trickle charge window before the firmware byte transitions: the firmware can remain on 0x05 for several minutes after the cell reaches capacity while voltage stabilizes at 4200 mV.

Step 6 β€” State machine and debouncing

Raw USB readings are noisy, particularly at startup when the host controller and device are still negotiating. NariMeter implements independent debounce thresholds in UsbDevice.cs:

  • Powered on is only confirmed after 4 consecutive active readings (4 Γ— 2s = 8 seconds)
  • Powered off is confirmed after 4 consecutive idle readings

Battery level is polled every 30 seconds when active, which is sufficient given the slow rate of battery discharge.

Step 7 β€” Architecture overview

Program.cs
└── TrayApp.cs           β€” ApplicationContext, timer orchestration, tray icon management
    β”œβ”€β”€ BatteryReader.cs β€” Charge state logic, stabilization, state persistence
    β”œβ”€β”€ UsbDevice.cs     β€” USB HID control transfers, static buffers, debounce state machine
    β”œβ”€β”€ HeadsetState.cs  β€” Immutable state record, ChargeStatus enum, tooltip formatting
    β”œβ”€β”€ StateStore.cs    β€” JSON persistence of last known percentage and calibrated mV bounds
    └── StartupManager.cs β€” Windows registry autostart toggle (HKCU\...\Run)

Notifications

NariMeter supports optional Windows balloon notifications, toggled via the right-click menu under Show Notifications:

  • Low Battery Warning β€” triggered at a configurable threshold (default: 20%)
  • Battery Critical β€” triggered at a configurable threshold (default: 10%)
  • Fully Charged β€” triggered when the headset reaches 100% while on cable

Warn and critical thresholds are configurable independently via the right-click menu and persist across sessions.


Building from Source

Requires: .NET 8 SDK

git clone https://github.com/indina853/NariMeter.git
cd NariMeter
dotnet build

Publishing a portable executable:

dotnet publish -c Release

Output: bin\Release\net8.0-windows\win-x64\publish\NariMeter.exe

The published executable requires the .NET 8 Runtime on the target machine.


Performance

Metric Value
Executable size ~345 KB
RAM usage (steady state) ~15 MB
CPU usage < 0.1%
Network activity None
Disk writes Only on battery % change and settings updates
Poll interval β€” state 2 seconds
Poll interval β€” battery 30 seconds

Project Structure

NariMeter/
β”œβ”€β”€ BatteryReader.cs       β€” Battery charge state logic
β”œβ”€β”€ HeadsetState.cs        β€” State record and charge status definitions
β”œβ”€β”€ Program.cs             β€” Entry point
β”œβ”€β”€ StartupManager.cs      β€” Run at startup via Windows registry
β”œβ”€β”€ StateStore.cs          β€” Battery % and settings persistence (JSON)
β”œβ”€β”€ TrayApp.cs             β€” Tray icon, timers, notifications, menu
β”œβ”€β”€ UsbDevice.cs           β€” USB HID communication layer
β”œβ”€β”€ App.ico                β€” Application icon (task manager, Explorer)
β”œβ”€β”€ Headphone.ico          β€” Tray: powered off / disconnected state
β”œβ”€β”€ BatteryGreen.ico       β€” Tray: battery > 50% or fully charged
β”œβ”€β”€ BatteryYellow.ico      β€” Tray: battery > 20%
β”œβ”€β”€ BatteryRed.ico         β€” Tray: battery ≀ 20%
β”œβ”€β”€ BatteryCharging.ico    β€” Tray: charging
β”œβ”€β”€ NariMeter.csproj
└── RunTimeConfig.template.json

Contributing

Pull requests are welcome. If you own a different Razer wireless headset and want to investigate whether the same protocol applies, the starting point is capturing USB traffic with USBPcap + Wireshark while Synapse queries your device, then comparing the SET_REPORT payload and response byte layout against the Nari protocol documented above.


License

MIT β€” see LICENSE

About

Razer Nari Headset battery level in your tray. No Synapse required.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

  •  

Contributors

Languages