218 lines
9.8 KiB
C#
218 lines
9.8 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO.Ports;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Text.Json;
|
||
using System.Diagnostics;
|
||
using System.Reflection;
|
||
using System.Threading;
|
||
using VisionAsist.SDK;
|
||
|
||
namespace ModuleArduino;
|
||
|
||
public class ArduinoModule : IModule
|
||
{
|
||
public string Name => "ArduinoModule";
|
||
public string Description => "Модуль для работы с COM-портами и прямой прошивки кода в Arduino";
|
||
|
||
public void Settings(object[] args) { }
|
||
|
||
public List<ToolDefinition> GetTools()
|
||
{
|
||
return new List<ToolDefinition>
|
||
{
|
||
new ToolDefinition
|
||
{
|
||
Name = "list_com_ports",
|
||
Description = "Получает список всех доступных COM-портов в системе",
|
||
ParametersSchema = "{\"type\": \"object\", \"properties\": {}}"
|
||
},
|
||
new ToolDefinition
|
||
{
|
||
Name = "send_serial_data",
|
||
Description = "Отправляет строку в указанный COM-порт",
|
||
ParametersSchema = JsonSerializer.Serialize(new
|
||
{
|
||
type = "object",
|
||
properties = new
|
||
{
|
||
port = new { type = "string", description = "Имя порта (например, COM3)" },
|
||
baudRate = new { type = "integer", description = "Скорость передачи (по умолчанию 9600)", @default = 9600 },
|
||
data = new { type = "string", description = "Данные для отправки" }
|
||
},
|
||
required = new[] { "port", "data" }
|
||
})
|
||
},
|
||
new ToolDefinition
|
||
{
|
||
Name = "read_serial_data",
|
||
Description = "Читает любые доступные данные из указанного COM-порта",
|
||
ParametersSchema = JsonSerializer.Serialize(new
|
||
{
|
||
type = "object",
|
||
properties = new
|
||
{
|
||
port = new { type = "string", description = "Имя порта (например, COM3)" },
|
||
baudRate = new { type = "integer", description = "Скорость передачи (по умолчанию 9600)", @default = 9600 },
|
||
timeoutMs = new { type = "integer", description = "Максимальное время ожидания данных в миллисекундах (по умолчанию 3000)", @default = 3000 }
|
||
},
|
||
required = new[] { "port" }
|
||
})
|
||
},
|
||
new ToolDefinition
|
||
{
|
||
Name = "compile_upload_code",
|
||
Description = "Принимает исходный код C++ (Arduino), компилирует его и загружает на плату",
|
||
ParametersSchema = JsonSerializer.Serialize(new
|
||
{
|
||
type = "object",
|
||
properties = new
|
||
{
|
||
code = new { type = "string", description = "Полный текст скетча .ino (включая setup и loop)" },
|
||
port = new { type = "string", description = "Имя порта (например, COM3)" },
|
||
boardType = new { type = "string", description = "Тип платы (например, arduino:avr:uno)" }
|
||
},
|
||
required = new[] { "code", "port", "boardType" }
|
||
})
|
||
}
|
||
};
|
||
}
|
||
|
||
public string Execute(string toolName, string argumentsJson)
|
||
{
|
||
try
|
||
{
|
||
var doc = JsonDocument.Parse(argumentsJson);
|
||
var root = doc.RootElement;
|
||
|
||
switch (toolName)
|
||
{
|
||
case "list_com_ports":
|
||
var ports = SerialPort.GetPortNames();
|
||
return ports.Length > 0 ? string.Join(", ", ports) : "Порты не найдены";
|
||
|
||
case "send_serial_data":
|
||
string portName = root.GetProperty("port").GetString()!;
|
||
string data = root.GetProperty("data").GetString()!;
|
||
int baud = root.TryGetProperty("baudRate", out var b) ? b.GetInt32() : 9600;
|
||
|
||
using (var serial = new SerialPort(portName, baud))
|
||
{
|
||
serial.DtrEnable = true; // Важно для многих плат Arduino
|
||
serial.RtsEnable = true;
|
||
serial.Open();
|
||
serial.WriteLine(data);
|
||
Thread.Sleep(100); // Даем время на отправку
|
||
serial.Close();
|
||
}
|
||
return $"Отправлено в {portName}: {data}";
|
||
|
||
case "read_serial_data":
|
||
string rPortName = root.GetProperty("port").GetString()!;
|
||
int rBaud = root.TryGetProperty("baudRate", out var rb) ? rb.GetInt32() : 9600;
|
||
int timeout = root.TryGetProperty("timeoutMs", out var rt) ? rt.GetInt32() : 3000;
|
||
|
||
using (var serial = new SerialPort(rPortName, rBaud))
|
||
{
|
||
serial.DtrEnable = true; // Без этого Arduino может не слать данные
|
||
serial.RtsEnable = true;
|
||
serial.Open();
|
||
|
||
// После открытия порта Arduino может перезагрузиться,
|
||
// дадим ей немного времени прийти в себя
|
||
Thread.Sleep(500);
|
||
|
||
int waited = 500;
|
||
while (serial.BytesToRead == 0 && waited < timeout)
|
||
{
|
||
Thread.Sleep(100);
|
||
waited += 100;
|
||
}
|
||
|
||
if (serial.BytesToRead > 0)
|
||
{
|
||
string received = serial.ReadExisting();
|
||
serial.Close();
|
||
return $"Получено из {rPortName}: {received}";
|
||
}
|
||
|
||
serial.Close();
|
||
return $"Данные из {rPortName} не поступили за {timeout}мс.";
|
||
}
|
||
|
||
case "compile_upload_code":
|
||
string code = root.GetProperty("code").GetString()!;
|
||
string p = root.GetProperty("port").GetString()!;
|
||
string board = root.GetProperty("boardType").GetString()!;
|
||
return HandleCompileAndUpload(code, p, board);
|
||
|
||
default:
|
||
return "Инструмент не найден";
|
||
}
|
||
}
|
||
catch (Exception ex) { return $"Ошибка: {ex.Message}"; }
|
||
}
|
||
|
||
private string HandleCompileAndUpload(string code, string port, string board)
|
||
{
|
||
string tempDir = Path.Combine(Path.GetTempPath(), "VisionAsist_Arduino_" + Guid.NewGuid().ToString("N"));
|
||
string sketchName = "Sketch";
|
||
string sketchDir = Path.Combine(tempDir, sketchName);
|
||
string sketchPath = Path.Combine(sketchDir, sketchName + ".ino");
|
||
|
||
string modulePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
|
||
|
||
// Определяем имя файла в зависимости от ОС
|
||
bool isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows);
|
||
string exeName = isWindows ? "arduino-cli.exe" : "arduino-cli";
|
||
|
||
string arduinoCliPath = Path.Combine(modulePath, exeName);
|
||
if (!File.Exists(arduinoCliPath)) arduinoCliPath = exeName;
|
||
|
||
try
|
||
{
|
||
Directory.CreateDirectory(sketchDir);
|
||
File.WriteAllText(sketchPath, code);
|
||
|
||
var compileResult = RunCommand(arduinoCliPath, $"compile --fqbn {board} \"{sketchDir}\"");
|
||
if (compileResult.ExitCode != 0) return $"Ошибка компиляции:\n{compileResult.Error}";
|
||
|
||
var uploadResult = RunCommand(arduinoCliPath, $"upload -p {port} --fqbn {board} \"{sketchDir}\"");
|
||
if (uploadResult.ExitCode != 0) return $"Ошибка загрузки:\n{uploadResult.Error}";
|
||
|
||
return "Код успешно скомпилирован и загружен в Arduino!";
|
||
}
|
||
catch (Exception ex) { return $"Критическая ошибка: {ex.Message}"; }
|
||
finally
|
||
{
|
||
try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
|
||
}
|
||
}
|
||
|
||
private (int ExitCode, string Output, string Error) RunCommand(string cmd, string args)
|
||
{
|
||
try
|
||
{
|
||
var process = Process.Start(new ProcessStartInfo
|
||
{
|
||
FileName = cmd,
|
||
Arguments = args,
|
||
RedirectStandardOutput = true,
|
||
RedirectStandardError = true,
|
||
UseShellExecute = false,
|
||
CreateNoWindow = true
|
||
});
|
||
|
||
string output = process?.StandardOutput.ReadToEnd() ?? "";
|
||
string error = process?.StandardError.ReadToEnd() ?? "";
|
||
process?.WaitForExit();
|
||
|
||
return (process?.ExitCode ?? -1, output, error);
|
||
}
|
||
catch (System.ComponentModel.Win32Exception)
|
||
{
|
||
return (-1, "", $"Ошибка: Исполняемый файл '{cmd}' не найден в папке модуля или в системе.");
|
||
}
|
||
}
|
||
} |