CNC · Handrad · Anleitung · ioSender

XHC Handrad Integration in ioSender

Wie du ein XHC-Funkhandrad mit einer Quelldatei und zwei Zeilen in ioSender einbaust — samt fertigem Build zum Herunterladen.

Ein Funkhandrad an ioSender

ioSender bringt keine Unterstützung für Handräder mit. Mit einer einzigen Quelldatei und zwei Zeilen im Hauptfenster läuft ein XHC WHB04B-6 trotzdem: jogging über das Rad, alle Tasten belegt, und Position, Vorschub und Drehzahl zurück aufs Display des Handrads.

Es gibt nichts zu referenzieren — der USB-Zugriff geht direkt an die Windows-API, kein NuGet-Paket, keine mitzuliefernde DLL. Wer den fertigen Build nur benutzen will, findet ihn unten zum Herunterladen.

Was du brauchst

Teil Angabe
Handrad XHC WHB04B-6, auch WHB04B-4 — USB-Kennung 10CE:EB93
Sender ioSender 2.0.47
Laufzeit .NET Framework 4.6.2, in Windows 10 und 11 enthalten
Zum Selberbauen Visual Studio 2022 Build Tools und das .NET Framework 4.6.2 Developer Pack
Steuerung grblHAL — hier getestet gegen den grblHAL-Simulator

Ein Treiber ist nicht nötig. Der USB-Empfänger meldet sich als herstellerdefiniertes HID-Gerät, also als Eingabegerät mit eigenem, nicht standardisiertem Datenformat. Windows lädt dafür seinen generischen Treiber, und weil es weder Tastatur noch Maus ist, gibt es den Zugriff zum Lesen und Schreiben frei.

Wohin die Datei gehört

ioSender ist in mehrere Projekte geteilt. CNC Core enthält die Kommunikation mit der Steuerung, CNC Controls die Bedienoberfläche.

XhcPendant.cs kommt nach CNC Controls. Sie braucht zwei Dinge, die dort liegen: AppConfig.Settings.Macros für die Makro-Tasten und JobControl für Start, Pause und Stop. CNC Core kennt beides nicht und darf es auch nicht kennen, sonst entsteht ein Ringbezug zwischen den Projekten.

Beides wird zur Laufzeit gesucht. Zu verdrahten ist nichts.

Datei ins Projekt aufnehmen

In CNC Controls.csproj zwischen die anderen Compile-Einträge. In Visual Studio genügt Rechtsklick auf das Projekt, Hinzufügen, Vorhandenes Element.

xml CNC Controls.csproj
<Compile Include="XhcPendant.cs" />

Aufruf im Hauptfenster

Die zweite Zeile steht im Konstruktor von MainWindow.xaml.cs, direkt nachdem ioSender sein Datenmodell in die globale Eigenschaft Grbl.GrblViewModel gelegt hat. Vorher gibt es nichts, woran sich die Anbindung hängen könnte. Attach() lädt die Einstellungen, startet seine Threads und meldet sich am Anwendungsende selbst wieder ab.

csharp ioSender/ioSender/MainWindow.xaml.cs
CNC.Core.Grbl.GrblViewModel = (GrblViewModel)DataContext;
GrblInfo.LatheModeEnabled = AppConfig.Settings.Lathe.IsEnabled;

CNC.Controls.XhcPendant.Attach();   // <-- diese Zeile

Mehr ist nicht nötig

Du kannst die Zeile stehen lassen, auch wenn gar kein Empfänger steckt. Der Lese-Thread findet dann nichts, wartet zwei Sekunden und sucht erneut. Keine Fehlermeldung, keine Abfrage, die du drumherum bauen müsstest.

Damit bleibt der Eingriff in fremden Code auf die beiden Zeilen beschränkt. Und wenn du es wieder loswerden willst: Datei raus, Zeile weg.

Wer ioSender XL verwendet, ändert dieselbe Zeile in dessen MainWindow.xaml.cs.

Ein frischer Klon baut nicht

