Návod - Jak přidat ovladač/joystick open rails

Ondras8_CZ

Cestující
Ahoj všem,
mám joystick thrustmaster hotas x a podařilo se mi ho rozchodit na open rails, včetně plynulé regulace výkonu.
Koho nezajímá backstory, nechť pokračuje na další odstavec.

Přišla mi škoda, že mám doma thrustmaster hotas x a nelze ho využít pro ovládání open rails. Procházel jsem fóra, googlil, ptal se AI ale řešení nenalezeno. Pouze program Joy2Key, kterým se ale nedá ovládat výkon, jelikož hodnoty předává jako klávesy. Musel jsem si tedy poradit sám. Po chvíli bádání jsem zjistil, že při spuštění open rails se spustí lokální server http://localhost:2150/ kde je mimo jiné možné ovládat soupravu přes webové rozhraní. Zjistil jsem, že to jsou jen jednoduché HTTP post požadavky na API openrails. Shodou náhod firmware od thrustmaster umí ukládat log z joysticku do textového souboru ve fortmátu osa - hodnota - čas. Vytvořil jsem tedy za pomocí AI (jelikož nejsem programátor, umím jen základy) první verzi, která četla textový soubor, ten poté převedla na správné hodoty co OR očekává a světe div se - mohl jsem lokomotivu ovládat přes joystick, včetně plynulého ovládání.

Ale je zde problém limitace na jednoho výrobce, takže po mnoha hodinách trápení ( a minus jedné klávesnici) je na světe program, který detekuje připojené joysticky (i jiných výrobců), data průběžně čte a odesílá jako POST na API open rails. Ne vše je však možné přes api, proto API využívám jen na regulaci výkonu, vlakové brzdy a rekuperace.

Pro běh je nutné mít na PC nainstalovaný Python (včetně PATH), poté nainstalovat pygame requests a psutil a program joytokey https://joytokey.net/en/
Po spuštění program zkontroluje, jestli běží joytokey, a pokud ne, spustí ho. Jelikož třeba houkačka v Olomoucké T3 šla, ale ne na ELL 193. To samé pantografy. Proto mám na zbytek kláves na joysticku nastavení přes joytokey
Před spuštěním kódu je nutné mít načtenou mapu v openrails!
Poté program průběžně čte hodnoty z os a posílá je na server.

Ještě zasílám krátké video jako ukázku Video na Youtube

Kdo by měl zájem, může kód níže využít, ale bez jakékoliv záruky a podpory.

Python:
import pygame
import requests
import time
import psutil
import subprocess
from datetime import datetime  # Import pro časové razítko

# Konfigurace
SERVER_URL = 'http://localhost:2150/API/CABCONTROLS'  # URL serveru s portem 2150
POLL_INTERVAL = 0.2  # Interval mezi jednotlivými kontrolami (v sekundách)
WAIT_TIME = 0.05  # Čas čekání mezi jednotlivými instrukcemi (v sekundách)
JOY2KEY_PATH = r"C:\Program Files (x86)\JoyToKey\JoyToKey.exe"  # Cesta k programu JoyToKey

# Mappings
KEY_MAPPINGS = {
    'Button4': 'dynamic_brake',
    'Slider0': 'dynamic_brake',
    'Z': 'throttle_train_brake'
}

# Dynamické proměnné s počátečními hodnotami
previous_values = {
    'throttle': None,
    'train_brake': None,
    'dynamic_brake': None,
}

# Funkce pro spuštění JoyToKey, pokud neběží
def check_and_start_joytokey():
    joytokey_process_name = "JoyToKey.exe"
    is_running = any(proc.name() == joytokey_process_name for proc in psutil.process_iter())

    if not is_running:
        subprocess.Popen([JOY2KEY_PATH])
        print(f"{joytokey_process_name} nebyl spuštěn. Spouštím...")

