|
2 | 2 | import select |
3 | 3 | import time |
4 | 4 | from threading import Event as TEvent |
5 | | -from typing import Sequence, Literal |
| 5 | +from typing import Sequence |
6 | 6 |
|
7 | 7 | from hhd.controller import DEBUG_MODE, Axis, Event, Multiplexer, can_read |
8 | 8 | from hhd.controller.lib.hide import unhide_all |
|
123 | 123 | ) |
124 | 124 |
|
125 | 125 |
|
| 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 | + |
126 | 156 | class AllyHidraw(GenericGamepadHidraw): |
127 | 157 | def __init__(self, *args, kconf={}, rgb_boot, rgb_charging, **kwargs) -> None: |
128 | 158 | super().__init__(*args, **kwargs) |
@@ -339,6 +369,40 @@ def controller_loop( |
339 | 369 | swap_armoury = conf.get("swap_armory", False) |
340 | 370 | swap_xbox = conf.get("swap_xbox", False) and xbox |
341 | 371 |
|
| 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 | + |
342 | 406 | # Imu |
343 | 407 | d_imu = CombinedImu(conf["imu_hz"].to(int), ALLY_MAPPINGS, gyro_scale="0.000266") |
344 | 408 | d_timer = HrtimerTrigger(conf["imu_hz"].to(int), [HrtimerTrigger.IMU_NAMES]) |
@@ -474,6 +538,109 @@ def prepare(m): |
474 | 538 | evs.extend(d.produce(r)) |
475 | 539 | evs.extend(d_vend.produce(r)) |
476 | 540 |
|
| 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 | + |
477 | 644 | evs = multiplexer.process(evs) |
478 | 645 | if evs: |
479 | 646 | if debug: |
|
0 commit comments