283 lines
6.4 KiB
Go
283 lines
6.4 KiB
Go
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
|
||
}
|