Previous Work Link to heading

This is a continuation of the work I developed previously in Rust UDP packet receiver & Handler

Introduction and Motivation Link to heading

As I stated in my previous post, I needed a new interface between EASportsWRC and my new revamped homemade rally sim shifter. The shifter connects to the computer via a USB interface and is defined as a HID device, The rust program receives game information via UDP and then sends it out via USB. The USB bandwidth is more limited than the UDP packets so I had to reduce the signals received from UDP, which also made sense because for the moment Im only interested in a single signal.

Concept Link to heading

Overall the target is simple, receive data from UDP, chose what I need, send it as a HID message.

GaEEmAAeSSppoorrtRtsasWwWRRCPCacket||RustUUDDCPPliLLeiinsTsttetelGeneenemaererrtUrpyGHeHIaIDrDDIoInwntnteerSraeaclcteticiotonendGear|PhysicSSaiilmmRRaacciinnggHHuubb

Lets go through this sequence diagram. Firstly, EASportsWRC sends out a UPD packet containg the game telemetry (I have gone into more detail how this works in previous posts) In my rust client, I run two important threads:

UDP Listener -> start_udp_listener(tx: mpsc::Sender<TelemetryData>) Link to heading

HID Interaction -> cyclic_hid_interaction(mut device: HidDevice, mut rx: mpsc::Receiver<TelemetryData>) Link to heading

The UDP listener starts a UDP socket and receives the buffer from the local port to which EASportsWRC produced the dataset. Using the sender side of a mpsc channel the parsed packet is sent out to the HID Interaction thread.

 1use hidapi::{HidApi, HidDevice};
 2use core::str;
 3use std::io::{self, Write};
 4use tokio::time::{sleep,Duration};
 5use tokio::{net::UdpSocket as AsyncUdpSocket, task, sync::mpsc};
 6
 7async fn start_udp_listener(tx: mpsc::Sender<TelemetryData>) -> io::Result<()> {
 8    //let mut addr = String::new();
 9    //println!("Please input the IP and the port:");
10    //io::stdin().read_line(&mut addr)?;
11    let mut addr = String::from("127.0.0.1:20782");
12
13    addr = addr.trim().to_string(); // Remove newline characters
14    let socket = AsyncUdpSocket::bind(&addr).await?;
15    println!("Listening on socket {}", addr);
16
17    let mut buf = [0u8; 1024];
18
19    loop {
20        match socket.recv_from(&mut buf).await {
21            Ok((size, _src)) => {
22                //println!("Received {} bytes from {}", size, src);
23                if let Ok(packet) = parse_packet(&buf[..size]) {
24                    // Send the packet to the HID task
25                    if tx.send(packet).await.is_err() {
26                        eprintln!("Failed to send packet to HID task");
27                    }
28                }
29            }
30            Err(e) => {
31                eprintln!("Error receiving data: {}", e);
32            }
33        }
34    }
35}

