Skip to content

DS5 Controller fixes - Gyro data and value scaling - #151

Merged
hgaiser merged 5 commits into
hgaiser:mainfrom
maugsburger:bugfix/ds5
Aug 5, 2026
Merged

DS5 Controller fixes - Gyro data and value scaling#151
hgaiser merged 5 commits into
hgaiser:mainfrom
maugsburger:bugfix/ds5

Conversation

@maugsburger

Copy link
Copy Markdown
Contributor

Bug: (Nearly) no gyro data arrived on the streaming host.

Confirmed by evtest:

Client:

[…]
Event: time 1785393423.028041, -------------- SYN_REPORT ------------
Event: time 1785393423.029038, type 3 (EV_ABS), code 3 (ABS_RX), value -253
Event: time 1785393423.029038, type 3 (EV_ABS), code 4 (ABS_RY), value -316
Event: time 1785393423.029038, type 3 (EV_ABS), code 5 (ABS_RZ), value 0
Event: time 1785393423.029038, type 3 (EV_ABS), code 0 (ABS_X), value -114
Event: time 1785393423.029038, type 3 (EV_ABS), code 1 (ABS_Y), value 8210
Event: time 1785393423.029038, type 3 (EV_ABS), code 2 (ABS_Z), value 1855
Event: time 1785393423.029038, type 4 (EV_MSC), code 5 (MSC_TIMESTAMP), value 60965606
Event: time 1785393423.029038, -------------- SYN_REPORT ------------
Event: time 1785393423.032042, type 3 (EV_ABS), code 3 (ABS_RX), value -126
Event: time 1785393423.032042, type 3 (EV_ABS), code 4 (ABS_RY), value -380
Event: time 1785393423.032042, type 3 (EV_ABS), code 0 (ABS_X), value -171
Event: time 1785393423.032042, type 3 (EV_ABS), code 1 (ABS_Y), value 8289
Event: time 1785393423.032042, type 3 (EV_ABS), code 2 (ABS_Z), value 1852
Event: time 1785393423.032042, type 4 (EV_MSC), code 5 (MSC_TIMESTAMP), value 60966856
Event: time 1785393423.032042, -------------- SYN_REPORT ------------
[…]

Server - mostly empty reports, but sometimes a bit of data comes through.:

[…]
Event: time 1785393488.591024, -------------- SYN_REPORT ------------
Event: time 1785393488.591027, type 4 (EV_MSC), code 5 (MSC_TIMESTAMP), value 396066201
Event: time 1785393488.591027, -------------- SYN_REPORT ------------
Event: time 1785393488.591030, type 4 (EV_MSC), code 5 (MSC_TIMESTAMP), value 396066204
Event: time 1785393488.591030, -------------- SYN_REPORT ------------
Event: time 1785393488.598028, type 4 (EV_MSC), code 5 (MSC_TIMESTAMP), value 396073209
[…]
❯ evtest /dev/input/event23 | grep ABS_ | tail -n 20
[…]
Event: time 1785393501.894532, type 3 (EV_ABS), code 3 (ABS_RX), value 0
Event: time 1785393501.894532, type 3 (EV_ABS), code 4 (ABS_RY), value 0
Event: time 1785393501.894532, type 3 (EV_ABS), code 5 (ABS_RZ), value 0
Event: time 1785393516.798814, type 3 (EV_ABS), code 0 (ABS_X), value 5342
Event: time 1785393516.798814, type 3 (EV_ABS), code 1 (ABS_Y), value 4204
Event: time 1785393516.798814, type 3 (EV_ABS), code 2 (ABS_Z), value -2986
Event: time 1785393516.810547, type 3 (EV_ABS), code 3 (ABS_RX), value 12851
Event: time 1785393516.810547, type 3 (EV_ABS), code 4 (ABS_RY), value 79104
Event: time 1785393516.810547, type 3 (EV_ABS), code 5 (ABS_RZ), value 24627
Event: time 1785393516.810553, type 3 (EV_ABS), code 0 (ABS_X), value 5511
Event: time 1785393516.810553, type 3 (EV_ABS), code 1 (ABS_Y), value 4154
Event: time 1785393516.810553, type 3 (EV_ABS), code 2 (ABS_Z), value -2829

Problem turned out to be different ENET_PACKET_FLAG_RELIABLE flags

// Send each controller on a separate channel
holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber;

// TODO: Send this as unreliable sequenced when we have a delayed reliable retransmission thread
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;

and a motion (gyro/accel) packet to CTRL_CHANNEL_SENSOR_BASE + controllerNumber (line 1569) — sent unreliable, since it's fine to drop a stale gyro sample:

