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 GetTools() { return new List { 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}' не найден в папке модуля или в системе."); } } }