Programmfluss / Timing

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
djevil
User
Beiträge: 58
Registriert: Montag 22. August 2016, 14:28

Hallo,
Ich habe mir nach einiger Zeit mal wieder mein altes
"Xbox360 wireless Controller with chatpad" Script
vorgenommen und auf meinen neuen Rechner installiert.

Das Script hatte auch schonmal funktioniert, obwohl ich mir nicht sicher bin, ob es die selbe Version ist
(ja ich sollte mich mal mit Subversion o.ä. beschäftigen),
oder ich zwischenzeitig nochmal etwas geändert habe, jedenfalls habe ich wieder Probleme damit.

Ähnliche Probleme wie früher,
damals hat mir das chatpad 8 Tastendrücke pro Loop ausgegeben,
Jetzt aber gibt es mir nur noch einen, etwa alle 3sek. aus.

Also, Zählerschleife deaktiviert, mit dem selben Ergebnis.

Da ich jetzt schon einiges ausprobiert habe, gebe ich jetzt mal den (fast) kompletten Programmcode raus,

Vielleicht könnt ihr mir ja helfen,
oder vielleicht hat jemand anderes Lust den Code weiter zu schreiben.

Code: Alles auswählen

#!/usr/bin/env python3

## Import utilities -------------------------------------------
import os, time, struct
import usb.core
import usb.util
import uinput

## Import mappings ------------------------------------------------------------
from initcodes import code
from keycodes import *
from buttoncodes import *

## XBox 360 wireless controller with chatpad --------------------------
## By David Vasic aka djevil(a)remotors.de ------------------------------
## Attribution-NonCommercial 4.0 International (CC BY-NC 4.0) 


#help(uinput)


## Get device
dev = usb.core.find(idVendor=0x045e, idProduct=0x0719)

time.sleep(.4)

## Write to controller at: --------------------------------------------------------
iface = 0
epoint = 0x01


## Controller commands: ---------------------------------------------------------
def rumble():
    dev.write(epoint, bytes([0x00, 0x01, 0x0f, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00]), iface)
