nein, der Pi ist in den BasicSets nicht dabei, es ist ein Shield (stackable), und die Ports sind sogar auch NXT-Sensor/Motor-kompatibel.
Werbung
Hier sieht man gut die Möglichkeiten des BrickPI3. 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.![]()
Geändert von ehenkes (19.07.2020 um 13:40 Uhr)
nein, der Pi ist in den BasicSets nicht dabei, es ist ein Shield (stackable), und die Ports sind sogar auch NXT-Sensor/Motor-kompatibel.
Geändert von HaWe (19.07.2020 um 14:19 Uhr) Grund: BasicSets
Die LEGO-Seite habe ich verstanden, auch den I²C Anschluss. Passt der Raspberry Pi auch noch in den BrickPi3 hinein? Z.B. hier. 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.
Geändert von ehenkes (19.07.2020 um 14:02 Uhr)
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.![]()
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
Pybricks, das Projekt
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
Lächle am Morgen, dann hast du es hinter dir.
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...f?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[color];
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,;
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 >>& 0xFF);
report[p++] =
(byte)(messageCounter & 0xFF);
report[p++] =
(byte)(messageCounter >>;
report[p++] =
0x00; // Direct Command WITH reply
report[p++] =
(byte)(memoryHeader & 0xFF);
report[p++] =
(byte)((memoryHeader >>& 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 >>& 0xFF));
return;
}
buffer.Add(0x83); // LC4
buffer.Add(
(byte)(value & 0xFF));
buffer.Add(
(byte)((value >>& 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);
- - - 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.
Lesezeichen