Files
usb_uart_terminal/main.go
2025-05-29 10:37:21 +03:00

283 lines
6.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}