Wer den fertigen Build unten nur benutzt, kann die nächsten Abschnitte überspringen. Wer selbst kompiliert, läuft in zwei Fallen. Beide melden sich nicht von selbst.

Die erste: ioSender braucht fünf Fremdbibliotheken, die nicht im Repository liegen. Es gibt keine .gitmodules, die Ordner sind nicht eingecheckt, und .gitignore schließt bin/ aus — genau dort erwarten drei der fünf ihre DLLs. Ein git clone kann sie deshalb prinzipiell nicht mitbringen, auch nicht mit --recursive. Das Wiki nennt vier davon samt Bezugsquelle; die fünfte, ToggleSwitch, steht dort nicht.

Am schnellsten holst du die DLLs aus einem offiziellen ioSender-Release und legst sie in diese Ordner neben den Quellcode.

Wohin die Fremdbibliotheken gehören

Diese Ordner liegen auf derselben Ebene wie CNC Core und ioSender. So stehen die Pfade in den HintPath-Einträgen der Projektdateien — legst du sie woanders hin, findet MSBuild die DLLs nicht und du bekommst CS0246 für jeden Typ daraus.

text
AForge2.2.5\Release\
Vector-master\RP.Math.Vector3\bin\Release\
helix-toolkit\
toggle-switch-control-master\WPF\ToggleSwitch\bin\Release\
toggle-switch-control-master\WPF\ToggleSwitch\bin\Release\en-US\
websocket-sharp\websocket-sharp\bin\Release\

CNC Core fehlt ein Verweis

Der Stand 7adaf7c, aus dem Release 2.0.47 hervorgegangen ist, kompiliert nicht. CNC Core\GCodeEmulator.cs benutzt die Vektor-Bibliothek RP.Math, die Projektdatei verweist sie aber nicht. Der Build bricht ab mit error CS0246: Der Typ- oder Namespacename "RP" wurde nicht gefunden. Diese beiden Einträge in CNC Core.csproj beheben das — sie zeigen auf dieselben DLLs, die schon im Ordner Vector-master liegen.

xml CNC Core/CNC Core/CNC Core.csproj
<Reference Include="RP.Math">
  <HintPath>..\..\Vector-master\RP.Math.Vector3\bin\Release\RP.Math.dll</HintPath>
</Reference>
<Reference Include="RP.Math.Vector3">
  <HintPath>..\..\Vector-master\RP.Math.Vector3\bin\Release\RP.Math.Vector3.dll</HintPath>
</Reference>

Unsichtbare Schalter unter Coolant

Die zweite Falle kostet mehr Zeit, weil nichts auf sie hinweist. Der Build läuft sauber durch, das Programm startet, die Verbindung steht — nur in der Gruppe Coolant fehlen die Schiebeschalter. „Flood“ und „Mist“ stehen da, davor eine leere Lücke.

Der Grund liegt in ToggleSwitch.dll. Die enthält nur den Code des Schalters, nicht sein Aussehen. Wie er gezeichnet wird, steht in einer zweiten Datei: en-US\ToggleSwitch.resources.dll, 13 KB groß, mit genau einem Eintrag darin — themes/generic.baml, der Standardvorlage des Steuerelements. Fehlt sie, erzeugt WPF das Element trotzdem, weiß aber nicht, wie es aussehen soll, und zeichnet nichts. Seinen Platz belegt es weiter, daher die Lücke vor der Beschriftung.

Wer die Fremdbibliotheken aus einem fertigen Release herauszieht, übersieht die Datei leicht: Sie liegt nicht bei den anderen DLLs, sondern im Unterordner en-US zwischen den Übersetzungen.

Gruppe Coolant in ioSender mit den Beschriftungen Flood und Mist, davor jeweils eine leere Fläche
Ohne den Satelliten: Beschriftung ohne Schalter, und keine Fehlermeldung dazu
Dieselbe Gruppe Coolant, jetzt mit je einem rot-grauen Schiebeschalter vor Flood und Mist
Mit ToggleSwitch.resources.dll im Ausgabeordner

Der Satellit muss nur danebenliegen