On the HID side theres two threads: the start_hid_listener, which is responsible to look for the correct HID device, and once it finds it to connect and spawn the thread that handles the HID communication cyclic_hid_interaction

 1
 2async fn cyclic_hid_interaction(mut device: HidDevice, mut rx: mpsc::Receiver<TelemetryData>) {
 3    println!("Connected to HID device. Sending UDP packets every 10ms.");
 4
 5    let mut last_packet = TelemetryData {
 6        packet_4cc: 0,
 7        packet_uid: 0,
 8        shiftlights_fraction: 0.0,
 9        shiftlights_rpm_start: 0.0,
10        shiftlights_rpm_end: 0.0,
11        shiftlights_rpm_valid: false,
12        vehicle_gear_index: 0,
13        vehicle_gear_index_neutral: 0,
14        vehicle_gear_index_reverse: 0,
15        vehicle_gear_maximum: 0,
16        vehicle_speed: 0.0,
17        vehicle_transmission_speed: 0.0,
18        vehicle_position_x: 0.0,
19        vehicle_position_y: 0.0,
20        vehicle_position_z: 0.0,
21        vehicle_velocity_x: 0.0,
22        vehicle_velocity_y: 0.0,
23        vehicle_velocity_z: 0.0,
24        vehicle_acceleration_x: 0.0,
25        vehicle_acceleration_y: 0.0,
26        vehicle_acceleration_z: 0.0,
27        vehicle_left_direction_x: 0.0,
28        vehicle_left_direction_y: 0.0,
29        vehicle_left_direction_z: 0.0,
30        vehicle_forward_direction_x: 0.0,
31        vehicle_forward_direction_y: 0.0,
32        vehicle_forward_direction_z: 0.0,
33        vehicle_up_direction_x: 0.0,
34        vehicle_up_direction_y: 0.0,
35        vehicle_up_direction_z: 0.0,
36        vehicle_hub_position_bl: 0.0,
37        vehicle_hub_position_br: 0.0,
38        vehicle_hub_position_fl: 0.0,
39        vehicle_hub_position_fr: 0.0,
40        vehicle_hub_velocity_bl: 0.0,
41        vehicle_hub_velocity_br: 0.0,
42        vehicle_hub_velocity_fl: 0.0,
43        vehicle_hub_velocity_fr: 0.0,
44        vehicle_cp_forward_speed_bl: 0.0,
45        vehicle_cp_forward_speed_br: 0.0,
46        vehicle_cp_forward_speed_fl: 0.0,
47        vehicle_cp_forward_speed_fr: 0.0,
48        vehicle_brake_temperature_bl: 0.0,
49        vehicle_brake_temperature_br: 0.0,
50        vehicle_brake_temperature_fl: 0.0,
51        vehicle_brake_temperature_fr: 0.0,
52        vehicle_engine_rpm_max: 0.0,
53        vehicle_engine_rpm_idle: 0.0,
54        vehicle_engine_rpm_current: 0.0,
55        vehicle_throttle: 0.0,
56        vehicle_brake: 0.0,
57        vehicle_clutch: 0.0,
58        vehicle_steering: 0.0,
59        vehicle_handbrake: false,
60        game_total_time: 0.0,
61        game_delta_time: 0.0,
62        game_frame_count: 0,
63        stage_current_time: 0.0,
64        stage_current_distance: 0.0,
65        stage_length: 0.0,
66    };
67
68    loop {
69        // Check if there's a new packet from UDP
70        if let Ok(packet) = rx.try_recv() {
71            last_packet = packet; // Update last received packet
72        }
73        //TODO! - Reduce the packets sent by only sending when theres a change
74        // Prepare the message to send
75        let mut output: Vec<u8> = vec![0x00]; // Report ID = 0x00
76        output = create_hid_packet(&last_packet,1);
77        //output.extend_from_slice(&last_packet);//TODO: ve la o que fazes aqui
78        println!("Sent to HID Device: {:?}\n", &output);
79        // Send to HID device
80        if let Err(e) = device.write(&output) {
81            eprintln!("Failed to write to device: {}", e);
82        }
83        
84        // Read response
85        let mut buf = [0u8; 5];
86        match device.read(&mut buf) {
87            Ok(len) => {
88                println!("Received from HID Device: {:?}\n", &buf[..len]);
89            }
90            Err(e) => {
91                eprintln!("Failed to read from device: {}", e);
92            }
93        }
94
95        // Sleep asynchronously for 10ms
96        //sleep(Duration::from_millis(1)).await;
97    }
98}

In this current iteration the HID packet has 5 bytes where the first byte is considered the packet ID As this is still in development, only one packet existes PacketID:1, which has the following structure:

Byte 0Byte 1Byte 2Byte 3Byte 4
1Current GearUnusedUnusedUnused

The HID device is configured to send the received packet, so in the cyclic method we also expect that we receive the same packet that was sent out.

In the following posts I will go into detail on the embedded device software so keep on the lookout 😄. I will also probably create a project post detailing the entire project

If you like my projects please consider supporting my hobby by buying me a coffee☕ 😄