// Send each controller on a separate channel specific to motion sensors
holder->channelId = CTRL_CHANNEL_SENSOR_BASE + controllerNumber;

and ENet therefore resetting the counter in enet_peer_setup_outgoing_command (enet/peer.c:636-672):

void
enet_peer_setup_outgoing_command (ENetPeer * peer, ENetOutgoingCommand * outgoingCommand)
{
    ...
    else
    {
        ENetChannel * channel = & peer -> channels [outgoingCommand -> command.header.channelID];

        if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
        {
           ++ channel -> outgoingReliableSequenceNumber;
           channel -> outgoingUnreliableSequenceNumber = 0;    // <-- reset here

           outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber;
           outgoingCommand -> unreliableSequenceNumber = 0;
        }
        else
        {
           if (outgoingCommand -> fragmentOffset == 0)
             ++ channel -> outgoingUnreliableSequenceNumber;    // normal unreliable increment

           outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber;
           outgoingCommand -> unreliableSequenceNumber = channel -> outgoingUnreliableSequenceNumber;
        }
    }

but then tokio-enet in moonshine discards the "too old" packets as stale:
moonshine's ENet reimplementation, handle_send_unreliable (tokio-enet-0.1.1/src/host.rs:1178-1207):

fn handle_send_unreliable(
    &mut self,
    peer_idx: usize,
    cmd_header: &CommandHeader,
    unreliable_seq: u16,
    data: Vec<u8>,
) -> Result<(), Error> {
    ...
    let channel = &mut peer.channels[channel_id as usize];

    // Drop packets that are older than what we've already received.
    if unreliable_seq.wrapping_sub(channel.incoming_unreliable_sequence_number) > 0x7FFF {
        return Ok(());
    }

    channel.incoming_unreliable_sequence_number = unreliable_seq;
    ...
}

By giving every input channel its own enet channel we no longer have this problem with discarded packets.


The removal of to_radians was merely a random find when comparing the data types.

https://github.com/moonlight-stream/moonlight-common-c/blob/e41355ea01670fd4c830b384009d31dd0339a705/src/Limelight.h#L810

```c
// LI_MOTION_TYPE_ACCEL should report data in m/s^2 (inclusive of gravitational acceleration).
// LI_MOTION_TYPE_GYRO should report data in deg/s.
```

https://github.com/games-on-whales/inputtino/blob/f4ce2b0df536ef309e9ff318f75b460f7097d7c1/include/inputtino/input.hpp#L410-L412

```c
  /**
   * Acceleration should report data in m/s^2 (inclusive of gravitational acceleration).
   * Gyroscope should report data in deg/s.
   *
   * The x/y/z axis assignments follow SDL's convention documented here:
   * https://github.com/libsdl-org/SDL/blob/96720f335002bef62115e39327940df454d78f6c/include/SDL3/SDL_sensor.h#L80-L124
   */
```

Moonlight delivers and inputtiino expects m/s^2 and deg/s – we don't need any conversion here.
Match the Channel Count with what moonlight-common-c uses:

https://github.com/moonlight-stream/moonlight-common-c/blob/e41355ea01670fd4c830b384009d31dd0339a705/src/Limelight-internal.h#L66

Enables a reliable gyro data stream for a ps5 dualsense controller.
@maugsburger
maugsburger marked this pull request as ready for review July 30, 2026 11:41
@maugsburger maugsburger changed the title DS5 Controller fixes - Gyrp data and value scaling DS5 Controller fixes - Gyro data and value scaling Jul 30, 2026
@hgaiser

hgaiser commented Aug 3, 2026

Copy link
Copy Markdown
Owner

I hadn't tried gyro in a while, thanks for looking into it. I tried gyro on main and it was indeed broken. I tried your fixes but the scaling seems off. I reverted your scaling to:

			gamepad.set_motion(
				motion.motion_type,
				motion.x.to_radians(),
				motion.y.to_radians(),
				motion.z.to_radians(),
			);

After that the gyro seems to behave identically compared to a DS5 connected directly to Steam. Why did you need to change the scaling?

@maugsburger

Copy link
Copy Markdown
Contributor Author

After that the gyro seems to behave identically compared to a DS5 connected directly to Steam. Why did you need to change the scaling?

As outlined in fa728e1 the comments in both libraries suggest they already work on the same units. TBH, never verified it end-to-end after I got it finally working.

@hgaiser

hgaiser commented Aug 5, 2026

Copy link
Copy Markdown
Owner

I checked and restored the original gyro scaling. They seemed to work for me and it also seems to be what wolf does.

Thanks!

@hgaiser
hgaiser merged commit 2aa01e4 into hgaiser:main Aug 5, 2026
5 checks passed
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