Leg ToggleSwitch.resources.dll in einen Unterordner en-US neben ToggleSwitch.dll im Bibliotheksverzeichnis. MSBuild erkennt Satelliten-Assemblys an dieser Ablage und kopiert sie beim nächsten Build von allein in die Ausgabe. Einzustellen ist nichts.

Bedienung

Bedienelement Wirkung
Linker Schalter Achswahl X, Y, Z, A, B, C
Rechter Schalter Schrittbetrieb: Schrittweite. Stufenlos: Vorschub in Prozent
Handrad Bewegt die gewählte Achse, rechts herum ist positiv
STEP / Continuous Schaltet den Betriebsmodus
Feed ± / Spindle ± Override für Vorschub und Drehzahl
Start/Pause Startet das geladene Programm, pausiert, setzt fort
Stop Bricht das Programm ab
Reset Soft-Reset der Steuerung
M-HOME Referenzfahrt, nur wenn Homing in der Steuerung aktiviert ist
S-ON/OFF Spindel ein und aus, erst nach Eintrag einer Drehzahl
Safe-Z, W-Home, Probe-Z, Macro-10 führen ioSender-Makros aus

Der rechte Schalter trägt zwei Beschriftungen, und welche gilt, hängt am Betriebsmodus. Im Schrittbetrieb ist es die Schrittweite, stufenlos der Vorschub in Prozent: 0.001 sind 2 %, 0.01 sind 5 %, 0.1 sind 10 %, 1 sind 30 %, dann folgen 60 % und 100 %.

Einstellungen

Beim ersten Start entsteht XhcPendant.xml neben der Exe. Die MacroId-Einträge verweisen auf ioSenders eigene Makros: Makro anlegen, Nummer merken, hier eintragen. 0 heißt unbelegt, dann bleibt die Taste wirkungslos. InvertWheel dreht die Bewegungsrichtung um, ShowMachinePosition schaltet das Display von Werkstück- auf Maschinenkoordinaten.

xml XhcPendant.xml
<XhcPendant>
  <Enable>true</Enable>
  <Log>false</Log>
  <StepDistances>
    <double>0.001</double>
    <double>0.01</double>
    <double>0.1</double>
    <double>1</double>
  </StepDistances>
  <StepFeedRate>500</StepFeedRate>
  <ContinuousFeedRate>2000</ContinuousFeedRate>
  <ContinuousDistance>1</ContinuousDistance>
  <InvertWheel>false</InvertWheel>
  <JogIntervalMs>50</JogIntervalMs>
  <DisplayIntervalMs>200</DisplayIntervalMs>
  <ShowMachinePosition>false</ShowMachinePosition>
  <SpindleRPM>0</SpindleRPM>
  <SafeZMacroId>0</SafeZMacroId>
  <WorkpieceHomeMacroId>0</WorkpieceHomeMacroId>
  <ProbeZMacroId>0</ProbeZMacroId>
  <Macro10MacroId>0</Macro10MacroId>
</XhcPendant>

Makros mit Rückfrage laufen nicht los

ioSender kennt zu jedem Makro die Einstellung „confirm on execute". Steht sie, erscheint beim Tastendruck eine Rückfrage am Bildschirm — und an der Maschine passiert nichts, bis jemand hinläuft. Für Makros, die du vom Handrad auslöst, schaltest du sie im Makro-Editor ab.

Wie die Anbindung arbeitet

Ein eigener Thread hält das USB-Gerät offen und blockiert in ReadFile, bis das Handrad etwas sendet. Er fasst nichts vom Sender an, sondern summiert nur Impulse auf und legt Tastendrücke in eine Warteschlange.

Die Impulse werden zusammengefasst, nie verworfen. Alle 50 ms wird die Summe zu genau einem $J=-Kommando. Ein Kommando pro Impuls würde die Warteschlange der Steuerung überfahren; beim zügigen Drehen landen so bis zu 11 Impulse in einem Kommando.

Alles, was zur Steuerung geht, läuft über den Dispatcher der Oberfläche. Das ist der Mechanismus, mit dem WPF Arbeit auf den Oberflächen-Thread zurückholt — nötig, weil ioSenders Verbindungsobjekt Comms.com nicht für den Zugriff aus mehreren Threads ausgelegt ist.

