feat: implement SerialInterface for TinyGo with UART communication and KISS protocol handling
This commit is contained in:
222
pkg/interfaces/serial_tinygo.go
Normal file
222
pkg/interfaces/serial_tinygo.go
Normal file
@@ -0,0 +1,222 @@
|
||||
// SPDX-License-Identifier: 0BSD
|
||||
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
|
||||
//go:build tinygo
|
||||
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
|
||||
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
|
||||
)
|
||||
|
||||
const (
|
||||
SERIAL_DEFAULT_BAUD = 115200
|
||||
SERIAL_MTU = 1500
|
||||
)
|
||||
|
||||
// SerialInterface implements a serial interface using TinyGo UART.
|
||||
type SerialInterface struct {
|
||||
BaseInterface
|
||||
uart *machine.UART
|
||||
baud uint32
|
||||
done chan struct{}
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
// NewSerialInterface creates and initializes a new SerialInterface.
|
||||
func NewSerialInterface(name string, portName string, baud uint32, enabled bool) (*SerialInterface, error) {
|
||||
if baud == 0 {
|
||||
baud = SERIAL_DEFAULT_BAUD
|
||||
}
|
||||
|
||||
uart, err := getUART(portName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
si := &SerialInterface{
|
||||
BaseInterface: NewBaseInterface(name, common.IF_TYPE_SERIAL, enabled),
|
||||
uart: uart,
|
||||
baud: baud,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
si.MTU = SERIAL_MTU
|
||||
si.Bitrate = int64(baud)
|
||||
|
||||
if enabled {
|
||||
err := si.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return si, nil
|
||||
}
|
||||
|
||||
// getUART returns a TinyGo UART handle by name or index.
|
||||
func getUART(name string) (*machine.UART, error) {
|
||||
switch name {
|
||||
case "UART0", "0":
|
||||
return machine.UART0, nil
|
||||
case "UART1", "1":
|
||||
return machine.UART1, nil
|
||||
case "UART2", "2":
|
||||
return machine.UART2, nil
|
||||
default:
|
||||
if name == "" {
|
||||
return machine.UART0, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown UART: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
// Start enables the serial interface and starts the read loop.
|
||||
func (si *SerialInterface) Start() error {
|
||||
si.Mutex.Lock()
|
||||
defer si.Mutex.Unlock()
|
||||
|
||||
if si.Online {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := si.uart.Configure(machine.UARTConfig{
|
||||
BaudRate: si.baud,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to configure UART: %w", err)
|
||||
}
|
||||
|
||||
si.Online = true
|
||||
si.Enabled = true
|
||||
|
||||
go si.readLoop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop disables the serial interface.
|
||||
func (si *SerialInterface) Stop() error {
|
||||
si.Mutex.Lock()
|
||||
si.Online = false
|
||||
si.Enabled = false
|
||||
si.Mutex.Unlock()
|
||||
|
||||
si.stopOnce.Do(func() {
|
||||
if si.done != nil {
|
||||
close(si.done)
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readLoop reads and processes frames from the UART, handling KISS framing.
|
||||
func (si *SerialInterface) readLoop() {
|
||||
buffer := make([]byte, si.MTU)
|
||||
dataBuffer := make([]byte, 0, si.MTU)
|
||||
inFrame := false
|
||||
escape := false
|
||||
|
||||
for {
|
||||
si.Mutex.RLock()
|
||||
online := si.Online
|
||||
done := si.done
|
||||
si.Mutex.RUnlock()
|
||||
|
||||
if !online {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if si.uart.Buffered() > 0 {
|
||||
n, err := si.uart.Read(buffer)
|
||||
if err != nil {
|
||||
debug.Log(debug.DEBUG_ERROR, "Serial read error", "name", si.Name, "error", err)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
for i := 0; i < n; i++ {
|
||||
b := buffer[i]
|
||||
|
||||
if b == KISS_FEND {
|
||||
if inFrame && len(dataBuffer) > 0 {
|
||||
packet := make([]byte, len(dataBuffer))
|
||||
copy(packet, dataBuffer)
|
||||
si.ProcessIncoming(packet)
|
||||
dataBuffer = dataBuffer[:0]
|
||||
}
|
||||
inFrame = true
|
||||
escape = false
|
||||
continue
|
||||
}
|
||||
|
||||
if inFrame {
|
||||
if b == KISS_FESC {
|
||||
escape = true
|
||||
} else {
|
||||
if escape {
|
||||
if b == KISS_TFEND {
|
||||
b = KISS_FEND
|
||||
} else if b == KISS_TFESC {
|
||||
b = KISS_FESC
|
||||
}
|
||||
escape = false
|
||||
}
|
||||
dataBuffer = append(dataBuffer, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send transmits data using KISS protocol with the default command 0x00.
|
||||
func (si *SerialInterface) Send(data []byte, address string) error {
|
||||
return si.SendKISS(0x00, data)
|
||||
}
|
||||
|
||||
// SendKISS sends a KISS-encoded packet over the serial UART.
|
||||
func (si *SerialInterface) SendKISS(command byte, data []byte) error {
|
||||
si.Mutex.RLock()
|
||||
online := si.Online
|
||||
si.Mutex.RUnlock()
|
||||
|
||||
if !online {
|
||||
return fmt.Errorf("interface offline")
|
||||
}
|
||||
|
||||
frame := make([]byte, 0, len(data)*2+3)
|
||||
frame = append(frame, KISS_FEND)
|
||||
frame = append(frame, command)
|
||||
frame = append(frame, escapeKISS(data)...)
|
||||
frame = append(frame, KISS_FEND)
|
||||
|
||||
_, err := si.uart.Write(frame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
si.Mutex.Lock()
|
||||
si.TxBytes += uint64(len(frame))
|
||||
si.lastTx = time.Now()
|
||||
si.Mutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user