G92-Grad + E-Korrektur

This commit is contained in:
chk
2026-06-25 18:58:55 +02:00
parent 8deb7bb8a6
commit b96a538b89
18 changed files with 1369 additions and 34 deletions

View File

@@ -0,0 +1,86 @@
# Emergency Stop Button — Erkenntnisse & Entscheidungen
## Hardware-Wahl
### ESP32-C3 Super Mini — abgelehnt
Das kompakte Board hat **keinen Laderegler** (kein TP4056/MCP73831, kein JST-Akku-Anschluss).
Zudem zieht eine dauerhaft leuchtende Power-LED 12 mA — selbst ohne WLAN-Betrieb würde ein kleiner Akku in wenigen Tagen leer sein.
### DFRobot FireBeetle 2 — gewählt
- Integrierter Laderegler + JST-PH-2.0-Anschluss direkt am Board
- Low-Power optimiert (ab Werk ~15 µA im Deep Sleep)
- Kein Zusatz-Hardware nötig für Akkubetrieb
## Akku-Spezifikation für den FireBeetle 2
Suchbegriffe:
- `LiPo Akku 3.7V JST PH2.0 2000mAh Schutzschaltung`
- `Li-Po 1S 3.7V protected JST-PH 2.0mm 2000mAh`
Pflichtmerkmale:
| Merkmal | Wert |
|---|---|
| Typ | LiPo / Li-Polymer, **1S** (1 Zelle) |
| Spannung | **3,7 V** nominal |
| Stecker | **JST PH, 2,0 mm Raster, 2-polig** |
| Schutzschaltung | **Ja** (BMS/PCM) |
| Kapazität | **2000 mAh** |
> **Achtung:** Polarität vor dem Einstecken mit Multimeter prüfen — JST-PH-Stecker sind nicht normiert. Sicherste Option: Akku direkt bei DFRobot kaufen.
## Architektur-Entscheidung: WiFi Light Sleep (Priorität: 250 ms Latenz)
Die **250 ms Latenz** vom Knopfdruck bis zum API-Call ist das primäre Ziel.
| Option | Latenz | Ø Strom | Laufzeit (2000 mAh) |
|---|---|---|---|
| **WiFi Light Sleep (DTIM=10)** | **150250 ms** ✅ | ~1 mA | **~80 Tage** |
| Deep Sleep + Reconnect | 6001300 ms ❌ | ~0,02 mA | ~mehrere Jahre |
Deep Sleep scheidet aus: Der WiFi-Reconnect nach dem Aufwachen dauert 6001300 ms — die 250-ms-Anforderung wird klar verfehlt.
## WiFi Light Sleep — Funktionsprinzip
Die CPU schläft, der WiFi-Stack bleibt aktiv. Mit DTIM=10 wacht der ESP32 alle ~1000 ms für 12 ms auf, um gepufferte Pakete vom Router abzuholen. Die Verbindungsassoziation bleibt erhalten.
Ein **GPIO-Interrupt** (Leitung auf GND) weckt den ESP32 in **15 ms** — der API-Call kann sofort abgesetzt werden, weil WiFi bereits verbunden ist.
## Latenzbudget
| Schritt | Zeit |
|---|---|
| Wakeup aus Light Sleep | 15 ms |
| WiFi-Verbindung prüfen (bereits aktiv) | 0 ms |
| HTTP-Request aufbauen | 2050 ms |
| TLS-Handshake (HTTPS) | 50150 ms |
| Server-Antwort | 2050 ms |
| **Gesamt** | **~100250 ms** ✅ |
## Akkulaufzeit (WiFi Light Sleep, 2000 mAh)
```
Durchschnittsstrom (DTIM=10, Taster selten gedrückt): ~1 mA
Nutzbare Kapazität (80 %): 1600 mAh
Selbstentladung LiPo: ~2 mAh/Tag
Laufzeit ≈ 1600 mAh / 1 mA ≈ 1600 h ≈ 6780 Tage
```
> Zum Vergleich: Mit 1000 mAh (alter Stand) waren es ~40 Tage.
## GPIO Wake-Up — technische Details
- **Pegel-Trigger** (kein Flanken-Trigger): die Leitung muss >ein paar ms auf GND bleiben.
- **Pull-Up intern** aktivieren: im Ruhezustand HIGH, Ereignis zieht auf GND.
- **Wake-fähige Pins** sind nur RTC/LP-GPIOs — im FireBeetle-2-Datenblatt prüfen.
- API: `esp_sleep_enable_gpio_wakeup()` / `esp_light_sleep_start()`
## Vergleich: Alternative Deep-Sleep-Architektur (nicht für E-Stop geeignet)
Falls in einem anderen Projekt Latenz < 1 s ausreicht und Akkulaufzeit Monate betragen soll:
- Deep Sleep, WLAN nur 818 Uhr alle 30 min (20 WLAN-Verbindungen/Tag)
- Zusätzlich GPIO-Wake für Ereignisse (innerhalb ~300 ms nach Aufwachen + Reconnect)
- Laufzeit 2000 mAh: **~810 Monate** (Selbstentladung dominant)
Für den Emergency Stop Button ist diese Option **nicht geeignet**, da die WiFi-Reconnect-Zeit die 250-ms-Anforderung überschreitet.

View File

@@ -41,7 +41,7 @@ Tastendruck geweckt.
\begin{itemize}
\item Latenz Knopfdruck $\rightarrow$ API-Call: \textbf{< 250\,ms}
\item Stromversorgung: LiPo 1000\,mAh (kabellos, batteriebetrieben)
\item Stromversorgung: LiPo 2000\,mAh (kabellos, batteriebetrieben)
\item Möglichst lange Akkulaufzeit (Taster wird selten gedrückt, $\leq$1\,×/h)
\item Einfache, wartungsarme Architektur
\end{itemize}
@@ -105,13 +105,17 @@ DTIM & Wakeup-Intervall & Ø Strom \\
Empfohlen: \texttt{DTIM=10} -- sparsamste Option, Verbindung bleibt stabil.
\subsection{Akkulaufzeit (1000 mAh LiPo)}
\subsection{Akkulaufzeit (2000 mAh LiPo)}
Bei $\approx$1\,mA Durchschnittsstrom (DTIM=10, selten gedrückt):
Bei $\approx$1\,mA Durchschnittsstrom (DTIM=10, selten gedrückt) und 80\,\% nutzbarer Kapazität:
\[
t = \frac{1000~\mathrm{mAh}}{1~\mathrm{mA}} \approx 40~\mathrm{Tage}
t = \frac{1600~\mathrm{mAh}}{1~\mathrm{mA}} \approx 67\text{--}80~\mathrm{Tage}
\]
Die \textbf{Latenz von 250\,ms} hat Vorrang vor maximaler Akkulaufzeit -- Deep Sleep scheidet
daher aus (WiFi-Reconnect 600--1300\,ms). Mit 2000\,mAh und WiFi Light Sleep sind
ca.\ 2,5 Monate Betrieb ohne Laden realistisch.
% -----------------------------------------------
\section{Latenzbudget}
@@ -148,6 +152,27 @@ Adafruit HUZZAH32 Feather & MCP73831 & $\approx$20\,€ & gute Dokumentation \\
\end{tabular}
\end{center}
\subsection{Akku-Spezifikation}
Für den FireBeetle\,2 wird ein \textbf{1S LiPo mit JST-PH-2,0-Stecker und BMS} benötigt:
\begin{center}
\begin{tabular}{ll}
\toprule
Merkmal & Wert \\
\midrule
Typ & LiPo / Li-Polymer, 1 Zelle (1S) \\
Nennspannung & 3{,}7\,V (voll: 4{,}2\,V) \\
Stecker & JST PH, 2{,}0\,mm Raster, 2-polig \\
Schutzschaltung & Ja (BMS/PCM) -- Pflicht bei Deep-Sleep-Betrieb \\
Kapazität & 2000\,mAh \\
\bottomrule
\end{tabular}
\end{center}
\textbf{Achtung Polarität:} JST-PH-Stecker sind nicht normiert -- Polung vor dem
Einstecken mit Multimeter prüfen. Sicherste Quelle: Akku direkt bei DFRobot kaufen.
\subsection{Schaltung}
\begin{lstlisting}
@@ -155,7 +180,7 @@ ESP32 Taster
GPIO 9 ---- [Taster] ---- GND
(interner Pull-Up aktiv: HIGH = offen, LOW = gedrueckt)
LiPo 1000mAh ---- BAT+ / BAT- des Boards
LiPo 2000mAh (JST PH 2.0, mit BMS) ---- BAT+ / BAT- des Boards
\end{lstlisting}
Kein weiteres Bauteil nötig -- der interne Pull-Up des ESP32 reicht.