Start, Pause und Stop gehen über JobControl, das im Fensterbaum gesucht wird. Der Echtzeitbefehl CMD_CYCLE_START allein würde nur einen Vorschubhalt fortsetzen und kein geladenes Programm starten.

Was du über das Gerät wissen solltest

Drei Eigenheiten entscheidet die Firmware des Handrads, sie lassen sich vom PC aus nicht ändern.

In Schalterstellung OFF sendet das Handrad nichts und lässt sein Display dunkel. Das sieht nach einem Defekt aus, ist aber gewollt — und mit Abstand die häufigste Ursache, wenn scheinbar nichts funktioniert.

Das LCD zeigt immer vier Nachkommastellen, und sein oberes Feld lässt sich nicht abschalten, nur füllen.

Die USB-Kennung unterscheidet -4 und -6 nicht — beide melden sich als 10CE:EB93. Verraten tut es der Achswahlschalter: Nur ein -6 meldet die Stellungen B und C. Das ist mehr als eine Spitzfindigkeit, denn Vorschub und Drehzahl erscheinen nur auf dem -6 im Display; auf einem -4 bleiben diese Felder leer, egal was du sendest.

Vor dem ersten Einsatz an der Maschine

Miss die Schrittweite nach. Ob eine Rastung genau einen Impuls liefert, hängt am Encoder des Geräts — meine Werte stammen vom Simulator. Setz <Log>true</Log>, dreh eine Rastung und sieh im Protokoll nach, ob dort (1 pulses, Step) steht. Und verlass dich nicht auf die Reset-Taste des Handrads als Not-Aus: Ihr Signal läuft über Funk, USB und Windows. Ein Not-Aus, auf den du dich verlässt, gehört fest verdrahtet in den Leistungskreis.

Fehlersuche

Mit <Log>true</Log> schreibt die Anbindung jedes Rohpaket in Hex samt Dekodierung und jedes abgesetzte Kommando nach XhcPendant.log. Das ist der schnellste Weg zu sehen, was dein Gerät wirklich meldet. Die Zeile jog zeigt dabei in Klammern, wie viele Impulse zusammengefasst wurden.

text XhcPendant.log
opened  in: \\?\hid#vid_10ce&pid_eb93&col01#...
opened out: \\?\hid#vid_10ce&pid_eb93&col02#...
in  04-10-00-00-0E-11-01-FE  key1=None key2=None axis=X dial=Step0_01 delta=1
jog $J=G91G21X0.01F500   (1 pulses, Step)
key  SafeZ
macro 1 "Pendant-Test" started

Makros belegen und ändern

Die aufgedruckten Nummern Macro-1 bis Macro-9 sind keine eigenen Tasten, sondern die Zweitbelegung vorhandener Tasten: Sie greifen, solange Fn gehalten wird. Nur Macro-10 hat eine Taste für sich.

Der fertige Build bringt zehn Makros mit, xhc_macro_1 bis xhc_macro_10, samt eingetragener Zuordnung. Jedes enthält genau eine Zeile: (MSG,xhc_macro_3). Das ist ein Kommentar, den grblHAL in die Konsole schreibt — du siehst beim Tastendruck, welches Makro gefeuert hat, und es bewegt sich nichts. Zum Erproben gedacht. Den Inhalt ersetzt du im Makro-Editor von ioSender durch das, was die Taste wirklich tun soll; behalte dabei die Nummer, dann bleibt die Zuordnung bestehen.

Reset, Stop und Start/Pause behalten ihre Wirkung auch mit gehaltenem Fn. Ein Stopp, der nur manchmal stoppt, wäre schlimmer als eine fehlende zweite Ebene. Kombinationen, deren Nummer auf 0 steht, fallen ebenfalls auf die normale Funktion der Taste zurück — solange du keine einzige Nummer einträgst, verhält sich das Handrad also wie vorher.

Was Fn auf welche Nummer legt