def statusLED():
    dev.write(epoint, bytes([0x00, 0x00, 0x08, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), iface)
def keepAlive1():
    dev.write(epoint, bytes([0x00, 0x00, 0x0c, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), iface)
def keepAlive2():
    dev.write(epoint, bytes([0x00, 0x00, 0x0c, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), iface)
def chatpadInit():
    dev.write(epoint, bytes([0x00, 0x00, 0x0c, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), iface) 
    
## Controller / Analog sticks ------------------------------------------------------
def leftStick():
    
    if u > 16384:
        device.emit(uinput.REL_X, 15, syn=False)
    if u < -16384:
        device.emit(uinput.REL_X, -15, syn=False)
        
    if v > 16384:
        device.emit(uinput.REL_Y, -15, syn=False)
    if v < -16384:
        device.emit(uinput.REL_Y, 15, syn=False)

    
## Chatpad / Keys -------------------------------------------------------------------
def keys():
    knr=data[26]
    print(knr)
    
    if counter == 0 and knr != 128 and knr != 0:
        ## None / default
        if data[25] == 0:
            device.emit_click(keycode[knr])
            print(knr)



## Counter for debouncing / debugging ------------------------------------
global counter
counter = 0

## Infinte loop for reading input ----------------------------------------------
with uinput.Device(code) as device:
    while 1:
        
        try:
            
            statusLED()
            
            keepAlive2()
            
            chatpadInit()
            
            ## Keep alive3
            #device.emit_click(buttoncode[64])
            
            keepAlive1()

            ## read(bEndpointAddress, size_or_buffer, timeout ms )
            data = dev.read(0x81, 29) #len=29
            #print(data)
            
            keepAlive2()
            
            ## Chatpad / Keys ----------------------------------------------------
            
            keys()
            ## -------------------------------------------------------------------------
            
            keepAlive1()
            
            ### read(bEndpointAddress, size_or_buffer, timeout ms )
            data = dev.read(0x81, 29, 0) #len=29
            ##print(data)
            
            ### Controller / Analog sticks ---------------------------------------
            u, v = struct.unpack_from("<hh", data, 10)
            #print("U: ", u, "V: ", v)
        
            leftStick()
            ### -------------------------------------------------------------------------
            
            #counter += 1
        
            #if counter == 8:
                #counter = 0
            
        except Exception as e:
            print(e)
        
## https://www.python-forum.de/viewtopic.php?f=4&t=39444
## Special thanks to: Sirius3 and BlackJack

Das Hauptproblem ist halt die Eingabe von Controller und Chatpad zu synchronisieren,
ich habe den Code dokumentiert, ist vielleicht nicht alles unbedingt selbst erklären, also gerne fragen.

Die keepAlive Packages sind nötig um das Chatpad nach der Initialisierung anzuschalten, und ihm am "Leben zu erhalten", andererseits behindern diese auch die Tastenausgabe und verzögern diese, das also ist wohl Kern des Problems.

Wenn jemand dieses Script weiterentwickeln will, kann ich ihm auch den kompletten Source senden.
Wäre dann schön den neuen Source hier wieder anzufügen, bzw mir zu senden.

MFG djevil
Benutzeravatar
__blackjack__
User
Beiträge: 13110
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@djevil: Das Modul `os` wird importiert, aber nicht verwendet.

Durch die beiden Sternchen-Importe weiss man nicht ob davon nicht vielleicht einer unnötig ist und welche Namen denn nun aus welchem der beiden Module kommen.

Das ganze Programm ist durch mehrere globale Variablen sehr unübersichtlich. Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. ``global`` am besten gleich wieder vergessen. Was eine Funktion ausser Konstanten benötigt, sollte als Argument(e) übergeben werden.

Im gleichen Programm und dann auch noch im gleichen Namensraum `dev` und `device` zu haben ist sehr unglücklich. An der Stelle möchte der Leser vielleicht doch ganz gern mehr über die Geräte wissen, beispilsweis das `dev` der `xbox_controller` ist und `device` das `uinput_device`.

Es würde Sinn machen mindestens den XBox-Controller in einer Klasse zu kaspeln.

Das das entprellen und die Umsetzung von Controller-Tastencode zu uinput-Tastencode in der Funktion passiert, die sich um das ”drücken” der Taste für das Betriebssystem kümmert, ist ungünstig.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
"""XBox 360 wireless controller with chatpad.

By David Vasic aka djevil(a)remotors.de
Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)
"""
from __future__ import absolute_import, division, print_function

import struct
import time
from collections import namedtuple

import usb.core
import usb.util
import uinput

from initcodes import code
from keycodes import keycode
from buttoncodes import buttoncode


ControllerKey = namedtuple('ControllerKey', 'number state')


class Controller(object):
    INTERFACE = 0
    ENDPOINT = 0x01
    
    def __init__(self, vendor_id, product_id):
        self.device = usb.core.find(idVendor=vendor_id, idProduct=product_id)
        time.sleep(.4)
    
    def _read(self, size=29, timeout=0):
        return self.device.read(0x81, size, timeout)

    def _write(self, data):
        self.device.write(self.ENDPOINT, data, self.INTERFACE)
    
    def read_key(self):
        data = self._read()
        return ControllerKey(data[26], data[25])
    
    def read_joystick(self):
        data = self._read()
        return struct.unpack_from('<hh', data, 10)
    
    def rumble(self):
        self._write(b'\x00\x01\x0f\xc0\x00\xff\xff\x00\x00\x00\x00\x00')

    def status_led(self):
        self._write(b'\x00\x00\x08\x42\x00\x00\x00\x00\x00\x00\x00\x00')

    def keep_alive_1(self):
        self._write(b'\x00\x00\x0c\x1f\x00\x00\x00\x00\x00\x00\x00\x00')

    def keep_alive_2(self):
        self._write(b'\x00\x00\x0c\x1e\x00\x00\x00\x00\x00\x00\x00\x00')

    def chatpad_init(self):
        self._write(b'\x00\x00\x0c\x1b\x00\x00\x00\x00\x00\x00\x00\x00')


class UInput(object):
    
    def __init__(self, init_code):
        self.device = uinput.Device(init_code)

    def __enter__(self):
        return self.device.__enter__()
    
    def __exit__(self, *args):
        self.device.__exit__(*args)

    def emit_key(self, keycode):
        self.device.emit_click(keycode)
    
    def left_stick(self, delta_x, delta_y):
        if delta_x > 16384:
            self.device.emit(uinput.REL_X, 15, syn=False)
        elif delta_x < -16384:
            self.device.emit(uinput.REL_X, -15, syn=False)
            
        if delta_y > 16384:
            self.device.emit(uinput.REL_Y, -15, syn=False)
        elif delta_y < -16384:
            self.device.emit(uinput.REL_Y, 15, syn=False)


def main():
    controller = Controller(0x045e, 0x0719)
    with UInput(code) as uinput_device:
        while True:
            try:
                controller.status_led()
                controller.keep_alive_2()
                controller.chatpad_init()

                controller.keep_alive_1()
                key = controller.read_key()
                controller.keep_alive_2()
                
                if key.number not in [0, 128] and key.state == 0:
                    uinput_device.emit_key(keycode[key.number])
                
                controller.keep_alive_1()
                delta_x, delta_y = controller.read_joystick()
                uinput_device.left_stick(delta_x, delta_y)
            except Exception as error:
                print(error)


if __name__ == '__main__':
    main()

# https://www.python-forum.de/viewtopic.php?f=4&t=39444
# Special thanks to: Sirius3 and BlackJack
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
djevil
User
Beiträge: 58
Registriert: Montag 22. August 2016, 14:28

@BlackJack
Hi, ja, macht natürlich Sinn es am Ende noch in Klassen zu unterteilen,
(Oder besser, man fängt von Anfang an damit an..)

Dein Code funktioniert übrigens, obwohl du ihn nicht getestet hast, bzw. testen konntest ;)
(Bis auf den
left_stick()
, ich habe jetzt die Controller Klasse von der UInput Klasse erben lassen.)

Das alles ändert natürlich nichts an dem Timing Problem.

Aber hier und ein kleines
sleep()
und es funktioniert immerhin wieder,
(Vielleicht liegt es wirklich daran, daß mein neuer PC einfach schneller ist?)
wenn auch momentan noch nicht sehr zuverlässig,
aber falls nicht doch irgendwo ein Logikfehler drin ist, läuft es wohl auf ein spannendes
Trial & Error hinaus,
also falls es mal ein Wochenende lang durchregnet und mir sehr, sehr, sehr langweilig ist...

MFG d
Antworten