package main import ( "fmt" "log" "strings" "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/widget" "go.bug.st/serial" ) type UARTApp struct { app fyne.App window fyne.Window portEntry *widget.Select baudEntry *widget.Select lineEnding *widget.RadioGroup connectButton *widget.Button disconnectButton *widget.Button sendEntry *widget.Entry sendButton *widget.Button receiveText *widget.Entry statusLabel *widget.Label port serial.Port isConnected bool receiveBuffer strings.Builder } func main() { myApp := app.New() uartApp := &UARTApp{ app: myApp, window: myApp.NewWindow("UART Terminal"), } uartApp.createUI() uartApp.window.Resize(fyne.NewSize(800, 600)) // Увеличиваем начальный размер окна uartApp.window.ShowAndRun() } func (a *UARTApp) createUI() { // Port selection a.portEntry = widget.NewSelect([]string{}, nil) a.refreshPorts() // Baud rate selection a.baudEntry = widget.NewSelect([]string{ "300", "600", "1200", "2400", "4800", "9600", "14400", "19200", "28800", "38400", "57600", "115200", }, nil) a.baudEntry.SetSelected("9600") // Line ending selection a.lineEnding = widget.NewRadioGroup([]string{"None", "CR", "LF", "CR+LF"}, nil) a.lineEnding.SetSelected("CR+LF") // Buttons a.connectButton = widget.NewButton("Connect", a.connect) a.disconnectButton = widget.NewButton("Disconnect", a.disconnect) a.disconnectButton.Disable() // Send section a.sendEntry = widget.NewEntry() a.sendEntry.SetPlaceHolder("Enter message to send") a.sendButton = widget.NewButton("Send", a.sendMessage) a.sendButton.Disable() // Receive section a.receiveText = widget.NewMultiLineEntry() a.receiveText.Wrapping = fyne.TextWrapOff // Status bar a.statusLabel = widget.NewLabel("Disconnected") // Создаем контейнер с настройками settingsBox := container.NewVBox( widget.NewLabel("Serial Port Settings"), container.NewHBox(widget.NewLabel("Port:"), a.portEntry, widget.NewButton("Refresh", a.refreshPorts)), container.NewHBox(widget.NewLabel("Baud Rate:"), a.baudEntry), container.NewHBox(widget.NewLabel("Line Ending:"), a.lineEnding), container.NewHBox(a.connectButton, a.disconnectButton), ) // Контейнер для отправки сообщений sendBox := container.NewVBox( widget.NewLabel("Send Message"), a.sendEntry, a.sendButton, ) // Верхняя часть (настройки + отправка) topSection := container.NewHSplit( container.NewVScroll(settingsBox), sendBox, ) topSection.SetOffset(0.4) // Настройки занимают 40% верхней части // Нижняя часть (полученные данные) receiveBox := container.NewBorder( widget.NewLabel("Received Data (one message per line)"), nil, nil, nil, container.NewScroll(a.receiveText), ) // Главный контейнер с правильными пропорциями mainContent := container.NewVSplit( topSection, receiveBox, ) mainContent.SetOffset(0.3) // Верхняя часть занимает 30% высоты // Создаем окончательный макет с растягиванием finalLayout := container.NewBorder( nil, a.statusLabel, nil, nil, mainContent, ) a.window.SetContent(finalLayout) } func (a *UARTApp) refreshPorts() { ports, err := serial.GetPortsList() if err != nil { log.Println("Error getting ports list:", err) return } if len(ports) == 0 { ports = []string{"No ports available"} a.portEntry.Disable() } else { a.portEntry.Enable() } a.portEntry.Options = ports if len(ports) > 0 && ports[0] != "No ports available" { a.portEntry.SetSelected(ports[0]) } } func (a *UARTApp) connect() { if a.isConnected { return } portName := a.portEntry.Selected if portName == "" { a.statusLabel.SetText("Please select a port") return } baudRate := a.baudEntry.Selected if baudRate == "" { a.statusLabel.SetText("Please select a baud rate") return } mode := &serial.Mode{ BaudRate: mustParseInt(baudRate), } port, err := serial.Open(portName, mode) if err != nil { a.statusLabel.SetText("Error opening port: " + err.Error()) return } a.port = port a.isConnected = true a.connectButton.Disable() a.disconnectButton.Enable() a.sendButton.Enable() a.statusLabel.SetText("Connected to " + portName + " at " + baudRate + " baud") a.receiveBuffer.Reset() go a.readFromPort() } func (a *UARTApp) disconnect() { if !a.isConnected || a.port == nil { return } err := a.port.Close() if err != nil { a.statusLabel.SetText("Error closing port: " + err.Error()) } else { a.statusLabel.SetText("Disconnected") } a.isConnected = false a.port = nil a.connectButton.Enable() a.disconnectButton.Disable() a.sendButton.Disable() } func (a *UARTApp) sendMessage() { if !a.isConnected || a.port == nil { a.statusLabel.SetText("Not connected") return } message := a.sendEntry.Text if message == "" { return } // Add line ending based on selection switch a.lineEnding.Selected { case "CR": message += "\r" case "LF": message += "\n" case "CR+LF": message += "\r\n" } _, err := a.port.Write([]byte(message)) if err != nil { a.statusLabel.SetText("Send error: " + err.Error()) } else { a.sendEntry.SetText("") } } func (a *UARTApp) readFromPort() { buf := make([]byte, 1024) for a.isConnected && a.port != nil { n, err := a.port.Read(buf) if err != nil { a.disconnect() a.app.Quit() return } if n > 0 { received := string(buf[:n]) a.processReceivedData(received) } time.Sleep(100 * time.Millisecond) } } func (a *UARTApp) processReceivedData(data string) { // Добавляем данные в буфер a.receiveBuffer.WriteString(data) // Получаем текущий текст и добавляем новые данные с новой строки currentText := a.receiveText.Text if currentText != "" { currentText += "\n" } currentText += data // Обновляем текстовое поле в UI a.receiveText.SetText(currentText) // Прокручиваем вниз a.receiveText.CursorRow = len(strings.Split(currentText, "\n")) - 1 a.receiveText.Refresh() } func mustParseInt(s string) int { var result int _, err := fmt.Sscanf(s, "%d", &result) if err != nil { return 9600 // default } return result }