Skip to content

Commit 60c85ea

Browse files
Hall effect interference correction
1 parent 5296894 commit 60c85ea

6 files changed

Lines changed: 312 additions & 3 deletions

File tree

src/hhd/device/rog_ally/__init__.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
Emitter,
88
Event,
99
HHDPlugin,
10+
fix_hall_interference,
1011
load_relative_yaml,
12+
get_hall_interference_config,
1113
get_outputs_config,
1214
get_limits_config,
1315
fix_limits,
@@ -40,7 +42,7 @@ def open(
4042
self.prev = None
4143

4244
def settings(self) -> HHDSettings:
43-
from .base import LIMIT_DEFAULTS
45+
from .base import HALL_DEFAULTS, LIMIT_DEFAULTS
4446

4547
base = {"controllers": {"rog_ally": load_relative_yaml("controllers.yml")}}
4648
base["controllers"]["rog_ally"]["children"]["controller_mode"].update(
@@ -51,16 +53,24 @@ def settings(self) -> HHDSettings:
5153
base["controllers"]["rog_ally"]["children"]["limits"] = get_limits_config(
5254
LIMIT_DEFAULTS(self.ally_x)
5355
)
56+
base["controllers"]["rog_ally"]["children"][
57+
"hall_interference"
58+
] = get_hall_interference_config(HALL_DEFAULTS)
5459

5560
if not self.xbox:
5661
del base["controllers"]["rog_ally"]["children"]["swap_xbox"]
5762

5863
return base
5964

6065
def update(self, conf: Config):
61-
from .base import LIMIT_DEFAULTS
66+
from .base import HALL_DEFAULTS, LIMIT_DEFAULTS
6267

6368
fix_limits(conf, "controllers.rog_ally.limits", LIMIT_DEFAULTS(self.ally_x))
69+
fix_hall_interference(
70+
conf,
71+
"controllers.rog_ally.hall_interference",
72+
HALL_DEFAULTS,
73+
)
6474

6575
new_conf = conf["controllers.rog_ally"]
6676
if new_conf == self.prev:

src/hhd/device/rog_ally/base.py

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import select
33
import time
44
from threading import Event as TEvent
5-
from typing import Sequence, Literal
5+
from typing import Sequence
66

77
from hhd.controller import DEBUG_MODE, Axis, Event, Multiplexer, can_read
88
from hhd.controller.lib.hide import unhide_all
@@ -123,6 +123,36 @@
123123
)
124124

125125

126+
def _clamp_axis(v: float) -> float:
127+
return max(-1.0, min(1.0, v))
128+
129+
130+
def _edge_gain_center(x: float) -> float:
131+
# Full effect near center, smoothly fades near extremes.
132+
g = max(0.0, 1.0 - abs(x))
133+
return g * g
134+
135+
136+
def _remap_deadzone(v: float, d: float) -> float:
137+
av = abs(v)
138+
if av <= d:
139+
return 0.0
140+
if d >= 1.0:
141+
return 0.0
142+
out = (av - d) / (1.0 - d)
143+
return -out if v < 0 else out
144+
145+
146+
HALL_DEFAULTS = {
147+
"ls_idle_x": -0.15,
148+
"lt_corr_x": 0.28,
149+
"rs_idle_x": 0.10,
150+
"ls_deadzone": 0.15,
151+
"rs_deadzone": 0.05,
152+
"remap_deadzone": True,
153+
}
154+
155+
126156
class AllyHidraw(GenericGamepadHidraw):
127157
def __init__(self, *args, kconf={}, rgb_boot, rgb_charging, **kwargs) -> None:
128158
super().__init__(*args, **kwargs)
@@ -339,6 +369,40 @@ def controller_loop(
339369
swap_armoury = conf.get("swap_armory", False)
340370
swap_xbox = conf.get("swap_xbox", False) and xbox
341371

372+
hall_mode = conf.get("hall_interference.mode", "default")
373+
hall_enabled = hall_mode != "disabled"
374+
if hall_mode == "manual":
375+
hall_ls_idle_x = conf.get(
376+
"hall_interference.manual.ls_idle_x", HALL_DEFAULTS["ls_idle_x"]
377+
)
378+
hall_lt_corr_x = conf.get(
379+
"hall_interference.manual.lt_corr_x", HALL_DEFAULTS["lt_corr_x"]
380+
)
381+
hall_rs_idle_x = conf.get(
382+
"hall_interference.manual.rs_idle_x", HALL_DEFAULTS["rs_idle_x"]
383+
)
384+
hall_ls_deadzone = conf.get(
385+
"hall_interference.manual.ls_deadzone", HALL_DEFAULTS["ls_deadzone"]
386+
)
387+
hall_rs_deadzone = conf.get(
388+
"hall_interference.manual.rs_deadzone", HALL_DEFAULTS["rs_deadzone"]
389+
)
390+
hall_remap_deadzone = conf.get(
391+
"hall_interference.manual.remap_deadzone",
392+
bool(HALL_DEFAULTS["remap_deadzone"]),
393+
)
394+
else:
395+
hall_ls_idle_x = HALL_DEFAULTS["ls_idle_x"]
396+
hall_lt_corr_x = HALL_DEFAULTS["lt_corr_x"]
397+
hall_rs_idle_x = HALL_DEFAULTS["rs_idle_x"]
398+
hall_ls_deadzone = HALL_DEFAULTS["ls_deadzone"]
399+
hall_rs_deadzone = HALL_DEFAULTS["rs_deadzone"]
400+
hall_remap_deadzone = bool(HALL_DEFAULTS["remap_deadzone"])
401+
402+
rt_now = 0.0
403+
ls_y_now = 0.0
404+
rs_y_now = 0.0
405+
342406
# Imu
343407
d_imu = CombinedImu(conf["imu_hz"].to(int), ALLY_MAPPINGS, gyro_scale="0.000266")
344408
d_timer = HrtimerTrigger(conf["imu_hz"].to(int), [HrtimerTrigger.IMU_NAMES])
@@ -474,6 +538,109 @@ def prepare(m):
474538
evs.extend(d.produce(r))
475539
evs.extend(d_vend.produce(r))
476540

541+
if hall_enabled:
542+
ls_x_seen = False
543+
ls_y_seen = False
544+
rs_x_seen = False
545+
rs_y_seen = False
546+
rt_seen = False
547+
for ev in evs:
548+
if ev["type"] != "axis":
549+
continue
550+
elif ev["code"] == "rt":
551+
rt_now = max(0.0, float(ev["value"]))
552+
rt_seen = True
553+
elif ev["code"] == "ls_x":
554+
ls_x_now = float(ev["value"])
555+
ls_x_seen = True
556+
elif ev["code"] == "ls_y":
557+
ls_y_now = float(ev["value"])
558+
ls_y_seen = True
559+
elif ev["code"] == "rs_x":
560+
rs_x_now = float(ev["value"])
561+
rs_x_seen = True
562+
elif ev["code"] == "rs_y":
563+
rs_y_now = float(ev["value"])
564+
rs_y_seen = True
565+
566+
trigger_corr_x = hall_lt_corr_x * rt_now
567+
ls_idle_gain = _edge_gain_center(ls_x_now)
568+
rs_idle_gain = _edge_gain_center(rs_x_now)
569+
570+
ls_x_corr = _clamp_axis(
571+
ls_x_now - hall_ls_idle_x * ls_idle_gain + trigger_corr_x
572+
)
573+
ls_y_corr = _clamp_axis(ls_y_now)
574+
ls_in_deadzone = (
575+
abs(ls_x_corr) < hall_ls_deadzone
576+
and abs(ls_y_corr) < hall_ls_deadzone
577+
)
578+
579+
rs_x_corr = _clamp_axis(rs_x_now - hall_rs_idle_x * rs_idle_gain)
580+
rs_y_corr = _clamp_axis(rs_y_now)
581+
rs_in_deadzone = (
582+
abs(rs_x_corr) < hall_rs_deadzone
583+
and abs(rs_y_corr) < hall_rs_deadzone
584+
)
585+
586+
if hall_remap_deadzone and not ls_in_deadzone:
587+
ls_x_corr = _clamp_axis(_remap_deadzone(ls_x_corr, hall_ls_deadzone))
588+
ls_y_corr = _clamp_axis(_remap_deadzone(ls_y_corr, hall_ls_deadzone))
589+
590+
if hall_remap_deadzone and not rs_in_deadzone:
591+
rs_x_corr = _clamp_axis(_remap_deadzone(rs_x_corr, hall_rs_deadzone))
592+
rs_y_corr = _clamp_axis(_remap_deadzone(rs_y_corr, hall_rs_deadzone))
593+
594+
for ev in evs:
595+
if ev["type"] != "axis":
596+
continue
597+
if ev["code"] == "ls_x":
598+
ev["value"] = 0.0 if ls_in_deadzone else ls_x_corr
599+
elif ev["code"] == "ls_y":
600+
ev["value"] = 0.0 if ls_in_deadzone else ls_y_corr
601+
elif ev["code"] == "rs_x":
602+
ev["value"] = 0.0 if rs_in_deadzone else rs_x_corr
603+
elif ev["code"] == "rs_y":
604+
ev["value"] = 0.0 if rs_in_deadzone else rs_y_corr
605+
606+
# Keep stick axes consistent when only one axis was reported this frame,
607+
# and propagate trigger-driven LS correction even if sticks were unchanged.
608+
if rt_seen or ls_x_seen or ls_y_seen:
609+
if not ls_x_seen:
610+
evs.append(
611+
{
612+
"type": "axis",
613+
"code": "ls_x",
614+
"value": 0.0 if ls_in_deadzone else ls_x_corr,
615+
}
616+
)
617+
if not ls_y_seen:
618+
evs.append(
619+
{
620+
"type": "axis",
621+
"code": "ls_y",
622+
"value": 0.0 if ls_in_deadzone else ls_y_corr,
623+
}
624+
)
625+
626+
if rs_x_seen or rs_y_seen:
627+
if not rs_x_seen:
628+
evs.append(
629+
{
630+
"type": "axis",
631+
"code": "rs_x",
632+
"value": 0.0 if rs_in_deadzone else rs_x_corr,
633+
}
634+
)
635+
if not rs_y_seen:
636+
evs.append(
637+
{
638+
"type": "axis",
639+
"code": "rs_y",
640+
"value": 0.0 if rs_in_deadzone else rs_y_corr,
641+
}
642+
)
643+
477644
evs = multiplexer.process(evs)
478645
if evs:
479646
if debug:

src/hhd/device/rog_ally/controllers.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,5 @@ children:
7575
tags: [advanced]
7676
title: RGB During Charging Asleep
7777
default: False
78+
79+
hall_interference:

src/hhd/plugins/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from .conf import Config
22
from .inputs import gen_gyro_state, get_gyro_config, get_gyro_state, get_touchpad_config
33
from .outputs import (
4+
fix_hall_interference,
45
fix_limits,
6+
get_hall_interference,
7+
get_hall_interference_config,
58
get_limits,
69
get_limits_config,
710
get_outputs,
@@ -39,7 +42,10 @@
3942
"HHDLocale",
4043
"HHDLocaleRegister",
4144
"get_limits_config",
45+
"get_hall_interference_config",
4246
"get_limits",
47+
"get_hall_interference",
4348
"get_gid",
4449
"fix_limits",
50+
"fix_hall_interference",
4551
]
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
type: mode
2+
tags: [non-essential]
3+
title: Hall Effect Interference Correction
4+
hint: >-
5+
Compensates left stick Hall trigger interference and stick idle drift.
6+
7+
default: disabled
8+
modes:
9+
disabled:
10+
type: container
11+
title: Disabled
12+
hint: >-
13+
Disables Hall interference correction.
14+
default:
15+
type: container
16+
title: Default
17+
hint: >-
18+
Uses built-in correction coefficients and deadzone.
19+
manual:
20+
type: container
21+
title: Manual
22+
hint: >-
23+
Set LS/RS idle offsets and LT-to-LS correction manually.
24+
children:
25+
ls_idle_x:
26+
type: float
27+
title: Left Stick Idle X
28+
hint: >-
29+
Constant offset removed from left stick X around center.
30+
min: -0.5
31+
max: 0.5
32+
smin: -500
33+
smax: 500
34+
step: 0.01
35+
36+
lt_corr_x:
37+
type: float
38+
title: LT Correction For LS X
39+
hint: >-
40+
Additional correction applied to left stick X as LT is pressed.
41+
Uses RT axis input internally on devices where LT/RT are reversed.
42+
min: -0.5
43+
max: 0.5
44+
smin: -500
45+
smax: 500
46+
step: 0.01
47+
48+
rs_idle_x:
49+
type: float
50+
title: Right Stick Idle X
51+
hint: >-
52+
Constant offset removed from right stick X around center.
53+
min: -0.5
54+
max: 0.5
55+
smin: -500
56+
smax: 500
57+
step: 0.01
58+
59+
ls_deadzone:
60+
type: float
61+
title: Left Stick Deadzone
62+
hint: >-
63+
Values below this absolute magnitude are clamped to zero on left stick X/Y.
64+
min: 0
65+
max: 0.3
66+
smin: 0
67+
smax: 300
68+
step: 0.01
69+
70+
rs_deadzone:
71+
type: float
72+
title: Right Stick Deadzone
73+
hint: >-
74+
Values below this absolute magnitude are clamped to zero on right stick X.
75+
min: 0
76+
max: 0.3
77+
smin: 0
78+
smax: 300
79+
step: 0.01
80+
81+
remap_deadzone:
82+
type: bool
83+
title: Remove Deadzone From Scale
84+
hint: >-
85+
Rescales outputs outside deadzone to keep full range.
86+
Uses (value - deadzone) / (1 - deadzone) with sign.
87+
88+
reset:
89+
type: action
90+
title: Reset to Default
91+
hint: >-
92+
Reset manual Hall interference values to default.

0 commit comments

Comments
 (0)