def process_joystick_data(joystick):
    global previous_values

    # Načtení hodnot os
    x_axis = joystick.get_axis(0)
    y_axis = joystick.get_axis(1)
    z_axis = joystick.get_axis(2)
    rz_axis = joystick.get_axis(3)
    slider0 = joystick.get_axis(4)
    button_states = [joystick.get_button(i) for i in range(12)]  # Předpokládáme, že joystick má 12 tlačítek

    # Inicializace změn
    throttle_value, brake_value = None, None
    dynamic_brake = None

    # Zpracování tlačítek
    for i, state in enumerate(button_states):
        if state:
            button_name = f'Button{i+1}'
            action = KEY_MAPPINGS.get(button_name)
            if action == 'dynamic_brake':
                dynamic_brake = previous_values['dynamic_brake']

    # Zpracování os
    if z_axis is not None:
        # THROTTLE a TRAIN_BRAKE výpočty
        if z_axis <= 0:
            # Invertování hodnoty pro THROTTLE, pokud je v rozmezí -1.0 až 0.0
            throttle_value = round(-z_axis, 3)
            throttle_value = min(max(throttle_value, 0.0), 1.0)  # Ošetření rozsahu 0.0 až 1.0
        else:
            throttle_value = None

        if z_axis >= 0:
            # Ošetření hodnoty pro TRAIN_BRAKE, pokud je v rozmezí 0.0 až 1.0
            brake_value = round(z_axis, 3)
            brake_value = min(max(brake_value, 0.0), 1.0)  # Ošetření rozsahu 0.0 až 1.0
        else:
            brake_value = None

    if slider0 is not None:
        # Zpracování slider0
        if slider0 <= -0.9:
            # Pokud je hodnota -1 až -0.9, odečítej po 0.1
            dynamic_brake = round(max(0.0, (previous_values['dynamic_brake'] - 0.1)), 3) if previous_values['dynamic_brake'] is not None else 0.0
        elif -0.9 < slider0 <= -0.08:
            # Pokud je hodnota -0.9 až -0.08 (mimo), odečítej po 0.05
            dynamic_brake = round(max(0.0, (previous_values['dynamic_brake'] - 0.05)), 3) if previous_values['dynamic_brake'] is not None else 0.0
        elif 0.08 < slider0 < 0.9:
            # Pokud je hodnota 0.08 až 0.9 (mimo), přičítej po 0.05
            dynamic_brake = round(min(1.0, (previous_values['dynamic_brake'] + 0.05)), 3) if previous_values['dynamic_brake'] is not None else 0.05
        elif slider0 >= 0.9:
            # Pokud je hodnota 0.9 až 1.0, přičítej po 0.1
            dynamic_brake = round(min(1.0, (previous_values['dynamic_brake'] + 0.1)), 3) if previous_values['dynamic_brake'] is not None else 0.1
        else:
            dynamic_brake = previous_values['dynamic_brake'] if previous_values['dynamic_brake'] is not None else 0.0

    # Porovnání a uložení změn
    changes = {}
    for key, value in {
        'throttle': throttle_value,
        'train_brake': brake_value,
        'dynamic_brake': dynamic_brake,
    }.items():
        if value is not None and value != previous_values[key]:
            changes[key] = value
            previous_values[key] = value

    return changes

def send_data_to_server(changes):
    if not changes:
        return

    json_data = []
    for key, value in changes.items():
        type_name = {
            'throttle': 'THROTTLE',
            'train_brake': 'TRAIN_BRAKE',
            'dynamic_brake': 'DYNAMIC_BRAKE'
        }[key]
        json_data.append({"TypeName": type_name, "Value": value})

    if json_data:
        try:
            response = requests.post(SERVER_URL, json=json_data)
            current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            print(f"[{current_time}] Odesláno: {json_data}, Odezva: {response.status_code}, Text: {response.text}")
        except requests.RequestException as e:
            print(f"Chyba při odesílání dat na server: {e}")

def main():
    pygame.init()
    pygame.joystick.init()

    if pygame.joystick.get_count() == 0:
        print("No joystick detected. Please connect a joystick and restart the program.")
        return

    joystick = pygame.joystick.Joystick(0)
    joystick.init()
    print("Joystick initialized: " + joystick.get_name())

    check_and_start_joytokey()

    try:
        while True:
            pygame.event.pump()  # Zpracování událostí (aby se joystick aktualizoval)

            changes = process_joystick_data(joystick)

            send_data_to_server(changes)

            time.sleep(POLL_INTERVAL)
    
    except KeyboardInterrupt:
        print("Program byl ukončen uživatelem.")
    finally:
        pygame.quit()

if __name__ == "__main__":
    main()
 
Použitelné to bude asi jen na ofiko verzi OR. Nikoliv na OR CZ SK.
Tyhle vychytávky totiž nejsou na poslední verzi OR CZ SK.
 
Jo, tak já ani nevěděl, že nějaká CZ/SK verze je Jel jsem na ofiko a chyběla mi ta podpora, tak jsem si jí udělal
 
Back
Nahoře