Kombination Makro Kombination Makro
Fn + Feed + Macro-1 Fn + Safe-Z Macro-6
Fn + Feed − Macro-2 Fn + W-Home Macro-7
Fn + Spindle + Macro-3 Fn + S-ON/OFF Macro-8
Fn + Spindle − Macro-4 Fn + Probe-Z Macro-9
Fn + M-HOME Macro-5 Macro-10 (ohne Fn) Macro-10

Am Gerät nachgeprüft: Alle neun Kombinationen haben das Makro ausgelöst, mit dem die Taste beschriftet ist. Weicht ein anderes Exemplar ab, nennt dir <Log>true</Log> die Wahrheit — die Protokollzeile key SafeZ +Fn steht für die Taste, die das Handrad wirklich gemeldet hat. Ändern lässt sich die Zuordnung an genau einer Stelle, FnMacroId() in XhcPendant.cs.

Die Zuordnung in XhcPendant.xml

Die Zahl ist die Makronummer aus ioSender, nicht die Nummer auf dem Handrad. Beide gleich zu halten spart Verwirrung, nötig ist es nicht. 0 heißt unbelegt.

xml XhcPendant.xml
<Macro1MacroId>1</Macro1MacroId>
<Macro2MacroId>2</Macro2MacroId>
<Macro3MacroId>3</Macro3MacroId>
<Macro4MacroId>4</Macro4MacroId>
<Macro5MacroId>5</Macro5MacroId>
<Macro6MacroId>6</Macro6MacroId>
<Macro7MacroId>7</Macro7MacroId>
<Macro8MacroId>8</Macro8MacroId>
<Macro9MacroId>9</Macro9MacroId>
<Macro10MacroId>10</Macro10MacroId>

Stand

Geprüft und in Betrieb sind alle Schalterstellungen, das Rad in beiden Richtungen, Schritt- und stufenloser Betrieb, sämtliche Tasten, die Makro-Ausführung über ioSenders eigene Verwaltung, Start, Pause, Stop und Reset, dazu Position, Vorschub, Drehzahl und die Umschaltung auf A/B/C im Display.

Getestet habe ich gegen den grblHAL-Simulator und mit einem WHB04B-6. Das WHB04B-4 unterstützt die Datei ebenfalls, in der Hand hatte ich keins.

Das Protokoll ist nirgends veröffentlicht. Grundlage ist die Reverse-Engineering-Arbeit der LinuxCNC-Gemeinschaft an der Komponente xhc-whb04b-6.

Zwei Dinge sind offen. Die Zuordnung der beiden Modustasten war vertauscht: STEP schaltete auf stufenlos und umgekehrt. Ich habe die Tastencodes getauscht — 0x0E ist STEP, 0x0F ist Continuous —, am Gerät nachgeprüft ist der neue Stand aber noch nicht. Und der Build unten bringt nur en-US mit, die Oberfläche ist also englisch. Die übrigen Sprachdateien des offiziellen Release entstehen mit LocBaml in einem eigenen Arbeitsschritt außerhalb der Projektmappe.

Downloads zu diesem Beitrag

Alle Dateien dieses Artikels, gebündelt am Ende.

ZIP ioSender 2.0.47 mit XHC-Unterstützung (1.00-u4) Fertiger Build zum Entpacken und Starten, mit der Fn-Ebene, zehn vorbereiteten Makros und allen sieben Sprachen. Paketstand in PAKETSTAND.txt. Kein offizielles ioSender-Release. 1,6 MB · Stand ZIP XhcPendant.cs mit Einbauanleitung (1.00-u4) Die Quelldatei für den eigenen ioSender-Build, samt INTEGRATION.md. 19 KB · Stand ZIP Vollständiger Quellbaum (1.00-u4) ioSender-Quellcode mit Handrad, allen fünf Fremdbibliotheken und dem Skript für die Sprachdateien — baut ohne Nacharbeit. 5,5 MB · Stand

Zuletzt aktualisiert am · erschienen am 5. August 2026

Stimmt hier etwas nicht? Ein falscher Wert, Quelltext, der so nicht läuft, ein fehlender Schritt?