USB & die X10 Fernbedienung

Hardware- und Elektronikbasteleien, Ansteuerung von Schnittstellen und Peripherie.
Fragen zu "Consumer"-Problemen kommen in Offtopic.
Benutzeravatar
mpz
Beiträge: 497
Registriert: 14.06.2005 15:53
Computerausstattung: Win 10 Pro, 16 GB Ram, Intel I5 CPU und TI1070 Grafikkarte, PB 5.73 / 6.00 beta4
Wohnort: Berlin, Tempelhof

Beitrag von mpz »

Hallo Leute,

ich habe ein Programm geschrieben um eine PIC Microcontroller auszulesen. Der PIC funktioniert als HID am PC. Es wird eine externe DLL benutzt. Mein PIC konnte ich nicht mit dem "USB_Remote_Receiver_Read_1" Sourcecode auslesen, da der einen stetig sendendes HID erwartet. Bei mir blieb das Programm immer bei ReadFile_(HidHandle,@InDat,BytesToRead,@BytesRead,0) stehen bis der PIC was sendet. Ihr könnt ja mal testen ob sich die Fernsteuerung mit diesem Programm auslesen läßt.

Gruß
MPZ

Code: Alles auswählen

; "USB HID Controll.pb"
; 
; PowerBasic Example um ein HID Gerät anzusteuern.
; funktioniert z.B. mit einem PIC Controller PIC 18F2550 mit USB Schnittstelle
; Wer es wissen will: Der PIC 18F2550 hat z.b. 24x I/O´s und 10x 10bit AD´s
;  
; by MPZ Okt.2006
; for PB 4.00
; 
; benötigt wird die mcHID.dll von der Firma Mecanique. Die mcHID Library ist 
; Bestandteil der EasyHID Software:
; http://www.mecanique.co.uk/software/EasyHID.zip 
;
; Ein direkter Download der DLL ist über den folgenden Link möglich 
; http://www.docpro.com.br/arquivos/mcHID.zip
; 



Enumeration
  #Window_0
  #Listview_0
  #Text_0
  #Text_1
  #Button_0
  #Button_1
  #Button_2

EndEnumeration


