Skip to content

Fix compress_value#4

Open
MrPaschenko wants to merge 1 commit into
deadboy18:mainfrom
MrPaschenko:compress-value-fix
Open

Fix compress_value#4
MrPaschenko wants to merge 1 commit into
deadboy18:mainfrom
MrPaschenko:compress-value-fix

Conversation

@MrPaschenko
Copy link
Copy Markdown

Fix compress_value: IR transmit produced 16× too-short timings for all pulses > 2032 µs

Summary

The Ocrustar/ElkSmart pulse encoder in server.py divided every timing value by 16 before LEB128-encoding it. This is only correct for small values. For any pulse longer than 2032 µs — which includes the lead-in mark and inter-frame gaps of essentially every IR signal — the device firmware interprets the encoded bytes as raw microseconds, so the transmitted timing came out ~16× too short.

The result was an un-decodable carrier envelope: the IR LED still lit up, but no receiver could decode the signal. In practice this meant Ocrustar transmit (both synthesized protocol codes and captured/replayed signals) never produced a working IR command.

Root cause

compress_value used a single code path that always applied round(value / 16) and then LEB128-encoded the result. The correct encoding has two regimes:

  • ≤ 2032 µs: a single byte = round(µs / 16) (device decodes as byte × 16)
  • > 2032 µs: LEB128 of the raw microsecond value (NOT divided by 16); the device detects the continuation bit and decodes the raw value directly

So e.g. a Sony 2400 µs start pulse was transmitted as ~150 µs, and 45 ms inter-frame gaps as ~2.8 ms.

Fix

Reimplemented compress_value to match the two-regime behavior. Round-trip verified: 2400 / 9000 / 45000 / 58992 µs now decode to their exact values; small values stay within the normal ±8 µs (16 µs quantization).

How it was found

Reproduced with real hardware: an Ocrustar USB IR blaster (D552 variant, PID 0x0132) transmitting to a Sony Bravia TV (RMT-TX450E remote). Symptoms were a flashing IR LED but zero response from the TV, for both generated SIRC codes and signals captured from the original remote — which pointed at a timing/encoding corruption rather than wrong command data or carrier frequency.

The bug and fix were diagnosed and generated with Claude Opus 4.8 (Claude Code).

Reference for the correct implementation

Verified against the upstream Android app's shipping driver, ElkSmartUsbProtocolFormatter.compressValueUs():

https://github.com/iodn/android-ir-blaster/blob/master/android/app/src/main/kotlin/org/nslabs/irblaster/usb/ElkSmartUsbProtocolFormatter.kt

Note: the two-regime split is the key detail — some cleaned-up reimplementations collapse it into a single always-÷16 path, which reproduces this same bug.

Testing

  • Verified the encoder output round-trips to correct microsecond values for representative small and large pulses.
  • Confirmed on real hardware: after the fix, Sony Power and Volume commands control the TV reliably, and captured signals replay successfully (works at both 38 kHz and 40 kHz carrier).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant