Skip to content

Fix the one degree hue shift in the LED fixed colour layer - #11918

Open
Raffi1202 wants to merge 1 commit into
iNavFlight:maintenance-10.xfrom
Raffi1202:fix/led-strip-colour-table
Open

Raffi1202 wants to merge 1 commit into
iNavFlight:maintenance-10.xfrom
Raffi1202:fix/led-strip-colour-table

Conversation

@Raffi1202

@Raffi1202 Raffi1202 commented Sep 10, 2026

Copy link
Copy Markdown

Problem

#9633: on a SpeedyBee F405WING running INAV 7.0.0 with a WS2812 bar, an LED set to COLOR mode with colour 2 (documented as red) lights up pink. The same LED switched to GPS mode without a fix shows a proper red, so the reporter asked for colour 2 to be changed to match the GPS red.

Cause

src/main/io/ledstrip.c:444 on maintenance-10.x: applyLedFixedLayers() starts with hOffset = HSV_HUE_MAX (359, src/main/common/color.h:40) and applies it at line 498 as color.h = (color.h + hOffset) % (HSV_HUE_MAX + 1). The offset is meant as a neutral full turn so the negative adjustments of the battery and RSSI branches (down to -30) cannot push the hue below zero, but a full turn is 360, not 359. Every hue through this layer comes out one degree low: red (hue 0) becomes hue 359, which hsvToRgb24() (src/main/common/colorconversion.c:74, case 5) renders as R255 G0 B4 instead of R255 G0 B0. The GPS layer (ledstrip.c:637) writes its colour through applyLedHsv() with no offset, which is why the same palette entry is correct there.

Change

One line: hOffset starts at HSV_HUE_MAX + 1 (360), with a comment stating why. The palette table, the conversion function and the function-channel branch (which assigns hOffset instead of adding to it) are untouched. No stored setting or default changes.

Test

Not run on hardware or SITL. Cause verified by reading ledstrip.c:444 and :498 and colorconversion.c:74; the RGB values follow directly from the switch (hue / 60) cases: hue 359 vs 0 gives 255,0,4 vs 255,0,0 (red), hue 119 vs 120 gives 4,255,0 vs 0,255,0 (green). With the fix, the battery and RSSI ramps give hue 0 (red) at 20 % and hue 330 (deep pink) at 0 %, as listed in docs/LedStrip.md. No fork CI run exists for this branch yet.

Flash / RAM

Not measured yet. The upstream firmware CI has not been released for this PR, so no size report exists.

Docs

No documentation change needed: docs/LedStrip.md already lists colour 2 as red and the battery/RSSI ramps as red at 20 % and deep pink at 0 %; the fix makes the firmware match that text.

applyLedFixedLayers() biases every hue by hOffset before taking the
modulo, so the negative adjustments used by the battery, RSSI and
throttle layers cannot push the hue below zero. That bias has to be a
full turn, but it was HSV_HUE_MAX (359) instead of 360, so every LED
handled by this layer came out one degree low.

Colour 2 (red, hue 0) therefore reached the strip as hue 359, which
hsvToRgb24() turns into R=255 G=0 B=4 instead of R=255 G=0 B=0 - the
pink tint that was reported. The GPS layer writes its colour straight
to the LED buffer without the offset, which is why the same red looks
correct in GPS mode.

With the full turn restored the battery and RSSI ramps again hit the
colours listed in docs/LedStrip.md.

Fixes iNavFlight#9633
@Raffi1202
Raffi1202 marked this pull request as ready for review September 11, 2026 15:41
@qodo-code-review

Copy link
Copy Markdown
Contributor

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Tip of the day
💡 Did you know, you can switch off images and animations for a plain-text comment

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Correct fixed LED layer's one-degree hue shift

🐞 Bug fix 🕐 Less than 5 minutes

Grey Divider

AI Description

• Uses a full 360-degree neutral offset when processing fixed LED layer hues.
• Preserves exact palette colors while keeping negative hue adjustments safe.
Diagram

graph TD
  A["Palette Color"] -->|selected hue| B["Fixed LED Layer"] -->|starts at 360| C["Hue Adjustments"] -->|modulo 360| D["Wrapped Hue"] -->|write HSV| E["LED Buffer"]
Loading
High-Level Assessment

The targeted correction is optimal: using HSV_HUE_MAX + 1 expresses the full 360-degree turn required by the existing modulo operation. Changing the shared palette or HSV conversion would incorrectly alter already-correct consumers such as the GPS layer.

Files changed (1) +1 / -1

Bug fix (1) +1 / -1
ledstrip.cUse a full-turn hue offset in the fixed LED layer +1/-1

Use a full-turn hue offset in the fixed LED layer

• Initializes the fixed-layer hue offset to 360 instead of 359. This removes the one-degree palette shift while retaining enough positive bias for battery, RSSI, and throttle adjustments.

src/main/io/ledstrip.c

@sensei-hacker

Copy link
Copy Markdown
Member

Note: #11820 (LED rainbow overlay) touches the same hue path in ledstrip.c/colorconversion.c. Worth checking the rainbow overlay's hsvColor_t construction against this applyLedFixedLayers() offset fix before either merges, so one doesn't mask or re-introduce the other.

@Raffi1202

Copy link
Copy Markdown
Author

@sensei-hacker Checked #11820 against this one. They do not collide, and the rainbow overlay does not carry the same off-by-one.

No overlap in the diff. This PR changes one line inside applyLedFixedLayers() (ledstrip.c:444). #11820 adds applyLedRainbowLayer() as a new function around line 843, plus the config plumbing. Different functions, no shared hunk.

The rainbow's hue construction is already correct. HSV_HUE_MAX is 359 (common/color.h:40), so a full turn is 360 - which is exactly what this PR changes hOffset to. #11820 writes:

ledColor.h = (rainbowHue + (rainbowIndex * rainbowDelta)) % 360;

That is the same 360, so its hue never picks up the one-degree shift this PR fixes. Neither masks nor re-introduces the other, and the merge order does not matter.

One cosmetic thing for #11820, not a defect: it hardcodes 360 where HSV_HUE_MAX + 1 would say why. Worth a note there if you are reviewing it anyway.

I also looked at its ledColor.s = 0; // Force full color saturation since that reads backwards at first glance - it is right: colorconversion.c:32 does sat = 255 - c->s, so 0 is full saturation in this codebase. No issue.

From reading only; no LED hardware here.

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.

2 participants