;{/ Globale Variablen
Global pVendorID.l, pProductID.l, pIndex.l, DeviceHandle.l
Global VendorName.s, ProductName.s, GetSerialNumber.s
Global USB_HID.l
Global Dim BufferIn.b(256),Dim BufferOut.b(256)
Global NewList SelectList.s()

#WM_APP = 32768
#WM_HID_EVENT = #WM_APP + 200
#NOTIFY_PLUGGED = 1
#NOTIFY_UNPLUGGED = 2
#NOTIFY_CHANGED = 3
#NOTIFY_READ = 4

#VendorID = 6017    ; Herstellernummer eines bestimmten Gerätes = mein programmierter PIC 18F2550
#ProductID = 2000   ; Produktnummer eines bestimmten Gerätes = mein programmierter PIC 18F2550
                    ; Damit läßt sich der DeviceHandle  eines bestimmten HIDs leicht bestimmen
                    ; DeviceHandle = hidGetHandle(#VendorID, #ProductID) 

VendorName.s = Space(256)
ProductName.s = Space(256) 
GetSerialNumber.s = Space(256)
;}
Procedure Open_Window_0()
  If OpenWindow(#Window_0, 321, 133, 258, 259, "HID Empfang",  #PB_Window_SystemMenu | #PB_Window_TitleBar )
    If CreateGadgetList(WindowID(#Window_0))
      ListViewGadget(#Listview_0, 10, 30, 240, 170)
      TextGadget(#Text_0, 10, 8, 240, 20, "Daten empfangen", #PB_Text_Center)
      TextGadget(#Text_1, 10, 203, 240, 14, "Daten senden", #PB_Text_Center)
      ButtonGadget(#Button_0, 10, 220, 60, 30, "Senden 1")
      ButtonGadget(#Button_1, 100, 220, 60, 30, "Senden 2")
      ButtonGadget(#Button_2, 190, 220, 60, 30, "Senden 3")
    EndIf
    ProcedureReturn = 1
  EndIf
EndProcedure
;{/ DLL Datei Aufrufen

Prototype  hidConnect (pHostWin.l); As Boolean
Prototype  hidDisconnect (); As Boolean
Prototype  hidRead (pHandle.l, pData.b); As Boolean
Prototype  hidWrite (pHandle.l, pData.b); As Boolean
Prototype  hidReadEx (pVendorID.l, pProductID.l, pData.b); As Boolean
Prototype  hidWriteEx (pVendorID.l, pProductID.l, ByRef.b); As Boolean
Prototype  hidGetVendorName (pHandle.l, pText.s, pLen.l); As Long
Prototype  hidGetProductName (pHandle.l, pText.s, pLen.l); As Long
Prototype  hidGetSerialNumber (pHandle.l, pText.s, pLen.l); As Long
Prototype  hidGetVendorID (pHandle.l); As Long
Prototype  hidGetProductID (pHandle.l); As Long
Prototype  hidGetVersion (pHandle.l); As Long
Prototype  hidGetInputReportLength (pHandle.l); As Long
Prototype  hidGetOutputReportLength (pHandle.l); As Long
Prototype  hidGetHandle (pVendoID.l, pProductID.l); As Long
Prototype  hidGetItem (pIndex.l); As Long
Prototype  hidGetItemCount (); As Long
Prototype  hidSetReadNotify (pHandle.l, pValue.b);
Prototype  hidIsReadNotifyEnabled (pHandle.l); As Boolean
Prototype  hidIsAvailable (pVendorID.l, pProductID.l);  As Boolean


Define mcHID_DLL.l = OpenLibrary(#PB_Any, "mcHID.dll") 

If mcHID_DLL 
    Global hidConnect.hidConnect = GetFunction(mcHID_dll,"Connect") ; (ByVal pHostWin As Long) As Boolean
    Global hidDisconnect.hidDisconnect = GetFunction(mcHID_dll,"Disconnect");  () As Boolean
    Global hidRead.hidRead = GetFunction(mcHID_dll,"Read");  (ByVal pHandle As Long, ByRef pData As Byte) As Boolean
    Global hidWrite.hidWrite = GetFunction(mcHID_dll,"Write");  (ByVal pHandle As Long, ByRef pData As Byte) As Boolean
    Global hidReadEx.hidReadEx = GetFunction(mcHID_dll,"ReadEx");  (ByVal pVendorID As Long, ByVal pProductID As Long, ByRef pData As Byte) As Boolean
    Global hidWriteEx.hidWriteEx = GetFunction(mcHID_dll,"WriteEx");  (ByVal pVendorID As Long, ByVal pProductID As Long, ByRef pData As Byte) As Boolean
    Global hidGetVendorName.hidGetVendorName = GetFunction(mcHID_dll,"GetVendorName");  (ByVal pHandle As Long, ByVal pText As String, ByVal pLen As Long) As Long
    Global hidGetProductName.hidGetProductName = GetFunction(mcHID_dll,"GetProductName");  (ByVal pHandle As Long, ByVal pText As String, ByVal pLen As Long) As Long
    Global hidGetSerialNumber.hidGetSerialNumber = GetFunction(mcHID_dll,"GetSerialNumber");  (ByVal pHandle As Long, ByVal pText As String, ByVal pLen As Long) As Long
    Global hidGetVendorID.hidGetVendorID = GetFunction(mcHID_dll,"GetVendorID");  (ByVal pHandle As Long) As Long
    Global hidGetProductid.hidGetProductid = GetFunction(mcHID_dll,"GetProductID");  (ByVal pHandle As Long) As Long
    Global hidGetVersion.hidGetVersion = GetFunction(mcHID_dll,"GetVersion"); (ByVal pHandle As Long) As Long
    Global hidGetInputReportLength.hidGetInputReportLength = GetFunction(mcHID_dll,"GetInputReportLength");  (ByVal pHandle As Long) As Long
    Global hidGetOutputReportLength.hidGetOutputReportLength = GetFunction(mcHID_dll,"GetOutputReportLength");  (ByVal pHandle As Long) As Long
    Global hidGetHandle.hidGetHandle = GetFunction(mcHID_dll,"GetHandle");  (ByVal pVendoID As Long, ByVal pProductID As Long) As Long
    Global hidGetItem.hidGetItem = GetFunction(mcHID_dll,"GetItem");  (ByVal pIndex As Long) As Long
    Global hidGetItemCount.hidGetItemCount = GetFunction(mcHID_dll,"GetItemCount");  (ByVal pIndex As Long) As Long
    Global hidSetReadNotify.hidSetReadNotify = GetFunction(mcHID_dll,"SetReadNotify"); ByVal pHandle As Long, ByVal pValue As Boolean)
    Global hidIsReadNotifyEnabled.hidIsReadNotifyEnabled = GetFunction(mcHID_dll,"IsReadNotifyEnabled");  (ByVal pHandle As Long) As Boolean
    Global hidIsAvailable.hidIsAvailable = GetFunction(mcHID_dll,"IsAvailable"); (ByVal pVendorID As Long, ByVal pProductID As Long) As Boolean
Else
    MessageRequester("Fehlermeldung", "Das Programm konnte die mcHID.dll Datei nicht öffnen", #PB_MessageRequester_Ok)
    End
EndIf 
;}

ProcedureDLL mcHID_Free(); DLL freigeben
  If mcHID_DLL
    CloseLibrary(#PB_Any)
  EndIf
  XLTable_DLL = 0
EndProcedure

Procedure Selectrequester (Selectbox.s,Selecttext.s,Mylist.s())

Enumeration 100 ; bei vielen Fenster und Event Wert erhöhen
  #Window_Selectbox
  #Text_Selectbox
  #Listview_Selectbox
  #Button_Selectbox0
  #Button_Selectbox1
EndEnumeration

Return_Selectbox.l = -1 ; Fehlerwert = -1 

If CountList(Mylist()) < 1
   ProcedureReturn = Return_Selectbox
EndIf

If OpenWindow(#Window_Selectbox,0,0, 305, 138, Selectbox.s,  #PB_Window_SystemMenu | #PB_Window_TitleBar| #PB_Window_ScreenCentered)
  If CreateGadgetList(WindowID(#Window_Selectbox))
    TextGadget(#Text_Selectbox, 20, 10, 150, 14, Selecttext.s)
    ListViewGadget(#Listview_Selectbox, 10, 30, 210, 100)
    ButtonGadget(#Button_Selectbox0, 230, 70, 70, 23, "Wählen")
    ButtonGadget(#Button_Selectbox1, 230, 107, 70, 23, "Abbrechen")
  EndIf
EndIf

FirstElement(Mylist()) 
For a = 1 To CountList(Mylist())
   AddGadgetItem (#Listview_Selectbox, -1, Mylist())   ; definieren des Listview-Inhalts
   NextElement(Mylist()) 
Next

Repeat ; Start of the event loop
  
  Event = WaitWindowEvent() ; This line waits until an event is received from Windows
  GadgetID = EventGadget() ; Is it a gadget event?
  
  If Event = #PB_Event_Gadget
    If GadgetID = #Button_Selectbox0
       Event = #PB_Event_CloseWindow
       Return_Selectbox = GetGadgetState(#Listview_Selectbox) 
    ElseIf GadgetID = #Button_Selectbox1
       Event = #PB_Event_CloseWindow
    EndIf
  EndIf  
Until Event = #PB_Event_CloseWindow ; End of the event loop
CloseWindow(#Window_Selectbox)
ProcedureReturn = Return_Selectbox
EndProcedure 

;- Window Callback 
Procedure WinProc(hWnd,Msg,wParam,lParam) 
 result = #PB_ProcessPureBasicEvents 
 
 If Msg = #WM_HID_EVENT 
       
       Select wParam
           ; HID device has been plugged message...
        Case #NOTIFY_PLUGGED
           ;HID device has been plugged
           AddGadgetItem(#Listview_0,-1, "#NOTIFY_PLUGGED")
        Case #NOTIFY_UNPLUGGED
           ;HID device has been unplugged
           AddGadgetItem(#Listview_0,-1, "#NOTIFY_UNPLUGGED")
        Case #NOTIFY_CHANGED
           ;HID device has been changed
           AddGadgetItem(#Listview_0,-1, "#NOTIFY_CHANGED")
           If hidGetItemCount() > 0
              ClearList(SelectList()) 
              For a = 0 To hidGetItemCount()
                  Devicetemp.l = hidGetItem(a)
                  hidGetProductName (Devicetemp,ProductName,256)
                  AddElement(SelectList())
                  SelectList() = ProductName
              Next
              USB_HID = Selectrequester ("HID Gerät wählen","HID Geräte:",SelectList())
              If USB_HID > -1
                  DeviceHandle = hidGetItem(USB_HID)
                  hidGetProductName (DeviceHandle.l,ProductName,256)
                  AddGadgetItem(#Listview_0,-1, "hidGetProductName = "+ProductName)
                  AddGadgetItem(#Listview_0,-1, "hidGetVendorID = "+ Str(hidGetVendorID (DeviceHandle.l)))
                  AddGadgetItem(#Listview_0,-1, "hidGetProductID = " + Str(hidGetProductID(DeviceHandle.l)))
                  AddGadgetItem(#Listview_0,-1, "Input Datenlänge in Bytes = "+Str(hidGetInputReportLength (DeviceHandle.l)))
                  AddGadgetItem(#Listview_0,-1, "Output Datenlänge in Bytes = "+Str(hidGetOutputReportLength (DeviceHandle.l)))
                  hidSetReadNotify (DeviceHandle, #True) ;read event...
              EndIf
          EndIf
         ;DeviceHandle = hidGetHandle(#VendorID, #ProductID)
         ;hidSetReadNotify (DeviceHandle, #True) ! IMPORTANT ! Read works only With this flag
        Case #NOTIFY_READ
           ; READ works only if the hidSetReadNotify flag is set 
           AddGadgetItem(#Listview_0,-1, "#NOTIFY_READ")
           If hidRead(DeviceHandle, @BufferIn()) 
              AddGadgetItem(#Listview_0,-1, "Read: "+Str(BufferIn(0)) +"/"+ Str(BufferIn(1))+"/"+Str(BufferIn(2))+"/"+Str(BufferIn(3))+"/"+Str(BufferIn(4))+"/"+Str(BufferIn(5))+"/"+Str(BufferIn(6))+"/"+Str(BufferIn(7))+"/"+Str(BufferIn(8)))
           Else 
              AddGadgetItem(#Listview_0,-1, "Read fehlerhaft!")
           EndIf
        EndSelect
   
   EndIf

  
  ProcedureReturn result 
EndProcedure 

;{/ Main Unit
If Open_Window_0()

hidconnect (WindowID(#Window_0))

SetWindowCallback(@WinProc())

  Repeat
    EventID = WaitWindowEvent()

    Select EventID
     
       Case #PB_Event_Gadget
         Select EventGadget()
           Case #Button_0
             AddGadgetItem(#Listview_0,-1, "Senden 1 angeklickt!")
             ; Anzahl der BufferOut ist abhängig vom HID Gerät
             BufferOut(0) = 8
             BufferOut(1) = 7
             BufferOut(2) = 6
             BufferOut(3) = 5
             BufferOut(4) = 4
             BufferOut(5) = 3
             BufferOut(6) = 2
             BufferOut(7) = 1
             BufferOut(8) = 0
             hidWrite (DeviceHandle.l,@BufferOut(0)) ; Sende Daten zu einem HID Gerät
           Case #Button_1 : AddGadgetItem(#Listview_0,-1, "Senden 2 angeklickt!")
             ; Anzahl der BufferOut ist abhängig vom HID Gerät
             BufferOut(0) = 5
             BufferOut(1) = 4
             BufferOut(2) = 3
             BufferOut(3) = 2
             BufferOut(4) = 1
             BufferOut(5) = 0
             BufferOut(6) = 8
             BufferOut(7) = 7
             BufferOut(8) = 6
             hidWrite (DeviceHandle.l,@BufferOut(0)) ; Sende Daten zu einem HID Gerät
           Case #Button_2 : AddGadgetItem(#Listview_0,-1, "Senden 3 angeklickt!")
             ; Anzahl der BufferOut ist abhängig vom HID Gerät
             BufferOut(0) = 2
             BufferOut(1) = 1
             BufferOut(2) = 0
             BufferOut(3) = 8
             BufferOut(4) = 7
             BufferOut(5) = 6
             BufferOut(6) = 5
             BufferOut(7) = 4
             BufferOut(8) = 3
             hidWrite (DeviceHandle.l,@BufferOut(0)) ; Sende Daten zu einem HID Gerät
         EndSelect
       EndSelect

  Until EventID = #PB_Event_CloseWindow
  hidDisconnect ()
  mcHID_Free()
EndIf
;}
Working on - MP3D Engine -
Smash
Beiträge: 23
Registriert: 10.10.2004 00:09

Beitrag von Smash »

Hallo mpz,
Danke für dein Programm.
Ich habe mich schon ne ganze weile nicht mehr mit
dem Fernsteuerungsproblem beschäftigt.
Mit der USB abfrage hatte es ja nicht hingehauen.
Und eigentlich läuft mein Programm ja mit dem Testtool.
Dann bin ich noch über „Girder“ gestolpert.
Die letzte Freewareversion in Verbindung
mit einer extra dafür geschriebenen DLL. Geht auch.
Aber dennoch möchte ich irgendwann die Fernbedienung mit einem eigenen Programm (ohne Testtool) vernünftig abfragen.
Möglicherweise hilft dein Programm da weiter, Danke.
laserjones
Beiträge: 8
Registriert: 30.08.2009 00:43

Beitrag von laserjones »

Leider gibt es die EasyHID-Software nicht mehr bei Mecanique. Kennt jemand eine Alternative oder eine verständliche(!) Anleitung, wie man mit den Windows-eigenen DLLs die HID-Schnittstelle unter PB anspricht?
Benutzeravatar
mpz
Beiträge: 497
Registriert: 14.06.2005 15:53
Computerausstattung: Win 10 Pro, 16 GB Ram, Intel I5 CPU und TI1070 Grafikkarte, PB 5.73 / 6.00 beta4
Wohnort: Berlin, Tempelhof

Beitrag von mpz »

Kleiner Tipp,

oberen Text des Quelltextes lesen:

; Bestandteil der EasyHID Software:
; http://www.mecanique.co.uk/software/EasyHID.zip
;
; Ein direkter Download der DLL ist über den folgenden Link möglich
; http://www.docpro.com.br/arquivos/mcHID.zip <- Zaunpfahl....
;

Ansonsten ne Mail mit Deiner Email an mich...

Gruß Michael
Working on - MP3D Engine -
laserjones
Beiträge: 8
Registriert: 30.08.2009 00:43

Beitrag von laserjones »

Ups - wer lesen kann, ist klar im Vorteil ... :oops: Vielen Dank!

Noch eine grundsätzliche Frage: Ist die Verwendung von SetWindowCallback für den USB-Zugriff zwingend erforderlich? Ich habe so was noch nie benutzt und versuche gerade das Prinzip zu verstehen ... Die PB-Hilfe klärt mich auch nur teilweise auf.
Benutzeravatar
mpz
Beiträge: 497
Registriert: 14.06.2005 15:53
Computerausstattung: Win 10 Pro, 16 GB Ram, Intel I5 CPU und TI1070 Grafikkarte, PB 5.73 / 6.00 beta4
Wohnort: Berlin, Tempelhof

Beitrag von mpz »

Ja,

da der USB Port ständig Daten empfängt bzw USB Geräte ständig senden und diese Daten kontinuierlich abgefragt werden müssen. Wenn nicht entsteht das beliebte "buffer overflow" und nichts geht mehr. Du musst Dir das als ständig laufende Hintergrundschleife vorstellen die immer arbeitet bzw. verarbeitet. Das restliche Programm läuft davon unabhängig weiter...

Gruß Michael
Working on - MP3D Engine -
laserjones
Beiträge: 8
Registriert: 30.08.2009 00:43

Beitrag von laserjones »

Hm, interessant ist allerdings, dass das anfangs erwähnte Programm (http://www.purebasic.fr/german/viewtopic.php?t=4128) den Zugriff irgendwie ohne SetWindowCallback hinbekommt. Wobei ich das Programm allerdings noch viel weniger verstehe als deins ...

Was ist denn der Unterschied zwischen USB-Events, die ständig abgefragt werden müssen, und normalen Fenster-Events (Button-Klicks etc.), die ja ebenso ständig (bzw. sehr oft) abgefragt werden müssen? Letzteres geht ja offenbar auch ohne Callback.

Deinen Code habe ich inzwischen immerhin so weit "zweitverwertet", dass ich Kontakt zu meinem PIC-USB-Gerät (ein anderes als deins, Eigenbau) bekomme, allerdings kann ich noch keine Daten austauschen. Aufgrund meiner geringen PB-Erfahrung ist das Ganze eher ein Trial-and-error-Prozess. Aber schon ein erheblicher Fortschritt - ganz vielen Dank für die Vorlage!
Benutzeravatar
mpz
Beiträge: 497
Registriert: 14.06.2005 15:53
Computerausstattung: Win 10 Pro, 16 GB Ram, Intel I5 CPU und TI1070 Grafikkarte, PB 5.73 / 6.00 beta4
Wohnort: Berlin, Tempelhof

Re: USB & die X10 Fernbedienung

Beitrag von mpz »

Hi,

die Fernbedienungsabfrage liest nur Daten, kann aber keine zum USB Gerät senden (ist ja eine Fernbedienung...). Damit geht sie nur in eine Richtung, mein Programm in beide.

Man kann auch eine permanente Abfrage einfach als Schleife machen, aber hier ist der Sinn das ich im Programm was anderes machen kann und nur reagiere wenn per USB was passiert, quasi ein Multitasking. Wichtig ist das das Hintergrundprogramm ständig gleichmässig läuft, das normale Programm kann ja auch mal unterbrochen werden -> Ergebnis ist ein crash bzw. Pufferüberlauf.

Events sind "Ereignisse" d.h hier reagiere ich auf entstandene Windows oder USB Ereignisse. Wenn ich ein Haus habe warum soll ich die Haustürklingel klingeln lassen wenn ich ein Fenster öffne (bitte nicht antworten mit "Weil ich immer ein Klingeldraht am Fenster habe >:) )

Leider ist mein Projekt schon 3 Jahre her, so das ich nicht mehr alles auswendig weiß. Wenn Du die den "hidGetProductName", "hidGetVendorID" und "hidGetProductID" ausließt werden dann die richtigen Daten und Nummern ausgelesen oder falsche Zahlen? Wenn du nicht die richtigen Daten bekommst, ist die Programmierung des PIC falsch (z.B. falsche MHz Angabe oder Teiler). Wenn da alles Okay ist musst Du überprüfen wie gross den die "Input Datenlänge in Bytes" und "Output Datenlänge in Bytes" ist und das Programm anpassen...

Gruß Michael
Working on - MP3D Engine -
laserjones
Beiträge: 8
Registriert: 30.08.2009 00:43

Re: USB & die X10 Fernbedienung

Beitrag von laserjones »

Hallo,

mir war schon klar, was Events sind, ich hatte nur den Unterschied zwischen USB- und normalen Fenster-Events nicht verstanden. Aber klar, wenn ich ständiges Durchlaufen der Abfrageschleife nicht garantieren kann, dann brauche ich einen "Parallel-Task" dafür. Dann muss ich mal schauen, wie dieser Callback genau funktioniert (ist leider in der PB-Hilfe nicht gerade verständlich beschrieben).

Ich bekomme Kontakt zum richtigen Gerät, denn es wird der Name meines PIC-Gerätes angezeigt, und den kann das PB-Programm ja nur über USB erfahren haben (VID und PID habe ich im Programm korrekt angepasst). Jetzt liegt es wohl tatsächlich an der Reportlänge, die ist bei meinem Gerät 1 Byte und bei deinem Programm 8 Byte (wobei du aber trotzdem z. B. beim PIC-Reset ja nur zwei Bytes sendest - das geht also offenbar?). Es ging aber auch nicht richtig, als ich den PIC auf 8 Byte geändert habe. Eigentlich sollte der PIC in der aktuellen Testkonfiguration lediglich die empfangenen Bytes wieder zurücksenden. Mit einem HID-Terminal klappt das auch, aber wenn ich von deinem Programm aus was sende (indem ich einfach zusätzliche HID-Write-Befehle einbaue), empfange ich im Terminal immer nur den Wert 1,teils mehrfach. Muss noch mal experimentieren.
Benutzeravatar
mk-soft
Beiträge: 3700
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: USB & die X10 Fernbedienung

Beitrag von mk-soft »

@mpz

suche schon den Thread mit den PIC und USB schon die ganze Zeit. Den Bauplan für die verwendung es PIC auch noch.
Gib mir doch mal bitte den Links dazu.

Danke
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Antworten