View File

@@ -2,6 +2,17 @@
Dieses Projekt empfängt G-Code und Robotersteuerbefehle, berechnet Inverse Kinematik für einen mehrgliedrigen Roboterarm und leitet die resultierenden Achsenbefehle an mehrere GRBL/FluidNC-Telnet-Sender weiter.
## Einbindung ins Projekt
Der Driver steht zwischen Eingabe-Programmen (appInput oder appAutomasiation oder jede beliebige andere Eingabeform)
welche die Eingabe steuert. Die Eingabe wird eben an den Driver weitergegeben. Hier im Driver werden die Welt-Koordinaten
in Motor-Koordinaten umgerechnet, es werden die Motoren angesteuert.
<img src="doc/SoftwareModularisation.svg" width="30%" alt="Software Modularisierung">
Die Motor--Steuerung erfolgt (momentan) per GCode an FluidNC Driver--Boards. Diese arbeiten jeweils mit
Motor-Koordinaten in den X, Y und Z-Achsen. Es werden mehrere FluidNC Boards unterstützt.
## Architektur
- `startRobot.js` startet zwei HTTPS-Server:
@@ -28,7 +39,7 @@ Die Eingaben kommen per WebSocket an den HTTPS-Server und werden in `server/Inpu
- Antwort: Positionsdaten des Roboters im JSON-Format.
- G-Code-Befehle:
- `G90`, `G91`, `G1`, `G28`
- `G92` (wird intern als `M92` verarbeitet — setzt Motorposition ohne Bewegung)
- `G92` setzt die Motorposition ohne Bewegung. Winkel (`Y`,`Z`,`A`,`B`,`C`) in **Grad** (G-Code-Konvention, wie FluidNC); `X` in mm; `E` = Greifer-Öffnung in mm (ab Null-Position eines Fingers), wird intern über die Greifer-Kopplung in `eMotor` umgerechnet. (`M92` macht dasselbe, erwartet die Winkel aber roh in **Radiant**.)
- Messungen in `X`, `Y`, `Z`, `A`, `B`, `C`, `E`, `F`
- `M1` für direkte Motor-Koordinaten
- FCodes (Datei-/Programm-Befehle) — werden durch den Driver an `appRobotFileservice` weitergeleitet:

553
doc/Server_to_Robot.svg Normal file
View File

@@ -0,0 +1,553 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="185.6852mm"
height="185.04396mm"
viewBox="0 0 185.6852 185.04396"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (ebf0e94, 2025-05-08)"
sodipodi:docname="Server_to_Robot.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.7884049"
inkscape:cx="418.56665"
inkscape:cy="298.70438"
inkscape:window-width="1920"
inkscape:window-height="1129"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showguides="true">
<sodipodi:guide
position="104.23929,266.06288"
orientation="0,-1"
id="guide2"
inkscape:locked="false" />
</sodipodi:namedview>
<defs
id="defs1">
<marker
style="overflow:visible"
id="ArrowTriangleStylized"
refX="0"
refY="0"
orient="auto-start-reverse"
inkscape:stockid="Stylized triangle arrow"
markerWidth="0.80000001"
markerHeight="0.80000001"
viewBox="0 0 1 1"
inkscape:isstock="true"
inkscape:collect="always"
preserveAspectRatio="xMidYMid">
<path
transform="scale(0.5)"
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
id="path4" />
</marker>
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter1"
x="-0.30270667"
y="-0.03584998"
width="1.4504006"
height="1.162455">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="1.4976689"
id="feGaussianBlur1" />
</filter>
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter2"
x="-0.0071527473"
y="-0.027732722"
width="1.0143055"
height="1.0554654">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.24767946"
id="feGaussianBlur2" />
</filter>
<marker
style="overflow:visible"
id="ArrowTriangleStylized-9"
refX="0"
refY="0"
orient="auto-start-reverse"
inkscape:stockid="Stylized triangle arrow"
markerWidth="0.80000001"
markerHeight="0.80000001"
viewBox="0 0 1 1"
inkscape:isstock="true"
inkscape:collect="always"
preserveAspectRatio="xMidYMid">
<path
transform="scale(0.5)"
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
id="path4-0" />
</marker>
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter1-7"
x="-0.62539401"
y="-0.033621411"
width="2.5964451"
height="1.1841982">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="1.4976689"
id="feGaussianBlur1-6" />
</filter>
</defs>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-18.207626,-12.726111)">
<rect
style="fill:#ffffff;stroke:#ffff00;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round"
id="rect3"
width="268.13898"
height="222.83389"
x="-2.5991523"
y="-0.36202425"
rx="3.4391522"
ry="3.640882" />
<rect
style="fill:none;stroke:#800000;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0.363309"
id="rect2-7-3-0-1-1-3-3"
width="24.441923"
height="27.052231"
x="170.64005"
y="158.19603"
ry="7.2152543" />
<rect
style="fill:none;stroke:#800000;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0.363309"
id="rect2-7-3-0-1-1-3-3-2"
width="24.441923"
height="27.052231"
x="135.24863"
y="155.13577"
ry="7.2152543" />
<rect
style="fill:#ffffff;stroke:#800000;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0.363309"
id="rect2-7-3-0-1-1-3-3-2-3"
width="24.441923"
height="27.052231"
x="140.22813"
y="156.67696"
ry="7.2152543" />
<rect
style="opacity:1;fill:#ffffff;stroke:#008000;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0.298561"
id="rect2-1-7-2-0"
width="22.543501"
height="60.748863"
x="174.14117"
y="98.800468"
ry="7.2152543" />
<rect
style="fill:#ffffff;stroke:#008000;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2-1-7"
width="59.087742"
height="61.460766"
x="62.740757"
y="99.263435"
ry="7.2152543" />
<rect
style="fill:none;stroke:#000080;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2-7"
width="32.03553"
height="27.052229"
x="68.540405"
y="55.081566"
ry="7.2152543" />
<rect
style="fill:none;stroke:#000080;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0.25"
id="rect2-7-1"
width="34.888073"
height="75.545448"
x="66.882202"
y="53.743294"
ry="8.3898315" />
<rect
style="fill:none;stroke:#000080;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2-7-8"
width="14.712612"
height="27.052227"
x="124.56292"
y="54.991467"
ry="7.2152543" />
<rect
style="fill:none;stroke:#000080;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2-7-8-5"
width="14.712612"
height="27.052227"
x="159.14595"
y="54.767498"
ry="7.2152543" />
<rect
style="opacity:0.684327;fill:#ffffff;stroke:#000080;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none;filter:url(#filter2)"
id="rect2-8"
width="118.05676"
height="30.448875"
x="71.373299"
y="42.542305"
ry="8.5576286" />
<rect
style="fill:#ffffff;stroke:#008000;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0.586331"
id="rect2-1-7-2"
width="22.543501"
height="60.748863"
x="140.53279"
y="98.616264"
ry="7.2152543" />
<rect
style="opacity:0.187638;fill:#ffffff;stroke:#000080;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2-3-8"
width="36.781532"
height="27.76413"
x="27.882366"
y="43.876053"
ry="7.2152543" />
<rect
style="fill:none;stroke:#800000;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2-7-3-0-1-1-3"
width="24.441923"
height="27.052231"
x="82.887558"
y="166.4115"
ry="7.2152543" />
<rect
style="fill:#ffffff;stroke:#800000;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2-7-3-0-1-1"
width="24.441923"
height="27.052231"
x="77.66507"
y="170.46783"
ry="7.2152543" />
<rect
style="fill:none;stroke:#000080;stroke-width:0.5;stroke-linejoin:round;stroke-dasharray:none"
id="rect1"
width="177.95691"
height="55.182091"
x="18.457626"
y="35.237286"
ry="7.2152543" />
<text
xml:space="preserve"
style="font-weight:bold;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Bold';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000080;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="23.82712"
y="33.894917"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';fill:#000080;stroke:none;stroke-width:0.5"
x="23.82712"
y="33.894917">server.schooltech.ch.</tspan></text>
<text
xml:space="preserve"
style="font-weight:bold;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Bold';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#008000;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="-143.19246"
y="61.593941"
id="text1-9"
transform="rotate(-90)"><tspan
sodipodi:role="line"
id="tspan1-1"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';fill:#008000;stroke:none;stroke-width:0.5"
x="-143.19246"
y="61.593941">MiniPC</tspan></text>
<text
xml:space="preserve"
style="font-weight:bold;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Bold';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;opacity:0.572347;fill:#008000;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="-140.84836"
y="139.27716"
id="text1-9-3"
transform="rotate(-90)"><tspan
sodipodi:role="line"
id="tspan1-1-2"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';fill:#008000;stroke:none;stroke-width:0.5"
x="-140.84836"
y="139.27716">MiniPc2</tspan></text>
<text
xml:space="preserve"
style="font-weight:bold;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Bold';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;opacity:0.308682;fill:#008000;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="-128.90205"
y="173.23196"
id="text1-9-3-8"
transform="rotate(-90)"><tspan
sodipodi:role="line"
id="tspan1-1-2-4"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:center;text-anchor:middle;fill:#008000;stroke:none;stroke-width:0.5"
x="-128.90205"
y="173.23196">Raspi 3</tspan><tspan
sodipodi:role="line"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:center;text-anchor:middle;fill:#008000;stroke:none;stroke-width:0.5"
x="-128.90205"
y="182.96658"
id="tspan5">(ohne Video)</tspan></text>
<rect
style="fill:#ffffff;stroke:#000080;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2"
width="49.595745"
height="27.76413"
x="72.851173"
y="43.900543"
ry="7.2152543" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000080;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="81.393982"
y="60.274261"
id="text2"><tspan
sodipodi:role="line"
id="tspan2"
style="fill:#000080;stroke:none;stroke-width:0.5"
x="81.393982"
y="60.274261">Portal UI</tspan></text>
<rect
style="fill:#ffffff;stroke:#000080;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2-3"
width="36.781532"
height="27.76413"
x="23.999027"
y="55.154541"
ry="7.2152543" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000080;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="31.118034"
y="71.528259"
id="text2-87"><tspan
sodipodi:role="line"
id="tspan2-4"
style="fill:#000080;stroke:none;stroke-width:0.5"
x="31.118034"
y="71.528259">Admin</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;opacity:0.229581;fill:#000080;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="34.223759"
y="52.143547"
id="text2-87-1"><tspan
sodipodi:role="line"
id="tspan2-4-7"
style="fill:#000080;stroke:none;stroke-width:0.5"
x="34.223759"
y="52.143547">Nginx</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000080;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="72.928909"
y="79.513229"
id="text2-8"><tspan
sodipodi:role="line"
id="tspan2-2"
style="fill:#000080;stroke:none;stroke-width:0.5"
x="72.928909"
y="79.513229">Tunnel</tspan></text>
<rect
style="fill:none;stroke:#008000;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2-7-3"
width="31.532143"
height="25.70986"
x="68.459602"
y="101.92957"
ry="7.2152543" />
<rect
style="fill:none;stroke:#008000;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2-7-3-0"
width="32.03553"
height="27.052229"
x="68.916672"
y="130.88501"
ry="7.2152543" />
<rect
style="fill:#ffffff;stroke:#800000;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2-7-3-0-1"
width="24.441923"
height="27.052231"
x="70.099998"
y="167.20209"
ry="7.2152543" />
<rect
style="fill:#ffffff;stroke:#008000;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2-1"
width="44.058456"
height="26.757345"
x="74.350037"
y="111.79094"
ry="7.2152543" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#008000;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="82.053864"
y="127.73859"
id="text2-9"><tspan
sodipodi:role="line"
id="tspan2-8"
style="fill:#008000;stroke:none;stroke-width:0.5"
x="82.053864"
y="127.73859">MainApp</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#008000;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="73.756584"
y="109.90114"
id="text2-8-6"><tspan
sodipodi:role="line"
id="tspan2-2-5"
style="fill:#008000;stroke:none;stroke-width:0.5"
x="73.756584"
y="109.90114">Tunnel</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#008000;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="74.230339"
y="148.89832"
id="text2-8-6-5"><tspan
sodipodi:role="line"
id="tspan2-2-5-0"
style="fill:#008000;stroke:none;stroke-width:0.5"
x="74.230339"
y="148.89832">Driver</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#008000;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="72.633484"
y="178.56844"
id="text2-8-6-5-4"><tspan
sodipodi:role="line"
id="tspan2-2-5-0-4"
style="fill:#800000;stroke:none;stroke-width:0.5"
x="72.633484"
y="178.56844">Robot</tspan><tspan
sodipodi:role="line"
style="fill:#800000;stroke:none;stroke-width:0.5"
x="72.633484"
y="188.26981"
id="tspan3">board</tspan></text>
<rect
style="fill:#ffffff;stroke:#000080;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2-5"
width="22.780821"
height="27.526825"
x="128.87369"
y="43.978241"
ry="7.2152543" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000080;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="137.4165"
y="60.184162"
id="text2-7"><tspan
sodipodi:role="line"
id="tspan2-6"
style="fill:#000080;stroke:none;stroke-width:0.5"
x="137.4165"
y="60.184162">UI</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000080;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="129.6226"
y="79.75872"
id="text2-8-1"><tspan
sodipodi:role="line"
id="tspan2-2-8"
style="fill:#000080;stroke:none;stroke-width:0.5"
x="129.6226"
y="79.75872">T</tspan></text>
<rect
style="fill:#ffffff;stroke:#000080;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
id="rect2-5-4"
width="22.780821"
height="27.526825"
x="165.13467"
y="43.754272"
ry="7.2152543" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000080;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="171.99953"
y="59.960194"
id="text2-7-3"><tspan
sodipodi:role="line"
id="tspan2-6-1"
style="fill:#000080;stroke:none;stroke-width:0.5"
x="171.99953"
y="59.960194">UI</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.7611px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000080;stroke:none;stroke-width:0.499999;stroke-linejoin:round;stroke-dasharray:none"
x="163.70226"
y="79.199165"
id="text2-8-1-2"><tspan
sodipodi:role="line"
id="tspan2-2-8-3"
style="fill:#000080;stroke:none;stroke-width:0.5"
x="163.70226"
y="79.199165">T</tspan></text>
<path
style="opacity:0.677704;mix-blend-mode:normal;fill:none;stroke:#338000;stroke-width:5;stroke-linejoin:round;stroke-dasharray:none;marker-end:url(#ArrowTriangleStylized);filter:url(#filter1)"
d="m 139.46805,20.675199 c 0,0 -22.67989,32.409972 -24.62856,38.778949 -4.89798,16.008385 -4.12484,30.729749 -3.84747,40.716632 0.28059,10.10266 1.18759,28.44197 -1.1865,39.86643 -1.8449,8.87791 -8.54281,20.88243 -8.54281,20.88243"
id="path1"
sodipodi:nodetypes="csssc" />
<path
style="opacity:0.270096;mix-blend-mode:normal;fill:none;stroke:#338000;stroke-width:5;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#ArrowTriangleStylized-9);filter:url(#filter1-7)"
d="m 182.53306,26.969611 c -16.61161,54.420297 22.65849,44.720607 2.6103,128.616729"
id="path1-8"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-size:11.2889px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;opacity:0.781457;fill:#338000;stroke:#00ffff;stroke-width:10;stroke-linejoin:round;stroke-dasharray:none"
x="139.23076"
y="20.200598"
id="text4"><tspan
sodipodi:role="line"
id="tspan4"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:11.2889px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Bold';fill:#338000;stroke:none;stroke-width:10"
x="139.23076"
y="20.200598">User</tspan></text>
<text
xml:space="preserve"
style="font-size:11.2889px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, ';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;opacity:0.781457;fill:#338000;fill-opacity:0.399281;stroke:#00ffff;stroke-width:10;stroke-linejoin:round;stroke-dasharray:none"
x="176.33203"
y="24.640509"
id="text4-8"><tspan
sodipodi:role="line"
id="tspan4-6"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:11.2889px;font-family:'Times New Roman';-inkscape-font-specification:'Times New Roman, Bold';fill:#338000;fill-opacity:0.399281;stroke:none;stroke-width:10"
x="176.33203"
y="24.640509">User3</tspan></text>
<text
xml:space="preserve"
style="font-size:3.175px;text-align:center;text-anchor:middle;opacity:0.270096;fill:#ffffff;fill-opacity:0.553957;stroke:#338000;stroke-width:0.799999"
x="173.63927"
y="23.712084"
id="text6"><tspan
sodipodi:role="line"
id="tspan6"
style="stroke-width:0.8"
x="173.63927"
y="23.712084" /></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,321 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="254.71674mm"
height="103.7089mm"
viewBox="0 0 254.71674 103.7089"
version="1.1"
id="svg5"
inkscape:version="1.4.2 (ebf0e94, 2025-05-08)"
sodipodi:docname="SoftwareModularisation.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.73851712"
inkscape:cx="624.22385"
inkscape:cy="295.18612"
inkscape:window-width="1710"
inkscape:window-height="752"
inkscape:window-x="122"
inkscape:window-y="174"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<rect
x="82.597954"
y="404.86536"
width="207.17191"
height="81.243889"
id="rect342" />
<marker
style="overflow:visible"
id="ArrowTriangleStylized"
refX="0"
refY="0"
orient="auto-start-reverse"
inkscape:stockid="Stylized triangle arrow"
markerWidth="0.80000001"
markerHeight="0.80000001"
viewBox="0 0 1 1"
inkscape:isstock="true"
inkscape:collect="always"
preserveAspectRatio="xMidYMid">
<path
transform="scale(0.5)"
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
d="m 6,0 c -3,1 -7,3 -9,5 0,0 0,-4 2,-5 -2,-1 -2,-5 -2,-5 2,2 6,4 9,5 z"
id="path4" />
</marker>
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter1"
x="-0.046015804"
y="-0.087898715"
width="1.1621232"
height="1.3448635">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="1.4976689"
id="feGaussianBlur1" />
</filter>
</defs>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(36.090885,-5.8258868)">
<rect
style="fill:#ffffff;stroke:#ffff00;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round"
id="rect1"
width="322.79492"
height="150.47044"
x="-50.063141"
y="0.81020516"
rx="3.4391522"
ry="3.640882" />
<rect
style="fill:none;fill-opacity:0.204013;stroke:#ff5555;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round"
id="rect1257"
width="178.77321"
height="59.471653"
x="9.6731005"
y="49.798553"
ry="6.2628565" />
<rect
style="fill:#0000ff;fill-opacity:0.204013;stroke:#0000ff;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round"
id="rect234"
width="77.743065"
height="24.361881"
x="40.125454"
y="60.188179"
ry="6.2628565" />
<text
xml:space="preserve"
transform="scale(0.26458333)"
id="text340"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;white-space:pre;shape-inside:url(#rect342);display:inline;fill:#000000;fill-opacity:1;stroke:#ff5555" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.61905px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.240477"
x="52.229092"
y="68.706116"
id="text402"
transform="scale(0.90888935,1.1002439)"><tspan
sodipodi:role="line"
id="tspan400"
style="stroke-width:0.240477"
x="52.229092"
y="68.706116">RoboticsDriver</tspan></text>
<rect
style="fill:#0000ff;fill-opacity:0.204013;stroke:#0000ff;stroke-width:0.547466;stroke-linecap:round;stroke-linejoin:round"
id="rect234-4"
width="31.870832"
height="13.579219"
x="29.924107"
y="92.440994"
ry="6.2544284" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:7.05556px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="31.706272"
y="101.74669"
id="text402-5"><tspan
sodipodi:role="line"
id="tspan400-9"
style="font-size:7.05556px;stroke-width:0.264583"
x="31.706272"
y="101.74669">FluidNC</tspan></text>
<rect
style="fill:#0000ff;fill-opacity:0.204013;stroke:#0000ff;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round"
id="rect234-4-1"
width="29.735825"
height="13.597518"
x="67.371765"
y="92.798347"
ry="6.2628565" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:7.05556px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="68.78791"
y="102.2177"
id="text402-5-4"><tspan
sodipodi:role="line"
id="tspan400-9-5"
style="font-size:7.05556px;stroke-width:0.264583"
x="68.78791"
y="102.2177">FluidNC</tspan></text>
<rect
style="fill:#0000ff;fill-opacity:0.204013;stroke:#0000ff;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round"
id="rect234-4-1-3"
width="29.735825"
height="13.597518"
x="106.22498"
y="92.798347"
ry="6.2628565" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:7.05556px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="112.161"
y="102.11319"
id="text402-5-3-2"><tspan
sodipodi:role="line"
id="tspan400-9-4-6"
style="font-size:7.05556px;stroke-width:0.264583"
x="112.161"
y="102.11319">... </tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:7.05556px;line-height:1.25;font-family:sans-serif;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="186.6279"
y="76.870903"
id="text1365"><tspan
sodipodi:role="line"
id="tspan1363"
style="font-size:7.05556px;text-align:end;text-anchor:end;fill:#ff0000;stroke-width:0.264583"
x="186.6279"
y="76.870903">Info, Installation</tspan><tspan
sodipodi:role="line"
style="font-size:7.05556px;text-align:end;text-anchor:end;fill:#ff0000;stroke-width:0.264583"
x="186.6279"
y="85.690353"
id="tspan1367">&amp; Management</tspan></text>
<rect
style="fill:#bcffbc;fill-opacity:1;stroke:#00ff00;stroke-width:0.57906;stroke-linecap:round;stroke-linejoin:round"
id="rect1369"
width="46.882557"
height="19.296309"
x="18.31204"
y="32.985142"
ry="6.2467051" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="20.270605"
y="45.266979"
id="text3141"><tspan
sodipodi:role="line"
id="tspan3139"
style="stroke-width:0.264583"
x="20.270605"
y="45.266979">Eingabe</tspan></text>
<rect
style="fill:#bcffbc;fill-opacity:1;stroke:#00ff00;stroke-width:0.58073;stroke-linecap:round;stroke-linejoin:round"
id="rect1369-9"
width="60.136616"
height="19.294638"
x="53.404377"
y="7.1910405"
ry="6.1369925" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="55.362103"
y="19.472044"
id="text3141-8"><tspan
sodipodi:role="line"
id="tspan3139-7"
style="stroke-width:0.264583"
x="55.362103"
y="19.472044">Programm</tspan></text>
<rect
style="fill:#bcffbc;fill-opacity:1;stroke:#00ff00;stroke-width:0.59402;stroke-linecap:round;stroke-linejoin:round"
id="rect1369-9-7"
width="90.575684"
height="20.620722"
x="127.75317"
y="6.1228967"
ry="6.1341004" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="133.28688"
y="20.546833"
id="text3141-8-4"><tspan
sodipodi:role="line"
id="tspan3139-7-5"
style="stroke-width:0.264583"
x="133.28688"
y="20.546833">Automatisieren</tspan></text>
<rect
style="fill:#bcffbc;fill-opacity:1;stroke:#00ff00;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round"
id="rect1369-9-7-1"
width="55.172501"
height="18.177734"
x="-35.826302"
y="13.41999"
ry="6.1533923" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.75471px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.243868"
x="-36.601929"
y="23.712566"
id="text3141-8-4-3"
transform="scale(0.92170774,1.0849426)"><tspan
sodipodi:role="line"
id="tspan3139-7-5-9"
style="stroke-width:0.243868"
x="-36.601929"
y="23.712566">Interaktion</tspan></text>
<rect
style="fill:#bcffbc;fill-opacity:1;stroke:#00ff00;stroke-width:0.572522;stroke-linecap:round;stroke-linejoin:round"
id="rect1369-0"
width="45.814304"
height="19.302847"
x="138.13206"
y="48.384411"
ry="6.2488213" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="140.09389"
y="60.669518"
id="text3141-7"><tspan
sodipodi:role="line"
id="tspan3139-6"
style="stroke-width:0.264583"
x="140.09389"
y="60.669518">Anzeige</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="196.68637"
y="35.060978"
id="text9629"><tspan
sodipodi:role="line"
id="tspan9627"
style="fill:#00ff00;stroke-width:0.264583"
x="196.68637"
y="35.060978">App</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="-2.2734814"
y="38.910786"
id="text9629-1"><tspan
sodipodi:role="line"
id="tspan9627-2"
style="fill:#00ff00;stroke-width:0.264583"
x="-2.2734814"
y="38.910786">App</tspan></text>
<path
style="opacity:0.677704;mix-blend-mode:normal;fill:none;stroke:#338000;stroke-width:5;stroke-linejoin:round;stroke-dasharray:none;marker-end:url(#ArrowTriangleStylized);filter:url(#filter1)"
d="m 30.489165,22.750961 c 39.165646,1.677077 80.639205,15.871211 80.435915,31.846606 -0.27243,21.408071 1.05382,18.470822 -9.37105,37.461762"
id="path1-9"
sodipodi:nodetypes="csc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -10809,3 +10809,205 @@
2026-06-14T11:37:52.055Z ::ffff:127.0.0.1: M114
2026-06-14T11:37:52.277Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-14T11:37:52.509Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:03:59.536Z ::ffff:127.0.0.1: FList
2026-06-25T16:03:59.571Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:03:59.593Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:03:59.614Z ::ffff:127.0.0.1: FShow
2026-06-25T16:03:59.621Z ::ffff:127.0.0.1: M114
2026-06-25T16:03:59.637Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:03:59.836Z ::ffff:127.0.0.1: M114
2026-06-25T16:04:00.049Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:04:00.282Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:04:11.315Z ::ffff:127.0.0.1: FList
2026-06-25T16:04:11.353Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:04:11.356Z ::ffff:127.0.0.1: M114
2026-06-25T16:04:11.369Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:04:11.371Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:04:11.382Z ::ffff:127.0.0.1: FShow
2026-06-25T16:04:11.508Z ::ffff:127.0.0.1: M114
2026-06-25T16:04:11.722Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:04:11.953Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:04:20.552Z ::ffff:127.0.0.1: M114
2026-06-25T16:04:20.569Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:04:20.569Z ::ffff:127.0.0.1: FList
2026-06-25T16:04:20.605Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:04:20.617Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:04:20.634Z ::ffff:127.0.0.1: FShow
2026-06-25T16:04:20.771Z ::ffff:127.0.0.1: M114
2026-06-25T16:04:20.998Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:04:21.222Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:08:34.871Z ::ffff:127.0.0.1: M114
2026-06-25T16:08:34.888Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:08:34.888Z ::ffff:127.0.0.1: FList
2026-06-25T16:08:34.915Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:08:34.926Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:08:34.936Z ::ffff:127.0.0.1: FShow
2026-06-25T16:08:35.051Z ::ffff:127.0.0.1: M114
2026-06-25T16:08:35.264Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:08:35.494Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:08:38.150Z ::ffff:127.0.0.1: FList
2026-06-25T16:08:38.171Z ::ffff:127.0.0.1: M114
2026-06-25T16:08:38.185Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:08:38.186Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:08:38.200Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:08:38.209Z ::ffff:127.0.0.1: FShow
2026-06-25T16:08:38.344Z ::ffff:127.0.0.1: M114
2026-06-25T16:08:38.558Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:08:38.789Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:14:41.227Z ::ffff:127.0.0.1: FList
2026-06-25T16:14:41.286Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:14:41.305Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:14:41.318Z ::ffff:127.0.0.1: FShow
2026-06-25T16:14:41.333Z ::ffff:127.0.0.1: M114
2026-06-25T16:14:41.350Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:14:41.531Z ::ffff:127.0.0.1: M114
2026-06-25T16:14:41.755Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:14:41.992Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:25:20.901Z ::ffff:127.0.0.1: M114
2026-06-25T16:25:21.135Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:25:21.372Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:25:21.445Z ::ffff:127.0.0.1: FList
2026-06-25T16:25:21.476Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:25:21.491Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:25:21.490Z ::ffff:127.0.0.1: M114
2026-06-25T16:25:21.502Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:25:21.505Z ::ffff:127.0.0.1: FShow
2026-06-25T16:25:28.958Z ::ffff:127.0.0.1: M114
2026-06-25T16:25:29.178Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:25:29.424Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:25:29.442Z ::ffff:127.0.0.1: FList
2026-06-25T16:25:29.470Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:25:29.482Z ::ffff:127.0.0.1: M114
2026-06-25T16:25:29.485Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:25:29.492Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:25:29.498Z ::ffff:127.0.0.1: FShow
2026-06-25T16:25:42.243Z ::ffff:127.0.0.1: M114
2026-06-25T16:25:42.326Z ::ffff:127.0.0.1: FList
2026-06-25T16:25:42.351Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:25:42.357Z ::ffff:127.0.0.1: M114
2026-06-25T16:25:42.364Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:25:42.371Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:25:42.375Z ::ffff:127.0.0.1: FShow
2026-06-25T16:25:42.465Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:25:42.689Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:25:49.667Z ::ffff:127.0.0.1: FList
2026-06-25T16:25:49.696Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:25:49.708Z ::ffff:127.0.0.1: M114
2026-06-25T16:25:49.710Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:25:49.721Z ::ffff:127.0.0.1: FShow
2026-06-25T16:25:49.723Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:25:49.886Z ::ffff:127.0.0.1: M114
2026-06-25T16:25:50.103Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:25:50.331Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:26:01.509Z ::ffff:127.0.0.1: M114
2026-06-25T16:26:01.592Z ::ffff:127.0.0.1: FList
2026-06-25T16:26:01.617Z ::ffff:127.0.0.1: M114
2026-06-25T16:26:01.624Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:26:01.632Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:26:01.638Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:26:01.652Z ::ffff:127.0.0.1: FShow
2026-06-25T16:26:01.744Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:26:01.980Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:26:26.068Z ::ffff:127.0.0.1: M114
2026-06-25T16:26:26.152Z ::ffff:127.0.0.1: FList
2026-06-25T16:26:26.179Z ::ffff:127.0.0.1: M114
2026-06-25T16:26:26.186Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:26:26.197Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:26:26.200Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:26:26.213Z ::ffff:127.0.0.1: FShow
2026-06-25T16:26:26.297Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:26:26.533Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:30:08.621Z ::ffff:127.0.0.1: M114
2026-06-25T16:30:08.639Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:30:08.744Z ::ffff:127.0.0.1: M114
2026-06-25T16:30:09.003Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:30:09.009Z ::ffff:127.0.0.1: FList
2026-06-25T16:30:09.039Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:30:09.056Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:30:09.068Z ::ffff:127.0.0.1: FShow
2026-06-25T16:30:09.256Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:30:11.200Z ::ffff:127.0.0.1: M114
2026-06-25T16:30:11.365Z ::ffff:127.0.0.1: FList
2026-06-25T16:30:11.401Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:30:11.418Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:30:11.429Z ::ffff:127.0.0.1: M114
2026-06-25T16:30:11.431Z ::ffff:127.0.0.1: FShow
2026-06-25T16:30:11.433Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:30:11.447Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:30:11.664Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:36:03.192Z ::ffff:127.0.0.1: FList
2026-06-25T16:36:03.222Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:36:03.223Z ::ffff:127.0.0.1: M114
2026-06-25T16:36:03.235Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:36:03.248Z ::ffff:127.0.0.1: FShow
2026-06-25T16:36:03.479Z ::ffff:127.0.0.1: M114
2026-06-25T16:36:08.260Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:36:08.503Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:36:13.731Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:36:25.750Z ::ffff:127.0.0.1: M114
2026-06-25T16:36:25.972Z ::ffff:127.0.0.1: M114
2026-06-25T16:36:26.067Z ::ffff:127.0.0.1: FList
2026-06-25T16:36:26.098Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:36:26.111Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:36:26.124Z ::ffff:127.0.0.1: FShow
2026-06-25T16:36:30.778Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:36:30.998Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:36:36.241Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:36:39.714Z ::ffff:127.0.0.1: FList
2026-06-25T16:36:39.749Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:36:39.763Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:36:39.778Z ::ffff:127.0.0.1: FShow
2026-06-25T16:36:39.813Z ::ffff:127.0.0.1: M114
2026-06-25T16:36:39.830Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:36:40.002Z ::ffff:127.0.0.1: M114
2026-06-25T16:36:40.224Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:36:45.463Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:36:56.688Z ::ffff:127.0.0.1: FList
2026-06-25T16:36:56.716Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:36:56.734Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:36:56.747Z ::ffff:127.0.0.1: FShow
2026-06-25T16:36:56.884Z ::ffff:127.0.0.1: M114
2026-06-25T16:36:56.892Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:36:56.972Z ::ffff:127.0.0.1: M114
2026-06-25T16:36:57.212Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:36:57.462Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:36:59.277Z ::ffff:127.0.0.1: M114
2026-06-25T16:36:59.305Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:36:59.536Z ::ffff:127.0.0.1: FList
2026-06-25T16:36:59.567Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:36:59.582Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:36:59.596Z ::ffff:127.0.0.1: FShow
2026-06-25T16:36:59.793Z ::ffff:127.0.0.1: M114
2026-06-25T16:37:00.013Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:37:00.237Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:37:15.021Z ::ffff:127.0.0.1: M114
2026-06-25T16:37:15.030Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:39:35.648Z ::ffff:127.0.0.1: M114
2026-06-25T16:39:35.659Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:41:46.209Z ::ffff:127.0.0.1: M114
2026-06-25T16:41:46.340Z ::ffff:127.0.0.1: FList
2026-06-25T16:41:46.367Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:41:46.382Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:41:46.398Z ::ffff:127.0.0.1: FShow
2026-06-25T16:41:46.399Z ::ffff:127.0.0.1: M114
2026-06-25T16:41:46.416Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:41:46.437Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:41:46.671Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:42:28.868Z ::ffff:127.0.0.1: M114
2026-06-25T16:42:29.070Z ::ffff:127.0.0.1: FList
2026-06-25T16:42:29.086Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:42:29.093Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:42:29.099Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:42:29.108Z ::ffff:127.0.0.1: FShow
2026-06-25T16:42:29.211Z ::ffff:127.0.0.1: M114
2026-06-25T16:42:29.223Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:42:29.326Z ::ffff:127.0.0.1: G1 X1
2026-06-25T16:42:31.618Z ::ffff:127.0.0.1: M114
2026-06-25T16:42:31.633Z ::ffff:127.0.0.1: FList
2026-06-25T16:42:31.636Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:42:31.669Z ::ffff:127.0.0.1: FPlus
2026-06-25T16:42:31.684Z ::ffff:127.0.0.1: FLoad nichtda
2026-06-25T16:42:31.695Z ::ffff:127.0.0.1: FShow
2026-06-25T16:42:31.758Z ::ffff:127.0.0.1: M114
2026-06-25T16:42:31.991Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-25T16:42:32.218Z ::ffff:127.0.0.1: G1 X1

View File

@@ -14762,3 +14762,49 @@
2026-06-14T11:37:48.506Z ::ffff:127.0.0.1 : Ping
2026-06-14T11:37:51.780Z ::ffff:127.0.0.1 : Ping
2026-06-14T11:37:51.825Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:03:59.576Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:03:59.604Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:04:11.272Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:04:11.329Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:04:20.515Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:04:20.538Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:08:34.823Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:08:34.834Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:08:38.112Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:08:38.138Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:14:41.281Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:14:41.295Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:25:20.653Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:25:21.462Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:25:28.700Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:25:29.460Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:25:41.995Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:25:42.332Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:25:49.649Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:25:49.680Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:26:01.271Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:26:01.590Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:26:25.831Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:26:26.148Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:30:08.515Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:30:08.577Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:30:10.959Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:30:11.400Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:36:03.194Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:36:03.235Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:36:25.716Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:36:25.728Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:36:39.770Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:36:39.778Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:36:56.756Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:36:56.869Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:36:59.231Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:36:59.560Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:37:15.012Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:39:35.638Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:41:45.963Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:41:46.370Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:42:28.634Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:42:29.198Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:42:31.500Z ::ffff:127.0.0.1 : Ping
2026-06-25T16:42:31.578Z ::ffff:127.0.0.1 : Ping

View File

@@ -20,7 +20,8 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById('state-theta').textContent = fmt(p.b*180/Math.PI);
document.getElementById('state-psi').textContent = fmt(p.c*180/Math.PI);
document.getElementById('state-e').textContent = fmt(m.e*180/Math.PI);
// Greifer-Öffnung in mm (Workspace) — keine Grad-Umrechnung.
document.getElementById('state-e').textContent = fmt(p.e);
@@ -33,6 +34,7 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById('motor-b').textContent = fmt(m.b*180/Math.PI);
document.getElementById('motor-c').textContent = fmt(m.c*180/Math.PI);
// Greifer-Motorwert (eMotor, abgeleitet aus e/b/c) — roh, wie motor-x.
document.getElementById('motor-e').textContent = fmt(m.e);
})

View File

@@ -40,19 +40,24 @@ class GCode{
static getM114(robot){
// position = Workspace (x/y/z in mm, a/b/c als Euler-Winkel phi/theta/psi in rad,
// e = Greifer-Öffnung in mm).
// motorCounts = die 7 Motor-Slots, inkl. e = eMotor (abgeleiteter Greifer-Motorwert,
// NICHT die mm-Öffnung — die steht in position.e).
let text = '{"position":{ "x":'+robot.x+
', "y":'+robot.y+
', "z":'+robot.z+
', "a":' +robot.phi +
', "b":' +robot.theta +
', "c":' +robot.psi + '},' +
', "c":' +robot.psi +
', "e":' +(robot.e ?? 0) + '},' +
'"motorCounts":{ "x":'+ robot.xMotor +
', "y":'+ robot.alpha +
', "z":'+ robot.beta +
', "a":'+ robot.a +
', "b":'+ robot.b +
', "c":'+ robot.c +
', "e":'+ (robot.e ?? 0) +
', "e":'+ (robot.eMotor ?? 0) +
'}}';
return text;
}

View File

@@ -264,6 +264,21 @@ class RobotBase{
};
}
/**
* Greifer-Öffnung (Workspace, `e` in mm) → Greifer-Motorwert (`eMotor`).
*
* Default: keine Kopplung (`eMotor = e`). Kinematiken, bei denen die Greifer-Sehne
* mechanisch durchs Handgelenk läuft, überschreiben dies, um die Handgelenk-Winkel
* herauszurechnen (siehe {@link Arm3SegmentLinearX}). Wird sowohl von
* `calculateAngles3D()` als auch beim Setzen per G92/M92 genutzt — eine Quelle.
*
* @param {number} e Finger-Öffnung in mm (ab Null-Position)
* @returns {number} zugehöriger Greifer-Motorwert
*/
gripperMotorFromOpening(e) {
return e;
}
/**
* Rückwärts-Kinematik: Motorwinkel → Workspace-Koordinaten.
*

View File

@@ -8,7 +8,7 @@
* der Controller kennt nur strukturierte Befehle, keine rohen Textstrings.
*/
const GCodeParser = require('./GCodeParser');
const { motorStateFromPorts } = require('./portInverse');
const { motorStateFromPorts, D } = require('./portInverse');
class RobotController {
@@ -121,14 +121,31 @@ class RobotController {
}
if (cmd === 'M92' || cmd === 'G92') {
// Beide setzen die Motorposition ohne Bewegung, unterscheiden sich aber in den
// Winkel-EINHEITEN:
// G92 → GRAD (G-Code-Konvention für Rotationsachsen, wie FluidNC und die
// "Position Motoren"-Anzeige in public/app.js). Intern sind die
// Winkel-Slots in Radiant → Grad/D umrechnen (D = 180/π).
// M92 → RADIANT, roh in die internen Slots (interne/Test-Variante).
// X ist die lineare mm-Schiene, E die Greifer-Öffnung in mm (ab Null-Position
// eines Fingers) — beide ohne Winkel-Umrechnung.
const angScale = (cmd === 'G92') ? 1 / D : 1;
robot.createMotorPosition();
if (Number.isFinite(params.X)) { robot.xMotor = params.X; robot.xMotorChanged = true; }
if (Number.isFinite(params.Y)) { robot.alpha = params.Y; robot.yMotorChanged = true; }
if (Number.isFinite(params.Z)) { robot.beta = params.Z; robot.zMotorChanged = true; }
if (Number.isFinite(params.A)) { robot.a = params.A; robot.aMotorChanged = true; }
if (Number.isFinite(params.B)) { robot.b = params.B; robot.bMotorChanged = true; }
if (Number.isFinite(params.C)) { robot.c = params.C; robot.cMotorChanged = true; }
if (Number.isFinite(params.E)) { robot.e = params.E; robot.eMotorChanged = true; }
if (Number.isFinite(params.X)) { robot.xMotor = params.X; robot.xMotorChanged = true; }
if (Number.isFinite(params.Y)) { robot.alpha = params.Y * angScale; robot.yMotorChanged = true; }
if (Number.isFinite(params.Z)) { robot.beta = params.Z * angScale; robot.zMotorChanged = true; }
if (Number.isFinite(params.A)) { robot.a = params.A * angScale; robot.aMotorChanged = true; }
if (Number.isFinite(params.B)) { robot.b = params.B * angScale; robot.bMotorChanged = true; }
if (Number.isFinite(params.C)) { robot.c = params.C * angScale; robot.cMotorChanged = true; }
// E nach B/C setzen: der Greifer-Motorwert hängt über die Kinematik-Kopplung
// von b und c ab. robot.e = Finger-Öffnung (mm), eMotor = abgeleiteter Motorwert.
// Ohne diese eMotor-Ableitung bliebe der Greiferwert stale (alte E-Inkonsistenz):
// sendCommand() verschickt eMotor, nicht e.
if (Number.isFinite(params.E)) {
robot.e = params.E;
robot.eMotor = robot.gripperMotorFromOpening(robot.e);
robot.eMotorChanged = true;
}
robot.calculatePositionFromMotorAngles();
robot.sendCommand('G92');

View File

@@ -95,7 +95,17 @@ class Arm3SegmentLinearX extends RobotBase {
while(this.a > Math.PI){this.a -= 2*Math.PI}
while(this.a < -Math.PI){this.a += 2*Math.PI}
this.eMotor = this.e - this.b - this.c;
this.eMotor = this.gripperMotorFromOpening(this.e);
}
/**
* Greifer-Kopplung dieses Arms: die Finger-Sehne läuft durchs Handgelenk, daher
* ziehen Knick (`b`) und Dreh (`c`) am Greifer mit. `eMotor` kompensiert das, damit
* die Finger-Öffnung `e` (mm, ab Null-Position eines Fingers) unabhängig von der
* Handstellung bleibt. Einzige Quelle für diese Kopplung (auch via G92/M92 genutzt).
*/
gripperMotorFromOpening(e) {
return e - this.b - this.c;
}
calculatePositionFromMotorAngles(verbose = false) {

View File

@@ -113,5 +113,22 @@ describe("Robot G92", () => {
// ("Wenn nur G92 x3 gegeben wird, dann wird trotzdem auch y und z gesendet. schlecht." );
});
test("G92 E: Greifer-Öffnung (mm) → eMotor über Kopplung e - b - c", () => {
const robot = new Robot(300, 300, 20);
const D = 180 / Math.PI;
// B/C in Grad rein (→ intern Radiant), E in mm. eMotor muss aus e, b, c abgeleitet
// werden — die Greifer-Sehne läuft durchs Handgelenk (Arm3SegmentLinearX-Kopplung).
GCode.receiveGCode(robot, "G92 B30 C-45 E10");
expect(robot.b).toBeCloseTo(30 / D, 6); // 30° → rad
expect(robot.c).toBeCloseTo(-45 / D, 6); // -45° → rad
expect(robot.e).toBe(10); // mm, unverändert
expect(robot.eMotor).toBeCloseTo(10 - robot.b - robot.c, 6); // = e - b - c
// Konsistenz: identische Kopplung wie der reguläre Bewegungspfad (calculateAngles3D).
expect(robot.eMotor).toBeCloseTo(robot.gripperMotorFromOpening(robot.e), 12);
});
});

View File

@@ -204,7 +204,8 @@ describe('InfoServer', () => {
const httpsOptions = { key, cert, passphrase: 'abcd' };
const sharedState = { connectedClients: [], lastCommands: [], lastPings: [] };
const robot = { x: 10, y: 20, z: 30, phi: 0.1, theta: 0.2, psi: 0.3, xMotor: 0, alpha: 0, beta: 0, a: 0, b: 0, c: 0 };
// e = Greifer-Öffnung (mm) → position.e; eMotor = Greifer-Motorwert → motorCounts.e.
const robot = { x: 10, y: 20, z: 30, phi: 0.1, theta: 0.2, psi: 0.3, xMotor: 0, alpha: 0, beta: 0, a: 0, b: 0, c: 0, e: 2.5, eMotor: 7 };
const senders = [];
server = createInfoServer(httpsOptions, sharedState, robot, GCode, senders);
@@ -214,7 +215,8 @@ describe('InfoServer', () => {
expect(statusCode).toBe(200);
const json = JSON.parse(body);
expect(json.position).toEqual({ x: 10, y: 20, z: 30, a: 0.1, b: 0.2, c: 0.3 });
expect(json.position).toEqual({ x: 10, y: 20, z: 30, a: 0.1, b: 0.2, c: 0.3, e: 2.5 });
expect(json.motorCounts.e).toBe(7); // Motorwert (eMotor), nicht die mm-Öffnung
});
test('returns 404 for unknown endpoints', async () => {

View File

@@ -101,7 +101,7 @@ describe('InputWS API response routing', () => {
a.send('M114');
const parsed = JSON.parse(await aReply);
expect(parsed.position).toEqual({ x: 5, y: 6, z: 7, a: 0, b: 0, c: 0 });
expect(parsed.position).toEqual({ x: 5, y: 6, z: 7, a: 0, b: 0, c: 0, e: 0 });
expect(await bSilent).toBe(true);
a.close();
@@ -123,8 +123,8 @@ describe('InputWS API response routing', () => {
const aParsed = JSON.parse(await aReply);
const bParsed = JSON.parse(await bReply);
expect(aParsed.position).toEqual({ x: 1, y: 2, z: 3, a: 0, b: 0, c: 0 });
expect(bParsed.position).toEqual({ x: 1, y: 2, z: 3, a: 0, b: 0, c: 0 });
expect(aParsed.position).toEqual({ x: 1, y: 2, z: 3, a: 0, b: 0, c: 0, e: 0 });
expect(bParsed.position).toEqual({ x: 1, y: 2, z: 3, a: 0, b: 0, c: 0, e: 0 });
expect(robot.sendCommand).toHaveBeenCalled();
a.close();

View File

@@ -88,7 +88,7 @@ describe('InputWS', () => {
const message = await messagePromise;
const parsed = JSON.parse(message);
expect(parsed.position).toEqual({ x: 12, y: 34, z: 56, a: 1, b: 2, c: 3 });
expect(parsed.position).toEqual({ x: 12, y: 34, z: 56, a: 1, b: 2, c: 3, e: 0 });
expect(parsed.motorCounts).toBeDefined();
client.close();
@@ -108,7 +108,7 @@ describe('InputWS', () => {
const message = await messagePromise;
const parsed = JSON.parse(message);
expect(parsed.position).toEqual({ x: 1, y: 2, z: 3, a: 0, b: 0, c: 0 });
expect(parsed.position).toEqual({ x: 1, y: 2, z: 3, a: 0, b: 0, c: 0, e: 0 });
expect(robot.sendCommand).toHaveBeenCalled();
client.close();

View File

@@ -65,20 +65,38 @@ describe('RobotController (ToDo_6)', () => {
expect(robot.sendCommand).toHaveBeenCalledWith('G92');
});
test('applyCommand: G92 verhält sich identisch zu M92 (Bug 3)', () => {
test('applyCommand: G92 interpretiert Winkel als Grad (→ rad), X bleibt mm', () => {
const robot = createDummyRobot();
robot.createMotorPosition = jest.fn();
// G92 nutzt die G-Code-Konvention (Grad). Intern landen die Winkel in Radiant,
// X (lineare Schiene) bleibt unverändert in mm. Vgl. M92 oben (Roh-Radiant).
RobotController.applyCommand(robot, { command: 'G92', params: { X: 5, Y: 0.5, A: 0.3 } });
const DEG2RAD = Math.PI / 180;
expect(robot.createMotorPosition).toHaveBeenCalledTimes(1);
expect(robot.xMotor).toBe(5);
expect(robot.alpha).toBe(0.5);
expect(robot.a).toBe(0.3);
expect(robot.alpha).toBeCloseTo(0.5 * DEG2RAD, 10);
expect(robot.a).toBeCloseTo(0.3 * DEG2RAD, 10);
expect(robot.calculatePositionFromMotorAngles).toHaveBeenCalled();
expect(robot.sendCommand).toHaveBeenCalledWith('G92');
});
test('applyCommand: G92 E setzt Greifer-Öffnung (mm) und leitet eMotor ab', () => {
const robot = createDummyRobot();
robot.createMotorPosition = jest.fn();
robot.b = 0.2; // Handgelenk-Knick
robot.c = -0.5; // Hand-Dreher
// E ist mm (keine Grad/rad-Umrechnung). eMotor wird über die Greifer-Kopplung
// aus e, b, c abgeleitet — sonst bliebe der an FluidNC gesendete Wert stale.
RobotController.applyCommand(robot, { command: 'G92', params: { E: 10, B: 0.2 * (180 / Math.PI), C: -0.5 * (180 / Math.PI) } });
expect(robot.e).toBe(10);
expect(robot.eMotor).toBeCloseTo(10 - robot.b - robot.c, 10); // = 10 - 0.2 - (-0.5) = 10.3
expect(robot.sendCommand).toHaveBeenCalledWith('G92');
});
test('applyCommand: ungültiger Befehl wird ignoriert', () => {
const robot = createDummyRobot();
RobotController.applyCommand(robot, null);

View File

@@ -20,12 +20,17 @@ function createDummyRobot() {
a: 0,
b: 0,
c: 0,
eMotor: 0,
// Geometrie
l1: 10,
l2: 10,
l3: 10,
// Greifer-Kopplung (RobotBase-Default: keine Kopplung). Konkrete Kinematiken
// überschreiben dies; siehe Arm3SegmentLinearX.gripperMotorFromOpening.
gripperMotorFromOpening(e) { return e - this.b - this.c; },
// Methoden → jest.fn erlaubt Call-Tracking
calculateAngles3D: jest.fn(),
calculatePositionFromMotorAngles: jest.fn(),