Archiv verlassen und diese Seite im Standarddesign anzeigen : Einstieg in das EV3 System mit 31313
Einstieg in das EV3 System mit 31313:
Folgendes ist inzwischen passiert (Zielgruppe 8 1/2 Jahre, männlich): Nachdem die optische Programmierung (Scratch) gut ankam, habe ich mir Lego Mindstorms EV3 genauer angeschaut, denn dort wird ebenfalls mit visueller Programmierung (von Labview) gearbeitet. Ein gebrauchtes Set 31313 im Küpper Sortierkasten ersteigert, und schon geht die Bastelei los. Zunächst haben wir nach Rat von LEGO mit dem TRACK3R begonnen. Dieser Bot ist mit seinem Kettenantrieb (fährt locker über Hindernisse wie Kabel) robust und übersichtlich in der Programmierung, da er zunächst nur Aktoren (zwei große Motoren, ein mittlerer Motor) aktiv einsetzt, und zugleich lustig im Einsatz.
Zwei Dinge nervten zunächst:
die Übertragung der Hex-Dateien mittels usb-Kabel (Gefummel beim Einstecken am eingebauten Brick) <-- gelöst mittels usb-Bluetooth Dongle "iAmotus Bluetooth Adapter, Wireless USB Adapter 4.0 EDR Plug and Play Dongle Stick" (Koppelung mit V3 Brick klappt mühelos, wenn man weiß, wie man das in Win10 mit dem Plus-Symbol anstößt)
der Kanalschalter der (trist wirkenden) IR-Fernbedienung im EV3 Set <-- abgelöst durch die (modellspezifische) LEGO Commander App für das Smartphone (einfach genial gemacht).
Nachdem diese lästigen Punkte behoben sind, geht es gut vorwärts. Erst kommt die Programmierung der Aktoren (Steuerung vor/zurück/Kurven/Drehungen/...), dann Schleifen in der Programmierung, jetzt sind wir an den Sensoren. Man muss sich halt als begleitender Erwachsener in die Blöcke einarbeiten. Leider gibt es da keinen Rechtsklick mit Erklärungen. Man findet aber gute Hilfen, wenn man sucht. In Deutsch habe ich allerdings noch nichts gefunden. Toll ist der Geräusch-Editor im Werkzeugkasten. Da kann man mit dem Headset ruckzuck eigene kurze Ansagen aufnehmen und im Klangblock abspielen lassen. Hier suche ich noch Übernahme aus WAV oder mp3.
Langfristig kann man den EV3 (ARM926EJ-S mit 64 MB RAM und stark erweiterbaren 16 MB Flash) mit vereinfachtem BASIC, JAVA oder Python betreiben, wenn man textuelle Programmierung benötigt. Das ist mit 8 1/2 aber noch einige Zeit weg. An die Sensorenschiene (I²C) kommt man wohl auch ran, die Ports (je 4 für Aktoren und 4 für Sensoren) soll man auch erweitern können. Am Anfang geht es aber um den Einstieg und Abwechslung.
Das Wichtigste: Man hat zusammen Spaß, kann (Erklär-/Vorführ-Videos drehen, ...). Mein neuer Curved Monitor ist für die optische Programmierung ideal, denn das wird schnell fummelig an zu kleinen Monitoren. "Zeilenumbruch" habe ich noch nicht gefunden. https://www.c-plusplus.net/forum/plugins/nodebb-plugin-emoji/emoji/emoji-one/1f609.png?v=13up5dhfk1u
ich habe mich bewusst für die HOME-Version entschieden, da sie mehr Spaß bringt als die EDUCATIONAL-Version, mit der Lehrer ihre Schüler belustigen können. Außerdem kann man upgraden. Ich hoffe, dass die Preise fallen, wenn das neue System im August bei LEGO erscheint. Hier im Roboternetz sieht man dieses eher als Vereinfachung, so dass ich zunächst bei dem ausgereiften EV3 mit großer Community bleibe.
Nach dem TRACK3R kam jetzt auf Wunsch des Enkels der SPIK3R dran (Skorpion, der krabbelt, zuschnappt und Kugeln - viel Spaß beim Suchen - aus dem Stachel schießt <-- echt genial gelöst von LEGO).
Schaut euch mal an, was man alles mit EV3 machen kann:
https://www.youtube.com/watch?v=SBOdGG4sx4w (Lego Mindstorms EV3 Rubik's Cube Solver)
... und wenn es langweilig oder zu eng wird: ab damit in die Bucht. ;)
ja, man kann schon einiges damit anstellen, allerdings läuft der Cubesolver auch schon auf dem Vorgänger NXT,
https://www.youtube.com/watch?v=ibcrg3Jy6Og
und das C-ähnliche NXC ist weit mächtiger als das SmallBasic auf dem EV3, auch sind UART und I2C auf dem NXT weitaus kompatibler zu Standard-Devices, im Gegensatz zum EV3.
Die GUI-Programmierung des NXT war auch intuitiver und übersichtlicher als die auf dem EV3.
Mit dem NXT habe ich z.B. sogar schon per NXC einen autonomen Schachroboter programmiert.
http://www.youtube.com/watch?v=Cv-yzuebC7E
Andererseits kann man auf dem EV3 auch Debian als OS auf einer SD installieren (eines der letzten großen Abenteuer auf dieser Welt) und laden (statt des default Texas Instruments Linux) und dann per Python programmieren .... naja, wer's mag... , aber "echtes" gnu C(++) ist ein wahrer Graus auf dem EV3.
Das sind alles allerdings eh keine Gesichtspunkte für 10-jährige (oder gar jüngere).
Schön, dass das NXT bereits richtig gut war.
Was kann man eigentlich mit dem usb-Anschluss des EV3 für tolle Sachen anfangen? (außer WLAN und Reihenschaltung von bis zu vier EV3 Bricks)
Gibt es ein Erweiterungsset von 31313 zu Educational 45544? Ich finde das nirgends.
Gibt es interessante Anwendungen mit zwei EV3 Robotern, die miteinander kommunizieren?
Gibt es interessante Anwendungen mit zwei bis vier EV3 Bricks in Reihe?
Der Original Lego Bytecodeinterpreter ist extrem langsam, was es für zeitkritische Anwendungen problematisch macht (z.B. für mehr als 4 kaskadierte Motoren).
Extension-Pakete von Home auf Edu gab es früher zuhauf von Privatanbietern, die die Sets selber zusammengestellt haben, aber nichts original von Lego. Viele haben sich die fehlenden Teile aber stattdessen selber bei Lego oder über Ebay etc. einzeln nachgekauft (z.B. Gyro oder US-Sensor, IIRC).
Nach meinen Tests funktionieren die EV3-Modelle ganz gut, wenn man sie einzeln wie vorgesehen mit den Standard-Set-Bauteilen verwendet, sind aber sind extrem unflexibel und eingeschränkt, wenn man sie erweitern will. Sinnvolle Erweiterungen über die Standardmodelle/-anwendungen hinaus sind mit Debian OS möglich (ev3dev), aber das ist nur was für CT/IT-Fachleute, nichts für 10-jährige (oder jünger). Ich selber hatte den EV3 damals aber recht schnell wieder verkauft.
Sicher findest du aber Anregungen per Google oder in der Facebook-EV3-Gruppe: Da solltest du dich mal registrieren und dort fragen!
Ich sehe den EV3 als interessanten Einstieg für den Enkel in die Programmierung. Es regt die Phantasie an und bereitet Kindern Spaß. Welches käufliche System würdest du denn empfehlen, wenn der Enkel mal 10-12 Jahre ist? Ich habe da nichts gefunden.
ab 10 Jahre halte ich den EV3 durchaus für einigermaßen geeignet, wenn man bei den Basis-Beispielen bleibt (edit: oder ntl MOCs, die man mit 1 EV3 und seinen vorhandenen Anschluss-Ports problemlos selber entwickeln kann), und dann sind auch die GUI-Blöcke gerade noch OK: man sollte IMO aber nicht anfangen, großartige Erweiterungen mit Fremdsensoren oder Multi-Bricks etc bauen zu wollen.
Wenn ein Grundstufen-Schüler selber programmieren lernen möchte, stehen aber mit den Basis-Sets (egal ob Home oder EDU) Lernkurve und Erfolgserlebnis durchaus in einem guten Verhältnis. Wenn man sich an größere bzw. codemäßig anspruchsvollere Projekte heranwagt (wie den Cubesolver oder einen Segway), werden Schüler aber sonst zu schnell verleitet, einfach nur mal eben schnell ein Modell nach Anleitung aufzubauen, das fertige Programm von irgendwo down- und dann up-zu-loaden und damit zu spielen, als wäre es ein Roboter von der Stange: das macht zwar durchaus Spaß, aber man lernt dabei nicht programmieren. Auch kann ein Otto-Normal-Hobbyprogrammierer Programme wie für den Cubesolver oder Segway auch noch nicht einmal nachvollziehen und/oder verstehen (noch nicht mal in Schriftsprache, geschweigedenn in Bildchen-Puzzle).
Den NXT hatte ich bislang für Anfänger für weitaus geeigneter gehalten, sogar auch schon den RCX, aber hier kommt man heute an größere Hürden, was modernere Betriebssysteme (Windows 8 oder 10) angeht, oft ist schon Win7/32 zuviel oder es geht ohne XP/32 schon gar nichts mehr (64bit OS waren immer schon schwierig) - von daher sind die eigentlich 'raus.
Also traut euch ruhig an den EV3 ran, evt. kauf (später!) noch EDU-Teile dazu, dann hat man duchaus ein paar Jahre Freude daran. Aber erwarte nicht zuviel mit großartiger Erweiterbarkeit u/o "real-world-Kompatibilität" (i2c, SPI, ADC, DAC, UART), da wird man schnell scheitern.
Wenn man dann auf einem Level angelangt ist, dass man Python oder EV3Basic erfolgreich nutzt, ist dann (später) der Schritt zu Arduino oder RaspberryPi (ggf. mit BrickPi Shield) nicht mehr fern.
Vom neuen 51515 https://www.golem.de/news/lego-das-erste-lego-mindstorms-nach-sieben-jahren-2006-149083.html weiß ich zuwenig bezüglich seiner Möglichkeiten, der ist aber ebenfalls für 10+.
Für jüngere Kinder (7+) gibt es auch noch ein simpleres System "Boost" https://www.lego.com/de-de/product/boost-creative-toolbox-17101.
Aber du hast ja nun den 31313, also fang ruhig mal damit an, mit einfachen kleinen Programmier-Schritten.
Danke für den Hinweis in Richtung "Arduino oder RaspberryPi (ggf. mit BrickPi Shield)".
wenn dich BrickPi interessiert: guck mal hier, als Einstieg oder als Erweiterung für später:
https://www.ebay.de/itm/184367281318
:-)
Danke für die Info! Diesen BrickPi 3 kannte ich noch gar nicht. Das wäre wirklich eine interessante Weiterentwicklung von EV3.
Ich habe noch ein Educational Set 45544 (gebraucht) günstig ergattert, und das Erweiterungsset 45560 habe ich ebenfalls (z.Z. sehr preisgünstig auf a...) erworben. Damit kann ich auch zwei Systeme gleichzeitig aufbauen und mit den Enkeln untersuchen. Mit dem EDU Set hat man z.B. den Ultraschallsensor dabei, den man gegen den IR-Sensor des SPIK3R austauschen kann.
Ultraschallsensorik hatte ich schon beim Asuro nachgerüstet. Damit kann man viel exakter messen.
https://www.henkessoft.de/Roboter/Bilder/ASURO_US_1.JPG
PS: Der von dir verlinkte BrickPi 3 hat "2 Motoranschlüsse defekt (dummerweise wegen Überlastung, rot markiert)" <-- Das passiert bei EV3 hoffentlich nicht
PS: Der von dir verlinkte BrickPi 3 hat "2 Motoranschlüsse defekt (dummerweise wegen Überlastung, rot markiert)" <-- Das passiert bei EV3 hoffentlich nicht
da würde ich nicht drauf wetten, denke aber auch, dass sie bei Lego besser gegen Kurzschluss abgesichert sind - das ist da ja eher "fool-proof" ;)
Hier sieht man gut die Möglichkeiten des BrickPI3 (http://www.robotshop.com/de/de/brickpi3-basiskit.html?utm_source=google&utm_medium=surfaces&utm_campaign=surfaces_across_google_dede&gclid=EAIaIQobChMIqLz_16jZ6gIVk813Ch3h7gLcEAQYASAB EgKxA_D_BwE). Interessante Perspektive. Ist der Raspberry Pi in dem Brick bereits enthalten, oder ist das nur ein Interface zwischen Lego und Raspberrv PI?
Wie auch immer, das spricht doch alles sehr für EV3 als Einstieg. :)
nein, der Pi ist in den BasicSets nicht dabei, es ist ein Shield (stackable), und die Ports sind sogar auch NXT-Sensor/Motor-kompatibel.
Die LEGO-Seite habe ich verstanden, auch den I²C Anschluss. Passt der Raspberry PI auch noch in den BrickPi3 hinein? Z.B. hier (https://www.dexterindustries.com/store/brickpi3-starter-kit/). Ist da der Raspberry Pi integriert? Da steht, dass ein Raspberry Pi 3 dabei ist. Wo genau wird dieser angeschlossen?
Zurzeit hat der EV3 halt viele Vorteile, weil er kompakt ist und seine Akkus innen dabei hat. Das Display ist auch brauchbar. Dazu Bluetooth. Nachteil ist, dass ein Akku-Wechsel nicht ganz einfach ist.
Auf jeden Fall ist das eine klasse Sache, dass man da weiter wachsen kann, noch dazu recht flexibel. Danke nochmals für den wichtigen Hinweis.
ja, dort ist noch ein Pi3 dabei, vlt aber noch der alte, nicht der neue 3B+, weiß ich aber nicht. Der BrickPi3 ist ein Shield zum Aufstecken (26-pol Header) , da passt nichts "hinein".
- - - Aktualisiert - - -
PS,
neues Thema => bitte neues Topic!
Ja, wir bleiben zunächst beim EV3. Alleine die Schlange "R3PTAR" ist schon den Kauf wert. Eine Meisterleistung von LEGO und auch bezüglich Programmierung interessant. :)
Robzilla
18.09.2020, 14:21
ehenkes was kannst du über die R3PTAR berichten?
Die Schlange R3PTAR ist einfach klasse. Der Überraschungseffekt, wenn sie mit ihren Zähnen zuschnappt, das gefällt vor allem den Kindern. Aufbau und Programmierung sind ebenfalls gut gemacht. Für mich ein Highlight.
Noch ein Nachtrag der von Interesse sein könnte...
Meine beiden Söhne arbeiten im Gymnasium im Informatik-Unterricht mit Python (Tigerjython). Nachdem ich Python auch für verschiedene Dinge verwende habe ich da mal etwas tiefer nachgegraben. Als erstes fand ich Neuigkeiten bei Texas Instruments, die für Ihre CAS und GTR seit kurzem ein Update mit Circuitpython als Programmiersprache anbieten. Da lag es nahe, auch mal wieder bei LEGO nachzusehen und siehe da, für die Hubs (EV3, Inventor...) von LEGO gibt es jetzt ebenfalls eine Variante mit Python.
Lego, offiziell (https://education.lego.com/de-de/product-resources/mindstorms-ev3/materialien-f%C3%BCr-lehrkr%C3%A4fte/mit-python-programmieren)
Pybricks, das Projekt (https://pybricks.com/)
Was soll ich sagen, die Kiste mit den EV3 und NXT-Teilen wurde umgehend entstaubt und es rollen und robben wieder die wunderlichsten Systeme durch die Wohnung. Zumindest um Informatik muss ich mir jetzt in "Home-Schooling-Zeiten" keine Gedanken mehr machen. ;-)
Gruß Jay
Wir sind nun im Zeitalter der KI angekommen. Daher habe ich mit ChatGPT diskutiert, was man so mit den alten EV3 Sets machen könnte. Zunächst habe ich den "Farbsortierer" https://assets.education.lego.com/v3/assets/blt293eea581807678a/bltc07cd1e69ab4c867/5f8801f31e95ad78fa1e8bac/ev3-model-core-set-color-sorter.pdf?locale=de-de verwendet, da ich mit der visuellen Lego-Programmierung Probleme hatte, die Farben grün und blau sicher zu unterscheiden. ChatGPT-5.6 Sol hat zunächst via usb-Anbindung mit mir zusammen recht schnell eine C# .Net Konsolen-Anwendung gebastelt, die gut funktioniert. Als Modul in .Net wurde HidSharp verwendet, da ein LegoMindstorms-Modul nicht funktionierte.
Längerfristig ist eine Idee, zwei EV3-Systeme zusammen mit einer zentralen Steuerung auf dem PC mit WebCams verschiedene Aufgaben erledigen zu lassen.
Hier ist das aktuelle C#-Programm:
// ================================================== ==========================
// LEGO MINDSTORMS EV3 Color Sorter
//
// PC-controlled version using direct EV3 USB/HID commands.
// No program has to be downloaded to the EV3 brick.
//
// Hardware:
// Motor A - eject/feed mechanism
// Motor D - sorter carriage positioning mechanism
// Sensor S1 - touch sensor / carriage home switch
// Sensor S3 - EV3 color sensor
//
// NuGet:
// HidSharp
// ================================================== ==========================using System.Buffers.Binary;
using System.Diagnostics;
using HidSharp;
namespace Ev3ColorSorter;
internal static class Program
{
private static void Main()
{
Console.WriteLine("LEGO EV3 Color Sorter - C# USB Control");
Console.WriteLine("--------------------------------------");
Console.WriteLine();
try
{
using var ev3 = Ev3Brick.ConnectUsb();
var classifier = ColorClassifier.CreateFromLearnedCentroids();
var sorter = new ColorSorterController(ev3, classifier);
sorter.RunInteractive();
}
catch (Exception ex)
{
Console.WriteLine();
Console.WriteLine("ERROR:");
Console.WriteLine(ex.Message);
}
Console.WriteLine();
Console.WriteLine("Program finished.");
}
}
internal sealed class ColorSorterController
{
private readonly Ev3Brick _ev3;
private readonly ColorClassifier _classifier;
private static readonly Dictionary<SortColor, int> BinPositions =
new()
{
[SortColor.Blue] = 10,
[SortColor.Green] = 132,
[SortColor.Yellow] = 360,
[SortColor.Red] = 530
};
private const int BatchCapacity = 8;
// Raw RGB brightness required to consider an object present.
private const int ObjectBrightnessThreshold = 10;
// Reject classifications that are too close to a second color.
private const double MinimumConfidence = 0.20;
private bool _ejectorHomed = false;
public ColorSorterController(
Ev3Brick ev3,
ColorClassifier classifier)
{
_ev3 = ev3;
_classifier = classifier;
}
public void RunInteractive()
{
Console.WriteLine("EV3 connected successfully.");
Console.WriteLine();
Console.WriteLine("Commands:");
Console.WriteLine(" H = Home sorter carriage (Motor D)");
Console.WriteLine(" E = Test one eject cycle (Motor A)");
Console.WriteLine(" P = Test sorter bin positions (Motor D)");
Console.WriteLine(" R = Run one complete sorting batch");
Console.WriteLine(" Q = Quit");
Console.WriteLine();
while (true)
{
Console.Write("> ");
ConsoleKey key = Console.ReadKey(true).Key;
try
{
switch (key)
{
case ConsoleKey.H:
HomeCarriage();
break;
case ConsoleKey.E:
TestEjector();
break;
case ConsoleKey.P:
TestBinPositions();
break;
case ConsoleKey.R:
RunBatch();
break;
case ConsoleKey.Q:
SafeStop();
return;
}
}
catch (Exception ex)
{
SafeStop();
Console.WriteLine();
Console.WriteLine($"Operation aborted: {ex.Message}");
Console.WriteLine();
}
}
}
private void RunBatch()
{
Console.WriteLine();
Console.WriteLine("=== NEW SORTING BATCH ===");
Console.WriteLine();
HomeEjector();
HomeCarriage();
List<SortColor> colors = ScanBatch();
if (colors.Count == 0)
{
Console.WriteLine("No objects registered.");
return;
}
Console.WriteLine();
Console.WriteLine($"Sorting {colors.Count} object(s)...");
Console.WriteLine();
SortBatch(colors);
Console.WriteLine();
Console.WriteLine("Returning carriage to home...");
HomeCarriage();
_ev3.PlayTone(880, 150);
Console.WriteLine();
Console.WriteLine("Batch completed.");
Console.WriteLine();
}
private void HomeCarriage()
{
Console.WriteLine();
Console.WriteLine("Homing sorter carriage...");
// If the switch is already pressed, the carriage is already at home.
if (!_ev3.ReadTouchSensor())
{
// Move toward the home switch.
_ev3.RunMotorPower(
MotorPort.D,
-20);
var timeout = Stopwatch.StartNew();
while (!_ev3.ReadTouchSensor())
{
if (timeout.Elapsed > TimeSpan.FromSeconds(6))
{
_ev3.StopMotor(MotorPort.D);
throw new TimeoutException(
"Carriage home switch was not reached.");
}
Thread.Sleep(20);
}
}
_ev3.StopMotor(MotorPort.D);
Thread.Sleep(200);
// Define this mechanical position as encoder position zero.
_ev3.ClearMotorCount(MotorPort.D);
Thread.Sleep(50);
int position = _ev3.ReadMotorCount(MotorPort.D);
Console.WriteLine(
$"Carriage home position established: {position}°");
}
private List<SortColor> ScanBatch()
{
var result = new List<SortColor>();
Console.WriteLine();
Console.WriteLine("SCAN MODE");
Console.WriteLine(
$"Scan up to {BatchCapacity} objects using the color sensor.");
Console.WriteLine("Press ENTER to start sorting early.");
Console.WriteLine("Press Q to cancel the batch.");
Console.WriteLine();
while (result.Count < BatchCapacity)
{
if (Console.KeyAvailable)
{
ConsoleKey key = Console.ReadKey(true).Key;
if (key == ConsoleKey.Enter && result.Count > 0)
break;
if (key == ConsoleKey.Q)
return new List<SortColor>();
}
RgbRaw raw = _ev3.ReadRgbRaw();
if (raw.Brightness < ObjectBrightnessThreshold)
{
Thread.Sleep(40);
continue;
}
Chromaticity? measured = CaptureStableColor();
if (measured is null)
continue;
Console.WriteLine(
$"Measured: r={measured.Value.R:F3} " +
$"g={measured.Value.G:F3} " +
$"b={measured.Value.B:F3}");
ColorClassification classification =
_classifier.Classify(measured.Value);
if (classification.Confidence < MinimumConfidence)
{
Console.WriteLine(
$"Color uncertain " +
$"({classification.Color}, " +
$"{classification.Confidence:P0}). " +
$"Please scan again.");
_ev3.PlayTone(300, 150);
WaitUntilObjectRemoved();
continue;
}
result.Add(classification.Color);
Console.WriteLine(
$"#{result.Count}: " +
$"{classification.Color,-6} " +
$"confidence {classification.Confidence:P0}");
_ev3.PlayTone(
FrequencyForColor(classification.Color),
100);
WaitUntilObjectRemoved();
}
if (result.Count == BatchCapacity)
{
Console.WriteLine();
Console.WriteLine(
"Batch is full. Place the last object into the chute.");
Console.WriteLine(
"Press ENTER when all objects are ready for sorting.");
while (Console.ReadKey(true).Key != ConsoleKey.Enter)
{
}
}
Console.WriteLine();
Console.WriteLine(
"Registered sequence: " +
string.Join(" -> ", result));
return result;
}
private Chromaticity? CaptureStableColor()
{
const int requestedSamples = 7;
double sumR = 0;
double sumG = 0;
double sumB = 0;
int validSamples = 0;
for (int i = 0; i < requestedSamples; i++)
{
RgbRaw raw = _ev3.ReadRgbRaw();
if (raw.Brightness >= ObjectBrightnessThreshold)
{
Chromaticity c = Chromaticity.FromRaw(raw);
sumR += c.R;
sumG += c.G;
sumB += c.B;
validSamples++;
}
Thread.Sleep(20);
}
if (validSamples < 4)
return null;
return new Chromaticity(
sumR / validSamples,
sumG / validSamples,
sumB / validSamples);
}
private void WaitUntilObjectRemoved()
{
int consecutiveEmptyReadings = 0;
var timeout = Stopwatch.StartNew();
while (timeout.Elapsed < TimeSpan.FromSeconds(10))
{
RgbRaw raw = _ev3.ReadRgbRaw();
if (raw.Brightness < ObjectBrightnessThreshold)
{
consecutiveEmptyReadings++;
if (consecutiveEmptyReadings >= 3)
return;
}
else
{
consecutiveEmptyReadings = 0;
}
Thread.Sleep(30);
}
}
private void SortBatch(IReadOnlyList<SortColor> colors)
{
for (int i = 0; i < colors.Count; i++)
{
SortColor color = colors[i];
int targetPosition = BinPositions;
Console.WriteLine(
$"Object #{i + 1}: {color,-6} -> " +
$"carriage target {targetPosition}°");
_ev3.MoveMotorTo(
MotorPort.D,
targetPosition,
speedPercent: 45);
Thread.Sleep(250);
EjectOneObject();
Thread.Sleep(350);
}
}
private const int EjectorHomeBackoff = 230;
private const int EjectorStroke = 180;
private void HomeEjector()
{
const int homingPower = 40;
Console.WriteLine("Searching for Motor A mechanical stop...");
int previousPosition =
_ev3.ReadMotorCount(MotorPort.A);
int stationarySamples = 0;
var timeout = Stopwatch.StartNew();
_ev3.RunMotorPower(
MotorPort.A,
homingPower);
try
{
while (true)
{
Thread.Sleep(60);
int currentPosition =
_ev3.ReadMotorCount(MotorPort.A);
int movement =
Math.Abs(
currentPosition -
previousPosition);
previousPosition = currentPosition;
if (movement <= 1)
stationarySamples++;
else
stationarySamples = 0;
if (stationarySamples >= 12)
break;
if (timeout.Elapsed > TimeSpan.FromSeconds(5))
{
throw new TimeoutException(
"Motor A mechanical stop was not detected.");
}
}
}
finally
{
_ev3.StopMotor(MotorPort.A);
}
Console.WriteLine("Motor A stop detected.");
Thread.Sleep(150);
// The mechanical stop is our reference position.
int detectedStop = _ev3.ReadMotorCount(MotorPort.A);
Console.WriteLine($"Detected Motor A stop at encoder position: {detectedStop}°");
_ev3.ClearMotorCount(MotorPort.A);
Console.WriteLine($"Backing Motor A away from stop by {EjectorHomeBackoff}°...");
_ev3.MoveMotorRelative(
MotorPort.A,
-EjectorHomeBackoff,
speedPercent: 35);
int position =
_ev3.ReadMotorCount(MotorPort.A);
Console.WriteLine(
$"Motor A home position established: {position}°");
_ejectorHomed = true;
}
private void EjectOneObject()
{
int start = _ev3.ReadMotorCount(MotorPort.A);
Console.WriteLine(
$"Motor A start position: {start}°");
_ev3.MoveMotorRelative(
MotorPort.A,
+EjectorStroke,
speedPercent: 35);
int forward = _ev3.ReadMotorCount(MotorPort.A);
Console.WriteLine(
$"After eject movement: {forward}° " +
$"(delta {forward - start}°)");
Thread.Sleep(150);
_ev3.MoveMotorRelative(
MotorPort.A,
-EjectorStroke,
speedPercent: 35);
int back = _ev3.ReadMotorCount(MotorPort.A);
Console.WriteLine(
$"After return movement: {back}° " +
$"(delta {back - start}°)");
}
private void TestEjector()
{
Console.WriteLine();
if (!_ejectorHomed)
{
Console.WriteLine("Motor A is not initialized yet.");
Console.WriteLine("Homing Motor A...");
HomeEjector();
}
Console.WriteLine("Running one Motor A eject cycle...");
EjectOneObject();
Console.WriteLine("Eject cycle completed.");
Console.WriteLine();
}
private void SafeStop()
{
try
{
_ev3.StopMotor(MotorPort.A);
}
catch
{
}
try
{
_ev3.StopMotor(MotorPort.D);
}
catch
{
}
}
private static int FrequencyForColor(SortColor color)
{
return color switch
{
SortColor.Blue => 600,
SortColor.Green => 750,
SortColor.Yellow => 900,
SortColor.Red => 1050,
_ => 800
};
}
private void TestBinPositions()
{
Console.WriteLine();
Console.WriteLine("=== SORTER POSITION TEST ===");
Console.WriteLine();
HomeCarriage();
SortColor[] testOrder =
{
SortColor.Blue,
SortColor.Green,
SortColor.Yellow,
SortColor.Red
};
foreach (SortColor color in testOrder)
{
int target = BinPositions[color];
Console.WriteLine();
Console.WriteLine(
$"Press ENTER to move to {color} ({target}°), " +
"or Q to abort.");
while (true)
{
ConsoleKey key = Console.ReadKey(true).Key;
if (key == ConsoleKey.Q)
{
Console.WriteLine("Position test aborted.");
HomeCarriage();
return;
}
if (key == ConsoleKey.Enter)
break;
}
_ev3.MoveMotorTo(
MotorPort.D,
target,
speedPercent: 25);
int actual =
_ev3.ReadMotorCount(MotorPort.D);
Console.WriteLine(
$"Motor D position: {actual}° " +
$"(target {target}°)");
Console.WriteLine(
"Check the mechanical alignment before continuing.");
}
Console.WriteLine();
Console.WriteLine("All four positions tested.");
Console.WriteLine("Returning carriage to home...");
HomeCarriage();
Console.WriteLine("Position test completed.");
Console.WriteLine();
}
}
internal sealed class ColorClassifier
{
private readonly Dictionary<SortColor, Chromaticity> _centroids;
private ColorClassifier(
Dictionary<SortColor, Chromaticity> centroids)
{
_centroids = centroids;
}
public static ColorClassifier CreateFromLearnedCentroids()
{
return new ColorClassifier(
new Dictionary<SortColor, Chromaticity>
{
[SortColor.Red] =
new Chromaticity(
0.490,
0.229,
0.281),
[SortColor.Green] =
new Chromaticity(
0.179,
0.458,
0.363),
[SortColor.Blue] =
new Chromaticity(
0.162,
0.186,
0.653),
[SortColor.Yellow] =
new Chromaticity(
0.376,
0.335,
0.289)
});
}
public ColorClassification Classify(
Chromaticity measured)
{
var distances =
_centroids
.Select(
item => new
{
Color = item.Key,
Distance =
measured.DistanceTo(item.Value)
})
.OrderBy(item => item.Distance)
.ToArray();
var best = distances[0];
var second = distances[1];
double confidence;
if (second.Distance <= 1e-12)
{
confidence = 1.0;
}
else
{
confidence =
1.0 -
best.Distance /
second.Distance;
}
confidence =
Math.Clamp(
confidence,
0.0,
1.0);
return new ColorClassification(
best.Color,
best.Distance,
confidence);
}
}
internal sealed class Ev3Brick : IDisposable
{
private const int LegoVendorId = 0x0694;
private const int Ev3ProductId = 0x0005;
private const byte DirectCommandReply = 0x02;
private readonly HidDevice _device;
private readonly HidStream _stream;
private ushort _messageCounter;
private Ev3Brick(
HidDevice device,
HidStream stream)
{
_device = device;
_stream = stream;
_stream.ReadTimeout = 3000;
_stream.WriteTimeout = 3000;
}
public static Ev3Brick ConnectUsb()
{
HidDevice? device =
DeviceList.Local.GetHidDeviceOrNull(
LegoVendorId,
Ev3ProductId);
if (device is null)
{
throw new InvalidOperationException(
"EV3 USB device was not found.");
}
HidStream stream = device.Open();
return new Ev3Brick(
device,
stream);
}
public RgbRaw ReadRgbRaw()
{
var op = new List<byte>();
op.Add(0x99); // opINPUT_DEVICE
op.Add(0x1C); // READY_RAW
AddConstant(op, 0); // Layer
AddConstant(op, 2); // S3 -> internal port 2
AddConstant(op, 29); // EV3 Color Sensor
AddConstant(op, 4); // RGB-RAW mode
AddConstant(op, 3); // Three values
AddGlobalVariable(op, 0);
AddGlobalVariable(op, 4);
AddGlobalVariable(op, http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) ;
byte[] data =
ExecuteDirectCommand(
op,
globalMemoryBytes: 12);
int r =
BinaryPrimitives.ReadInt32LittleEndian(
data.AsSpan(0, 4));
int g =
BinaryPrimitives.ReadInt32LittleEndian(
data.AsSpan(4, 4));
int b =
BinaryPrimitives.ReadInt32LittleEndian(
data.AsSpan(8, 4));
return new RgbRaw(r, g, b);
}
public bool ReadTouchSensor()
{
var op = new List<byte>();
op.Add(0x99); // opINPUT_DEVICE
op.Add(0x1D); // READY_SI
AddConstant(op, 0); // Layer
AddConstant(op, 0); // S1 -> internal port 0
AddConstant(op, 16); // EV3 Touch Sensor
AddConstant(op, 0); // Touch mode
AddConstant(op, 1); // One value
AddGlobalVariable(op, 0);
byte[] data =
ExecuteDirectCommand(
op,
globalMemoryBytes: 4);
int bits =
BinaryPrimitives.ReadInt32LittleEndian(data);
float value =
BitConverter.Int32BitsToSingle(bits);
return value > 0.5f;
}
public int ReadMotorCount(
MotorPort port)
{
var op = new List<byte>();
op.Add(0xB3); // opOUTPUT_GET_COUNT
AddConstant(op, 0);
AddConstant(op, (int)port);
AddGlobalVariable(op, 0);
byte[] data =
ExecuteDirectCommand(
op,
globalMemoryBytes: 4);
return
BinaryPrimitives.ReadInt32LittleEndian(data);
}
// ------------------------------------------------------------------------
// Reset the motor encoder count used for positioning.
// ------------------------------------------------------------------------
public void ClearMotorCount(
MotorPort port)
{
var op = new List<byte>();
op.Add(0xB2); // opOUTPUT_CLR_COUNT
AddConstant(op, 0);
AddConstant(op, MotorMask(port));
ExecuteDirectCommand(
op,
globalMemoryBytes: 0);
}
public void RunMotorPower(
MotorPort port,
int powerPercent)
{
if (powerPercent is < -100 or > 100)
throw new ArgumentOutOfRangeException(
nameof(powerPercent));
int mask = MotorMask(port);
var op = new List<byte>();
op.Add(0xA4); // opOUTPUT_POWER
AddConstant(op, 0);
AddConstant(op, mask);
AddConstant(op, powerPercent);
op.Add(0xA6); // opOUTPUT_START
AddConstant(op, 0);
AddConstant(op, mask);
ExecuteDirectCommand(
op,
globalMemoryBytes: 0);
}
public void StopMotor(
MotorPort port,
bool brake = true)
{
var op = new List<byte>();
op.Add(0xA3); // opOUTPUT_STOP
AddConstant(op, 0);
AddConstant(op, MotorMask(port));
AddConstant(op, brake ? 1 : 0);
ExecuteDirectCommand(
op,
globalMemoryBytes: 0);
}
public void MoveMotorRelative(
MotorPort port,
int degrees,
int speedPercent)
{
if (degrees == 0)
return;
if (speedPercent is <= 0 or > 100)
throw new ArgumentOutOfRangeException(
nameof(speedPercent));
int direction =
Math.Sign(degrees);
int speed =
direction * speedPercent;
int steps =
Math.Abs(degrees);
int mask =
MotorMask(port);
var op = new List<byte>();
op.Add(0xAE); // opOUTPUT_STEP_SPEED
AddConstant(op, 0);
AddConstant(op, mask);
AddConstant(op, speed);
AddConstant(op, 0); // Ramp-up steps
AddConstant(op, steps); // Constant-speed steps
AddConstant(op, 0); // Ramp-down steps
AddConstant(op, 1); // Brake at target
op.Add(0xA6); // opOUTPUT_START
AddConstant(op, 0);
AddConstant(op, mask);
ExecuteDirectCommand(
op,
globalMemoryBytes: 0);
Thread.Sleep(30);
WaitForMotor(
port,
TimeSpan.FromSeconds(5));
}
public void MoveMotorTo(
MotorPort port,
int targetDegrees,
int speedPercent)
{
int current =
ReadMotorCount(port);
int difference =
targetDegrees - current;
MoveMotorRelative(
port,
difference,
speedPercent);
}
private bool IsMotorBusy(
MotorPort port)
{
var op = new List<byte>();
op.Add(0xA9); // opOUTPUT_TEST
AddConstant(op, 0);
AddConstant(op, MotorMask(port));
AddGlobalVariable(op, 0);
byte[] data =
ExecuteDirectCommand(
op,
globalMemoryBytes: 1);
return data[0] != 0;
}
private void WaitForMotor(
MotorPort port,
TimeSpan timeout)
{
var stopwatch =
Stopwatch.StartNew();
while (IsMotorBusy(port))
{
if (stopwatch.Elapsed > timeout)
{
StopMotor(port);
throw new TimeoutException(
$"Motor {port} movement timed out.");
}
Thread.Sleep(25);
}
}
public void PlayTone(
int frequency,
int durationMilliseconds)
{
var op = new List<byte>();
op.Add(0x94); // opSOUND
op.Add(0x01); // TONE
AddConstant(op, 30); // Volume
AddConstant(op, frequency);
AddConstant(op, durationMilliseconds);
ExecuteDirectCommand(
op,
globalMemoryBytes: 0);
}
private byte[] ExecuteDirectCommand(
List<byte> operations,
int globalMemoryBytes)
{
if (globalMemoryBytes is < 0 or > 1023)
{
throw new ArgumentOutOfRangeException(
nameof(globalMemoryBytes));
}
ushort messageCounter =
_messageCounter++;
int memoryHeader =
globalMemoryBytes;
int messageLength =
2 + // Message counter
1 + // Command type
2 + // Memory header
operations.Count;
byte[] report =
new byte[_device.GetMaxOutputReportLength()];
int p = 1; // HID report ID is byte zero.
report[p++] =
(byte)(messageLength & 0xFF);
report[p++] =
(byte)((messageLength >> http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) & 0xFF);
report[p++] =
(byte)(messageCounter & 0xFF);
report[p++] =
(byte)(messageCounter >> http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) ;
report[p++] =
0x00; // Direct Command WITH reply
report[p++] =
(byte)(memoryHeader & 0xFF);
report[p++] =
(byte)((memoryHeader >> http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) & 0xFF);
operations.CopyTo(
report,
p);
_stream.Write(report);
byte[] reply =
new byte[_device.GetMaxInputReportLength()];
int bytesRead =
_stream.Read(reply);
if (bytesRead < 6)
{
throw new IOException(
"Incomplete EV3 reply.");
}
ushort replyCounter =
BinaryPrimitives.ReadUInt16LittleEndian(
reply.AsSpan(3, 2));
if (replyCounter != messageCounter)
{
throw new IOException(
"EV3 reply message counter mismatch.");
}
if (reply[5] != DirectCommandReply)
{
throw new IOException(
$"EV3 returned reply type 0x{reply[5]:X2}.");
}
byte[] result =
new byte[globalMemoryBytes];
if (globalMemoryBytes > 0)
{
Array.Copy(
reply,
6,
result,
0,
globalMemoryBytes);
}
return result;
}
private static void AddConstant(
List<byte> buffer,
int value)
{
if (value is >= -32 and <= 31)
{
buffer.Add(
(byte)(value & 0x3F));
return;
}
if (value is >= sbyte.MinValue and <= sbyte.MaxValue)
{
buffer.Add(0x81); // LC1
buffer.Add(
unchecked((byte)(sbyte)value));
return;
}
if (value is >= short.MinValue and <= short.MaxValue)
{
buffer.Add(0x82); // LC2
short v =
(short)value;
buffer.Add(
(byte)(v & 0xFF));
buffer.Add(
(byte)((v >> http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) & 0xFF));
return;
}
buffer.Add(0x83); // LC4
buffer.Add(
(byte)(value & 0xFF));
buffer.Add(
(byte)((value >> http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) & 0xFF));
buffer.Add(
(byte)((value >> 16) & 0xFF));
buffer.Add(
(byte)((value >> 24) & 0xFF));
}
private static void AddGlobalVariable(
List<byte> buffer,
int byteOffset)
{
if (byteOffset is < 0 or > 31)
{
throw new ArgumentOutOfRangeException(
nameof(byteOffset));
}
buffer.Add(
(byte)(0x60 | byteOffset));
}
private static int MotorMask(
MotorPort port)
{
return 1 << (int)port;
}
public void Dispose()
{
_stream.Dispose();
}
}
// ================================================== ==========================
// Data types
// ================================================== ==========================
internal enum MotorPort
{
A = 0,
B = 1,
C = 2,
D = 3
}
internal enum SortColor
{
Red,
Green,
Blue,
Yellow
}
internal readonly record struct RgbRaw(
int R,
int G,
int B)
{
public int Brightness =>
R + G + B;
}
internal readonly record struct Chromaticity(
double R,
double G,
double B)
{
public static Chromaticity FromRaw(
RgbRaw raw)
{
double sum =
raw.R + raw.G + raw.B;
if (sum <= 0)
return new Chromaticity(0, 0, 0);
return new Chromaticity(
raw.R / sum,
raw.G / sum,
raw.B / sum);
}
public double DistanceTo(
Chromaticity other)
{
double dr =
R - other.R;
double dg =
G - other.G;
double db =
B - other.B;
return Math.Sqrt(
dr * dr +
dg * dg +
db * db);
}
}
internal readonly record struct ColorClassification(
SortColor Color,
double Distance,
double Confidence);
[COLOR=#000000]
- - - Aktualisiert - - -
Mit diesem Programm klappt vor allem die Farberkennung sehr gut, da man das Erkennen einer bestimmten Farbe - der größte Schwachpunkt bei diesem Modell - durch Referenzwerte, die man gezielt für den Aufbau und das Umfeld ermitteln kann, steuern kann.
Ja, und nun wollte ich unbedingt lokale KI (auf Ollama) in das C#/.Net Programm einbinden, um z.B. echte Deadlocks zu überwinden.
Das führte zu einem kleinen webcam-überwachten Feld (1 m²) mit einem LEGO-EV3-Rover, der zwei rote und zwei blaue Kisten in ein vorbestimmtes Feld sortieren sollte.
Auf meiner Website findet sich Video, Sourcecode vom Konsolen-Programm, Marker für den Rover und Log-Datei vom erfolgreichen Versuch.
Viel Spaß damit!
Hier findet sich alles: https://www.henkessoft.de/EV3/EV3.html
Lustiger wird es, wenn man zwei EV3 Rover in einem Kampf mit "augmented reality" gegeneinander antreten lässt.
https://www.youtube.com/watch?v=G5DtExIYVPg
Hier noch ein kleines Programm, mit dem man einen EV3 Brick mit zwei Motoren testen/einbinden kann. Vielleicht als Vorbild für eigene Entwicklungen interessant. Man benötigt keine LEGO Software.
// Man muss mittels NuGet das Paket System.IO.Ports einbinden. Es gibt auch ein Lego Mindstorms Paket, funktioniert nicht zuverlässig.
// Der ausgehende Port für das betroffene Brick ist COM9 (das muss angepasst werden).
// Zwei große Motoren sind an Ports A und D des Bricks angeschlossen.
using System.IO.Ports;
using System.Text;
const string PortName = "COM9";
Console.WriteLine("EV3C Hardware-Test");
Console.WriteLine("==================");
using var ev3 = new Ev3Brick(PortName);
try
{
ev3.Connect();
Console.WriteLine();
Console.WriteLine("SYSTEMINFO EV3C");
Console.WriteLine("----------------");
Ev3SystemInfo info = ev3.ReadSystemInfo();
Console.WriteLine($"Hardware-Version : {info.HardwareVersion}");
Console.WriteLine($"Firmware-Version : {info.FirmwareVersion}");
Console.WriteLine($"Firmware-Build : {info.FirmwareBuild}");
Console.WriteLine($"OS-Version : {info.OsVersion}");
Console.WriteLine($"OS-Build : {info.OsBuild}");
Console.WriteLine($"Batteriespannung : {info.BatteryVoltage:F2} V");
Console.WriteLine();
Console.WriteLine("Motoren bitte an A und D anschließen.");
Console.WriteLine("ENTER startet den Motortest.");
Console.ReadLine();
const int speed = 20;
const int durationMs = 800;
Console.WriteLine();
Console.WriteLine("Motor A vorwärts ...");
ev3.SetMotorSpeed(Ev3Output.A, speed);
Thread.Sleep(durationMs);
ev3.StopMotor(Ev3Output.A);
Thread.Sleep(500);
Console.WriteLine("Motor A rückwärts ...");
ev3.SetMotorSpeed(Ev3Output.A, -speed);
Thread.Sleep(durationMs);
ev3.StopMotor(Ev3Output.A);
Thread.Sleep(800);
Console.WriteLine();
Console.WriteLine("Motor D vorwärts ...");
ev3.SetMotorSpeed(Ev3Output.D, speed);
Thread.Sleep(durationMs);
ev3.StopMotor(Ev3Output.D);
Thread.Sleep(500);
Console.WriteLine("Motor D rückwärts ...");
ev3.SetMotorSpeed(Ev3Output.D, -speed);
Thread.Sleep(durationMs);
ev3.StopMotor(Ev3Output.D);
Console.WriteLine();
Console.WriteLine("Test beendet.");
}
catch (Exception ex)
{
Console.WriteLine();
Console.WriteLine("FEHLER:");
Console.WriteLine(ex);
}
finally
{
try
{
ev3.StopAllMotors();
}
catch
{
// Beim Beenden nichts mehr erzwingen.
}
}
// ================================================== ==============
// EV3
// ================================================== ==============
public enum Ev3Output : byte
{
A = 0x01,
B = 0x02,
C = 0x04,
D = 0x08
}
public sealed record Ev3SystemInfo(
string HardwareVersion,
string FirmwareVersion,
string FirmwareBuild,
string OsVersion,
string OsBuild,
double BatteryVoltage);
public sealed class Ev3Brick : IDisposable
{
private readonly SerialPort port;
private ushort messageCounter = 1;
public Ev3Brick(string portName)
{
port = new SerialPort(
portName,
115200,
Parity.None,
8,
StopBits.One);
port.Handshake = Handshake.None;
port.ReadTimeout = 15000;
port.WriteTimeout = 3000;
}
public void Connect()
{
if (port.IsOpen)
return;
Console.WriteLine($"Öffne {port.PortName} ...");
port.Open();
Console.WriteLine("Bluetooth-Verbindung geöffnet.");
}
// ================================================== ==========
// SYSTEMINFO
// ================================================== ==========
public Ev3SystemInfo ReadSystemInfo()
{
double batteryVoltage =
ReadUiFloat(0x01);
string osVersion =
ReadUiString(0x03, 31);
string hardwareVersion =
ReadUiString(0x09, (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) ;
string firmwareVersion =
ReadUiString(0x0A, (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) ;
string firmwareBuild =
ReadUiString(0x0B, 12);
string osBuild =
ReadUiString(0x0C, 12);
return new Ev3SystemInfo(
hardwareVersion,
firmwareVersion,
firmwareBuild,
osVersion,
osBuild,
batteryVoltage);
}
private double ReadUiFloat(byte subCode)
{
byte[] command =
{
0x81, // opUI_Read
subCode,
0x60 // GV0
};
byte[] data =
SendDirectCommand(
command,
globalMemoryBytes: 4);
return BitConverter.ToSingle(data, 0);
}
private string ReadUiString(
byte subCode,
int maxLength)
{
List<byte> command = new();
command.Add(0x81); // opUI_Read
command.Add(subCode);
command.AddRange(
LCX(maxLength));
command.Add(0x60); // GV0
byte[] data =
SendDirectCommand(
command.ToArray(),
globalMemoryBytes: maxLength);
int zero =
Array.IndexOf(data, (byte)0);
int length =
zero >= 0
? zero
: data.Length;
return Encoding.UTF8
.GetString(data, 0, length)
.Trim();
}
// ================================================== ==========
// MOTOREN
// ================================================== ==========
public void SetMotorSpeed(
Ev3Output output,
int speed)
{
speed =
Math.Clamp(speed, -100, 100);
if (speed == 0)
{
StopMotor(output);
return;
}
List<byte> command = new();
// opOutputSpeed
command.Add(0xA5);
command.AddRange(LCX(0)); // Layer
command.AddRange(LCX((int)output));
command.AddRange(LCX(speed));
// opOutputStart
command.Add(0xA6);
command.AddRange(LCX(0));
command.AddRange(LCX((int)output));
SendDirectCommand(
command.ToArray());
}
public void StopMotor(
Ev3Output output)
{
List<byte> command = new();
command.Add(0xA3); // opOutputStop
command.AddRange(LCX(0)); // Layer
command.AddRange(LCX((int)output));
command.AddRange(LCX(1)); // Brake = true
SendDirectCommand(
command.ToArray());
}
public void StopAllMotors()
{
if (!port.IsOpen)
return;
const int allOutputs = 0x0F;
List<byte> command = new();
command.Add(0xA3); // opOutputStop
command.AddRange(LCX(0));
command.AddRange(LCX(allOutputs));
command.AddRange(LCX(1));
SendDirectCommand(
command.ToArray());
}
// ================================================== ==========
// EV3 DIRECT COMMAND
// ================================================== ==========
private byte[] SendDirectCommand(
byte[] opcodes,
int globalMemoryBytes = 0)
{
if (!port.IsOpen)
throw new InvalidOperationException(
"EV3 ist nicht verbunden.");
ushort counter =
messageCounter++;
int bodyLength =
5 + opcodes.Length;
byte[] packet =
new byte[bodyLength + 2];
packet[0] =
(byte)(bodyLength & 0xFF);
packet[1] =
(byte)(bodyLength >> (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) ;
packet[2] =
(byte)(counter & 0xFF);
packet[3] =
(byte)(counter >> (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) ;
packet[4] = 0x00;
// Direct Command, Reply required
packet[5] =
(byte)(globalMemoryBytes & 0xFF);
packet[6] =
(byte)((globalMemoryBytes >> (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) & 0x03);
Array.Copy(
opcodes,
0,
packet,
7,
opcodes.Length);
port.Write(
packet,
0,
packet.Length);
byte[] lengthBytes =
ReadExactly(2);
int replyLength =
lengthBytes[0] |
(lengthBytes[1] << (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) ;
byte[] reply =
ReadExactly(replyLength);
if (reply.Length < 3)
throw new IOException(
"EV3-Antwort zu kurz.");
ushort replyCounter =
(ushort)(
reply[0] |
(reply[1] << (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) );
if (replyCounter != counter)
{
throw new IOException(
$"Falscher Message Counter: " +
$"{replyCounter} statt {counter}");
}
if (reply[2] != 0x02)
{
throw new IOException(
$"EV3 Direct Command Fehler: " +
$"0x{reply[2]:X2}");
}
byte[] payload =
new byte[globalMemoryBytes];
if (globalMemoryBytes > 0)
{
Array.Copy(
reply,
3,
payload,
0,
globalMemoryBytes);
}
return payload;
}
private byte[] ReadExactly(int count)
{
byte[] buffer =
new byte[count];
int offset = 0;
while (offset < count)
{
int n =
port.Read(
buffer,
offset,
count - offset);
if (n <= 0)
{
throw new IOException(
"Bluetooth-Verbindung unterbrochen.");
}
offset += n;
}
return buffer;
}
// EV3 LCX Parameter-Encoding
private static byte[] LCX(int value)
{
if (value >= -32 &&
value <= 31)
{
return new[]
{
(byte)(value & 0x3F)
};
}
if (value >= -128 &&
value <= 127)
{
return new[]
{
(byte)0x81,
unchecked((byte)value)
};
}
if (value >= short.MinValue &&
value <= short.MaxValue)
{
short v =
(short)value;
return new[]
{
(byte)0x82,
(byte)(v & 0xFF),
(byte)((v >> (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) & 0xFF)
};
}
return new[]
{
(byte)0x83,
(byte)(value & 0xFF),
(byte)((value >> (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) & 0xFF),
(byte)((value >> 16) & 0xFF),
(byte)((value >> 24) & 0xFF)
};
}
public void Dispose()
{
if (port.IsOpen)
{
try
{
StopAllMotors();
}
catch
{
}
port.Close();
}
port.Dispose();
}
}
http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif (http://www.roboternetz.de/phpBB2/images/smiles/icon_cool.gif) entspricht 8 )
Powered by vBulletin® Version 4.2.5 Copyright ©2026 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.