Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/hhd/device/rog_ally/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
Emitter,
Event,
HHDPlugin,
fix_hall_interference,
load_relative_yaml,
get_hall_interference_config,
get_outputs_config,
get_limits_config,
fix_limits,
Expand Down Expand Up @@ -40,7 +42,7 @@ def open(
self.prev = None

def settings(self) -> HHDSettings:
from .base import LIMIT_DEFAULTS
from .base import HALL_DEFAULTS, LIMIT_DEFAULTS

base = {"controllers": {"rog_ally": load_relative_yaml("controllers.yml")}}
base["controllers"]["rog_ally"]["children"]["controller_mode"].update(
Expand All @@ -51,16 +53,24 @@ def settings(self) -> HHDSettings:
base["controllers"]["rog_ally"]["children"]["limits"] = get_limits_config(
LIMIT_DEFAULTS(self.ally_x)
)
base["controllers"]["rog_ally"]["children"][
"hall_interference"
] = get_hall_interference_config(HALL_DEFAULTS)

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

return base

def update(self, conf: Config):
from .base import LIMIT_DEFAULTS
from .base import HALL_DEFAULTS, LIMIT_DEFAULTS

fix_limits(conf, "controllers.rog_ally.limits", LIMIT_DEFAULTS(self.ally_x))
fix_hall_interference(
conf,
"controllers.rog_ally.hall_interference",
HALL_DEFAULTS,
)

new_conf = conf["controllers.rog_ally"]
if new_conf == self.prev:
Expand Down
169 changes: 168 additions & 1 deletion src/hhd/device/rog_ally/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import select
import time
from threading import Event as TEvent
from typing import Sequence, Literal
from typing import Sequence

from hhd.controller import DEBUG_MODE, Axis, Event, Multiplexer, can_read
from hhd.controller.lib.hide import unhide_all
Expand Down Expand Up @@ -123,6 +123,36 @@
)


def _clamp_axis(v: float) -> float:
return max(-1.0, min(1.0, v))


def _edge_gain_center(x: float) -> float:
# Full effect near center, smoothly fades near extremes.
g = max(0.0, 1.0 - abs(x))
return g * g


def _remap_deadzone(v: float, d: float) -> float:
av = abs(v)
if av <= d:
return 0.0
if d >= 1.0:
return 0.0
out = (av - d) / (1.0 - d)
return -out if v < 0 else out


HALL_DEFAULTS = {
"ls_idle_x": -0.15,
"lt_corr_x": 0.28,
"rs_idle_x": 0.10,
"ls_deadzone": 0.15,
"rs_deadzone": 0.05,
"remap_deadzone": True,
}


class AllyHidraw(GenericGamepadHidraw):
def __init__(self, *args, kconf={}, rgb_boot, rgb_charging, **kwargs) -> None:
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -339,6 +369,40 @@ def controller_loop(
swap_armoury = conf.get("swap_armory", False)
swap_xbox = conf.get("swap_xbox", False) and xbox

hall_mode = conf.get("hall_interference.mode", "default")
hall_enabled = hall_mode != "disabled"
if hall_mode == "manual":
hall_ls_idle_x = conf.get(
"hall_interference.manual.ls_idle_x", HALL_DEFAULTS["ls_idle_x"]
)
hall_lt_corr_x = conf.get(
"hall_interference.manual.lt_corr_x", HALL_DEFAULTS["lt_corr_x"]
)
hall_rs_idle_x = conf.get(
"hall_interference.manual.rs_idle_x", HALL_DEFAULTS["rs_idle_x"]
)
hall_ls_deadzone = conf.get(
"hall_interference.manual.ls_deadzone", HALL_DEFAULTS["ls_deadzone"]
)
hall_rs_deadzone = conf.get(
"hall_interference.manual.rs_deadzone", HALL_DEFAULTS["rs_deadzone"]
)
hall_remap_deadzone = conf.get(
"hall_interference.manual.remap_deadzone",
bool(HALL_DEFAULTS["remap_deadzone"]),
)
else:
hall_ls_idle_x = HALL_DEFAULTS["ls_idle_x"]
hall_lt_corr_x = HALL_DEFAULTS["lt_corr_x"]
hall_rs_idle_x = HALL_DEFAULTS["rs_idle_x"]
hall_ls_deadzone = HALL_DEFAULTS["ls_deadzone"]
hall_rs_deadzone = HALL_DEFAULTS["rs_deadzone"]
hall_remap_deadzone = bool(HALL_DEFAULTS["remap_deadzone"])

rt_now = 0.0
ls_y_now = 0.0
rs_y_now = 0.0

# Imu
d_imu = CombinedImu(conf["imu_hz"].to(int), ALLY_MAPPINGS, gyro_scale="0.000266")
d_timer = HrtimerTrigger(conf["imu_hz"].to(int), [HrtimerTrigger.IMU_NAMES])
Expand Down Expand Up @@ -474,6 +538,109 @@ def prepare(m):
evs.extend(d.produce(r))
evs.extend(d_vend.produce(r))

if hall_enabled:
ls_x_seen = False
ls_y_seen = False
rs_x_seen = False
rs_y_seen = False
rt_seen = False
for ev in evs:
if ev["type"] != "axis":
continue
elif ev["code"] == "rt":
rt_now = max(0.0, float(ev["value"]))
rt_seen = True
elif ev["code"] == "ls_x":
ls_x_now = float(ev["value"])
ls_x_seen = True
elif ev["code"] == "ls_y":
ls_y_now = float(ev["value"])
ls_y_seen = True
elif ev["code"] == "rs_x":
rs_x_now = float(ev["value"])
rs_x_seen = True
elif ev["code"] == "rs_y":
rs_y_now = float(ev["value"])
rs_y_seen = True

trigger_corr_x = hall_lt_corr_x * rt_now
ls_idle_gain = _edge_gain_center(ls_x_now)
rs_idle_gain = _edge_gain_center(rs_x_now)

ls_x_corr = _clamp_axis(
ls_x_now - hall_ls_idle_x * ls_idle_gain + trigger_corr_x
)
ls_y_corr = _clamp_axis(ls_y_now)
ls_in_deadzone = (
abs(ls_x_corr) < hall_ls_deadzone
and abs(ls_y_corr) < hall_ls_deadzone
)

rs_x_corr = _clamp_axis(rs_x_now - hall_rs_idle_x * rs_idle_gain)
rs_y_corr = _clamp_axis(rs_y_now)
rs_in_deadzone = (
abs(rs_x_corr) < hall_rs_deadzone
and abs(rs_y_corr) < hall_rs_deadzone
)

if hall_remap_deadzone and not ls_in_deadzone:
ls_x_corr = _clamp_axis(_remap_deadzone(ls_x_corr, hall_ls_deadzone))
ls_y_corr = _clamp_axis(_remap_deadzone(ls_y_corr, hall_ls_deadzone))

if hall_remap_deadzone and not rs_in_deadzone:
rs_x_corr = _clamp_axis(_remap_deadzone(rs_x_corr, hall_rs_deadzone))
rs_y_corr = _clamp_axis(_remap_deadzone(rs_y_corr, hall_rs_deadzone))

for ev in evs:
if ev["type"] != "axis":
continue
if ev["code"] == "ls_x":
ev["value"] = 0.0 if ls_in_deadzone else ls_x_corr
elif ev["code"] == "ls_y":
ev["value"] = 0.0 if ls_in_deadzone else ls_y_corr
elif ev["code"] == "rs_x":
ev["value"] = 0.0 if rs_in_deadzone else rs_x_corr
elif ev["code"] == "rs_y":
ev["value"] = 0.0 if rs_in_deadzone else rs_y_corr

# Keep stick axes consistent when only one axis was reported this frame,
# and propagate trigger-driven LS correction even if sticks were unchanged.
if rt_seen or ls_x_seen or ls_y_seen:
if not ls_x_seen:
evs.append(
{
"type": "axis",
"code": "ls_x",
"value": 0.0 if ls_in_deadzone else ls_x_corr,
}
)
if not ls_y_seen:
evs.append(
{
"type": "axis",
"code": "ls_y",
"value": 0.0 if ls_in_deadzone else ls_y_corr,
}
)

if rs_x_seen or rs_y_seen:
if not rs_x_seen:
evs.append(
{
"type": "axis",
"code": "rs_x",
"value": 0.0 if rs_in_deadzone else rs_x_corr,
}
)
if not rs_y_seen:
evs.append(
{
"type": "axis",
"code": "rs_y",
"value": 0.0 if rs_in_deadzone else rs_y_corr,
}
)

evs = multiplexer.process(evs)
if evs:
if debug:
Expand Down
2 changes: 2 additions & 0 deletions src/hhd/device/rog_ally/controllers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,5 @@ children:
tags: [advanced]
title: RGB During Charging Asleep
default: False

hall_interference:
6 changes: 6 additions & 0 deletions src/hhd/plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from .conf import Config
from .inputs import gen_gyro_state, get_gyro_config, get_gyro_state, get_touchpad_config
from .outputs import (
fix_hall_interference,
fix_limits,
get_hall_interference,
get_hall_interference_config,
get_limits,
get_limits_config,
get_outputs,
Expand Down Expand Up @@ -39,7 +42,10 @@
"HHDLocale",
"HHDLocaleRegister",
"get_limits_config",
"get_hall_interference_config",
"get_limits",
"get_hall_interference",
"get_gid",
"fix_limits",
"fix_hall_interference",
]
92 changes: 92 additions & 0 deletions src/hhd/plugins/hall_interference.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
type: mode
tags: [non-essential]
title: Hall Effect Interference Correction
hint: >-
Compensates left stick Hall trigger interference and stick idle drift.

default: disabled
modes:
disabled:
type: container
title: Disabled
hint: >-
Disables Hall interference correction.
default:
type: container
title: Default
hint: >-
Uses built-in correction coefficients and deadzone.
manual:
type: container
title: Manual
hint: >-
Set LS/RS idle offsets and LT-to-LS correction manually.
children:
ls_idle_x:
type: float
title: Left Stick Idle X
hint: >-
Constant offset removed from left stick X around center.
min: -0.5
max: 0.5
smin: -500
smax: 500
step: 0.01

lt_corr_x:
type: float
title: LT Correction For LS X
hint: >-
Additional correction applied to left stick X as LT is pressed.
Uses RT axis input internally on devices where LT/RT are reversed.
min: -0.5
max: 0.5
smin: -500
smax: 500
step: 0.01

rs_idle_x:
type: float
title: Right Stick Idle X
hint: >-
Constant offset removed from right stick X around center.
min: -0.5
max: 0.5
smin: -500
smax: 500
step: 0.01

ls_deadzone:
type: float
title: Left Stick Deadzone
hint: >-
Values below this absolute magnitude are clamped to zero on left stick X/Y.
min: 0
max: 0.3
smin: 0
smax: 300
step: 0.01

rs_deadzone:
type: float
title: Right Stick Deadzone
hint: >-
Values below this absolute magnitude are clamped to zero on right stick X.
min: 0
max: 0.3
smin: 0
smax: 300
step: 0.01

remap_deadzone:
type: bool
title: Remove Deadzone From Scale
hint: >-
Rescales outputs outside deadzone to keep full range.
Uses (value - deadzone) / (1 - deadzone) with sign.

reset:
type: action
title: Reset to Default
hint: >-
Reset manual Hall interference values to default.
Loading