Big Gotool Refactoring
- Added Event Bus - Reworked Package Parsing - Rewokred Frame Parsing
This commit is contained in:
parent
9efef034f0
commit
0767ddac38
@ -1,358 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/pterm/pterm"
|
||||
"go.bug.st/serial"
|
||||
)
|
||||
|
||||
type ParserState int
|
||||
|
||||
const (
|
||||
// MISC
|
||||
UART_ECHO = 0x01
|
||||
UART_VERSION = 0x02
|
||||
UART_CLIENT_INFO = 0x03
|
||||
UART_CLIENT_INPUT = 0x04
|
||||
|
||||
// OTA
|
||||
UART_OTA_START = 0x10
|
||||
UART_OTA_PAYLOAD = 0x11
|
||||
UART_OTA_END = 0x12
|
||||
UART_OTA_STATUS = 0x13
|
||||
UART_OTA_START_ESPNOW = 0x14
|
||||
)
|
||||
|
||||
const (
|
||||
WAITING_FOR_START_BYTE ParserState = iota
|
||||
ESCAPED_MESSAGE_ID
|
||||
GET_MESSAGE_ID
|
||||
IN_PAYLOD
|
||||
ESCAPED_PAYLOAD_BYTE
|
||||
)
|
||||
|
||||
const (
|
||||
START_BYTE = 0xAA
|
||||
ESCAPE_BYTE = 0xBB
|
||||
END_BYTE = 0xCC
|
||||
)
|
||||
|
||||
type ParseError int
|
||||
|
||||
const (
|
||||
WRONG_CHECKSUM ParseError = iota
|
||||
UNEXPECETD_BYTE
|
||||
)
|
||||
|
||||
type MessageReceive struct {
|
||||
raw_message []byte
|
||||
parsed_message []byte
|
||||
checksum byte
|
||||
error ParseError
|
||||
state ParserState
|
||||
write_index int
|
||||
raw_write_index int
|
||||
}
|
||||
|
||||
type OTASyncManager struct {
|
||||
OTA_MessageCounter int
|
||||
OTA_PayloadMessageSequence int
|
||||
NewOTAMessage chan MessageReceive
|
||||
TimeoutMessage time.Duration
|
||||
}
|
||||
|
||||
func (ot *OTASyncManager) WaitForNextMessageTimeout() (*MessageReceive, error) {
|
||||
select {
|
||||
case msg := <-ot.NewOTAMessage:
|
||||
return &msg, nil
|
||||
case <-time.After(ot.TimeoutMessage):
|
||||
return nil, fmt.Errorf("Message Timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func initMessageReceive(mr *MessageReceive) {
|
||||
mr.raw_message = make([]byte, 1024*4)
|
||||
mr.parsed_message = make([]byte, 1024*4)
|
||||
mr.checksum = 0
|
||||
mr.error = 0
|
||||
mr.write_index = 0
|
||||
mr.raw_write_index = 0
|
||||
mr.state = WAITING_FOR_START_BYTE
|
||||
}
|
||||
|
||||
func addByteToRawBuffer(mr *MessageReceive, pbyte byte) {
|
||||
mr.raw_message[mr.raw_write_index] = pbyte
|
||||
mr.raw_write_index += 1
|
||||
}
|
||||
func addByteToParsedBuffer(mr *MessageReceive, pbyte byte) {
|
||||
mr.parsed_message[mr.write_index] = pbyte
|
||||
mr.write_index += 1
|
||||
mr.checksum ^= pbyte
|
||||
}
|
||||
|
||||
func parse_uart_ota_payload_payload(payloadBuffer []byte, payload_len int) {
|
||||
//fmt.Printf("RAW BUFFER: % 02X", payloadBuffer[:payload_len])
|
||||
if payload_len != 4 {
|
||||
fmt.Printf("Payload should be 4 is %v", payload_len)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Sequence %v, WriteIndex %v", binary.LittleEndian.Uint16(payloadBuffer[0:1]), binary.LittleEndian.Uint16(payloadBuffer[2:3]))
|
||||
}
|
||||
|
||||
func parse_uart_version_payload(payloadBuffer []byte, payload_len int) {
|
||||
type payload_data struct {
|
||||
Version uint16
|
||||
BuildHash [7]uint8
|
||||
}
|
||||
|
||||
tableHeaders := pterm.TableData{
|
||||
{"Version", "Buildhash"},
|
||||
}
|
||||
|
||||
tableData := tableHeaders
|
||||
|
||||
tableData = append(tableData, []string{
|
||||
fmt.Sprintf("%d", binary.LittleEndian.Uint16(payloadBuffer[1:3])),
|
||||
fmt.Sprintf("%s", payloadBuffer[3:10]),
|
||||
})
|
||||
|
||||
err := pterm.DefaultTable.WithHasHeader().WithBoxed().WithData(tableData).Render()
|
||||
if err != nil {
|
||||
fmt.Printf("Fehler beim Rendern der Tabelle: %s\n", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func parse_uart_client_info_payload(payloadBuffer []byte, payload_len int) {
|
||||
|
||||
type payload_data struct {
|
||||
ClientID uint8
|
||||
IsAvailable uint8
|
||||
SlotIsUsed uint8
|
||||
MACAddr [6]uint8
|
||||
LastPing uint32
|
||||
LastSuccessfulPing uint32
|
||||
Version uint16
|
||||
}
|
||||
|
||||
tableHeaders := pterm.TableData{
|
||||
{"Client ID", "Verfügbar", "Genutzt", "MAC-Adresse", "Last Ping", "Letzter Erfolg Ping", "Version"},
|
||||
}
|
||||
|
||||
tableData := tableHeaders
|
||||
|
||||
currentOffset := 2
|
||||
|
||||
const (
|
||||
ENTRY_LEN = 19
|
||||
OFFSET_MAC_ADDR = 3
|
||||
OFFSET_LAST_PING = 9
|
||||
OFFSET_LAST_SUCCESS_PING = 13
|
||||
OFFSET_VERSION = 17
|
||||
)
|
||||
|
||||
for i := 0; i < int(payloadBuffer[1]); i++ {
|
||||
if currentOffset+ENTRY_LEN > payload_len {
|
||||
fmt.Printf("Fehler: Payload zu kurz für Client-Eintrag %d\n", i)
|
||||
break
|
||||
}
|
||||
|
||||
entryBytes := payloadBuffer[currentOffset : currentOffset+ENTRY_LEN]
|
||||
|
||||
var clientData payload_data
|
||||
|
||||
clientData.ClientID = entryBytes[0]
|
||||
clientData.IsAvailable = entryBytes[1]
|
||||
clientData.SlotIsUsed = entryBytes[2]
|
||||
|
||||
copy(clientData.MACAddr[:], entryBytes[OFFSET_MAC_ADDR:OFFSET_MAC_ADDR+6])
|
||||
|
||||
clientData.LastPing = binary.LittleEndian.Uint32(entryBytes[OFFSET_LAST_PING : OFFSET_LAST_PING+4])
|
||||
clientData.LastSuccessfulPing = binary.LittleEndian.Uint32(entryBytes[OFFSET_LAST_SUCCESS_PING : OFFSET_LAST_SUCCESS_PING+4])
|
||||
clientData.Version = binary.LittleEndian.Uint16(entryBytes[OFFSET_VERSION : OFFSET_VERSION+2])
|
||||
|
||||
// Füge die geparsten Daten als String-Slice zur Tabelle hinzu
|
||||
tableData = append(tableData, []string{
|
||||
fmt.Sprintf("%d", clientData.ClientID),
|
||||
fmt.Sprintf("%d", clientData.IsAvailable),
|
||||
fmt.Sprintf("%d", clientData.SlotIsUsed),
|
||||
fmt.Sprintf("%X:%X:%X:%X:%X:%X",
|
||||
clientData.MACAddr[0], clientData.MACAddr[1], clientData.MACAddr[2],
|
||||
clientData.MACAddr[3], clientData.MACAddr[4], clientData.MACAddr[5]),
|
||||
fmt.Sprintf("%d", clientData.LastPing),
|
||||
fmt.Sprintf("%d", clientData.LastSuccessfulPing),
|
||||
fmt.Sprintf("%d", clientData.Version),
|
||||
})
|
||||
|
||||
currentOffset += ENTRY_LEN
|
||||
}
|
||||
err := pterm.DefaultTable.WithHasHeader().WithBoxed().WithData(tableData).Render()
|
||||
if err != nil {
|
||||
fmt.Printf("Fehler beim Rendern der Tabelle: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func parse_uart_client_input(payloadBuffer []byte, payload_len int) {
|
||||
|
||||
clientCount := payloadBuffer[1]
|
||||
fmt.Printf("Client Count %d\n", clientCount)
|
||||
clientInputLen := 13
|
||||
|
||||
for i := 0; i < int(clientCount); i++ {
|
||||
offset := 2 + (i * clientInputLen)
|
||||
|
||||
// --- Client ID (uint8) ---
|
||||
clientID := payloadBuffer[offset]
|
||||
offset += 1
|
||||
|
||||
fmt.Printf("Client: %d\n", clientID)
|
||||
|
||||
// --- Lage X (float32) ---
|
||||
xBits := binary.LittleEndian.Uint32(payloadBuffer[offset : offset+4])
|
||||
lageX := math.Float32frombits(xBits)
|
||||
offset += 4
|
||||
|
||||
fmt.Printf("\tLAGE_X: %f\n", lageX)
|
||||
|
||||
// --- Lage Y (float32) ---
|
||||
yBits := binary.LittleEndian.Uint32(payloadBuffer[offset : offset+4])
|
||||
lageY := math.Float32frombits(yBits)
|
||||
offset += 4
|
||||
|
||||
fmt.Printf("\tLAGE_Y: %f\n", lageY)
|
||||
|
||||
// --- Bitmask (int32) ---
|
||||
maskBits := binary.LittleEndian.Uint32(payloadBuffer[offset : offset+4])
|
||||
bitmask := uint32(maskBits)
|
||||
offset += 4
|
||||
|
||||
fmt.Printf("\tBITMASK: %032b\n", bitmask)
|
||||
}
|
||||
}
|
||||
|
||||
func message_receive_callback(mr MessageReceive) {
|
||||
log.Printf("Message Received: % 02X\n", mr.raw_message[:mr.raw_write_index])
|
||||
switch mr.parsed_message[0] {
|
||||
case byte(UART_ECHO):
|
||||
case UART_VERSION:
|
||||
parse_uart_version_payload(mr.parsed_message, mr.write_index)
|
||||
case UART_CLIENT_INFO:
|
||||
parse_uart_client_info_payload(mr.parsed_message, mr.write_index)
|
||||
case UART_OTA_START:
|
||||
OTA_UpdateHandler.NewOTAMessage <- mr
|
||||
case UART_OTA_PAYLOAD:
|
||||
parse_uart_ota_payload_payload(mr.parsed_message, mr.write_index)
|
||||
OTA_UpdateHandler.NewOTAMessage <- mr
|
||||
case UART_OTA_END:
|
||||
OTA_UpdateHandler.NewOTAMessage <- mr
|
||||
case UART_OTA_STATUS:
|
||||
OTA_UpdateHandler.NewOTAMessage <- mr
|
||||
case UART_CLIENT_INPUT:
|
||||
parse_uart_client_input(mr.parsed_message, mr.write_index)
|
||||
}
|
||||
}
|
||||
|
||||
func message_receive_failed_callback(mr MessageReceive) {
|
||||
log.Printf("Error Message Received: % 02X\n", mr.raw_message[:mr.raw_write_index])
|
||||
}
|
||||
|
||||
func parseByte(mr *MessageReceive, pbyte byte) {
|
||||
addByteToRawBuffer(mr, pbyte)
|
||||
switch mr.state {
|
||||
case WAITING_FOR_START_BYTE:
|
||||
if pbyte == START_BYTE {
|
||||
initMessageReceive(mr)
|
||||
mr.state = GET_MESSAGE_ID
|
||||
addByteToRawBuffer(mr, pbyte)
|
||||
}
|
||||
// ignore every other byte
|
||||
case GET_MESSAGE_ID:
|
||||
if pbyte == ESCAPE_BYTE {
|
||||
mr.state = ESCAPED_MESSAGE_ID
|
||||
} else {
|
||||
addByteToParsedBuffer(mr, pbyte)
|
||||
mr.state = IN_PAYLOD
|
||||
}
|
||||
case ESCAPED_MESSAGE_ID:
|
||||
addByteToParsedBuffer(mr, pbyte)
|
||||
mr.state = IN_PAYLOD
|
||||
case IN_PAYLOD:
|
||||
if pbyte == ESCAPE_BYTE {
|
||||
mr.state = ESCAPED_PAYLOAD_BYTE
|
||||
break
|
||||
}
|
||||
if pbyte == START_BYTE {
|
||||
mr.error = UNEXPECETD_BYTE
|
||||
go message_receive_failed_callback(*mr)
|
||||
initMessageReceive(mr)
|
||||
return
|
||||
}
|
||||
if pbyte == END_BYTE {
|
||||
if mr.checksum != 0 { // checksum wrong
|
||||
mr.error = WRONG_CHECKSUM
|
||||
go message_receive_failed_callback(*mr)
|
||||
initMessageReceive(mr)
|
||||
return
|
||||
}
|
||||
go message_receive_callback(*mr)
|
||||
initMessageReceive(mr)
|
||||
break
|
||||
}
|
||||
// normal case
|
||||
addByteToParsedBuffer(mr, pbyte)
|
||||
case ESCAPED_PAYLOAD_BYTE:
|
||||
addByteToParsedBuffer(mr, pbyte)
|
||||
mr.state = IN_PAYLOD
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected main.ParserState: %#v", mr.state))
|
||||
}
|
||||
}
|
||||
|
||||
func buildMessage(payloadBuffer []byte, payload_len int, sendBuffer []byte) int {
|
||||
var writeIndex int
|
||||
checksum := byte(0x00)
|
||||
writeIndex = 0
|
||||
sendBuffer[writeIndex] = START_BYTE
|
||||
writeIndex++
|
||||
for i := range payload_len {
|
||||
b := payloadBuffer[i]
|
||||
if b == START_BYTE || b == ESCAPE_BYTE || b == END_BYTE {
|
||||
sendBuffer[writeIndex] = ESCAPE_BYTE
|
||||
writeIndex++
|
||||
}
|
||||
sendBuffer[writeIndex] = b
|
||||
writeIndex++
|
||||
checksum ^= b
|
||||
}
|
||||
if checksum == START_BYTE || checksum == ESCAPE_BYTE || checksum == END_BYTE {
|
||||
sendBuffer[writeIndex] = ESCAPE_BYTE
|
||||
writeIndex++
|
||||
}
|
||||
sendBuffer[writeIndex] = checksum
|
||||
writeIndex++
|
||||
sendBuffer[writeIndex] = END_BYTE
|
||||
writeIndex++
|
||||
return writeIndex
|
||||
}
|
||||
|
||||
func sendMessage(port serial.Port, sendBuffer []byte) {
|
||||
n, err := port.Write(sendBuffer)
|
||||
if err != nil {
|
||||
log.Printf("Could not Send %v to Serial Port", sendBuffer)
|
||||
}
|
||||
if n < len(sendBuffer) {
|
||||
log.Printf("Did not send all data %v, only send %v", len(sendBuffer), n)
|
||||
}
|
||||
fmt.Printf("Send Message % 02X\n", sendBuffer[:n])
|
||||
}
|
||||
|
||||
var (
|
||||
updatePath string
|
||||
OTA_UpdateHandler OTASyncManager
|
||||
)
|
||||
11
goTool/api/frontend.go
Normal file
11
goTool/api/frontend.go
Normal file
@ -0,0 +1,11 @@
|
||||
package api
|
||||
|
||||
const (
|
||||
TopicFrontendCmd = "front:cmd"
|
||||
)
|
||||
|
||||
type FrontendCmd struct {
|
||||
Action string `json:"action"`
|
||||
ID byte `json:"id"`
|
||||
Data []byte `json:"data"`
|
||||
}
|
||||
39
goTool/api/uart.go
Normal file
39
goTool/api/uart.go
Normal file
@ -0,0 +1,39 @@
|
||||
package api
|
||||
|
||||
// Topics
|
||||
const (
|
||||
TopicUARTRx = "uart:rx"
|
||||
TopicUARTTx = "uart:tx"
|
||||
TopicUARTError = "uart:error"
|
||||
)
|
||||
|
||||
type Frame struct {
|
||||
Time uint64
|
||||
ID byte
|
||||
Data []byte
|
||||
}
|
||||
|
||||
const (
|
||||
CmdEcho byte = 0x01
|
||||
CmdVersion byte = 0x02
|
||||
CmdClientInfo byte = 0x03
|
||||
CmdClientInput byte = 0x04
|
||||
)
|
||||
|
||||
type PayloadVersion struct {
|
||||
Version uint16
|
||||
Buildhash [7]uint8
|
||||
}
|
||||
|
||||
type PayloadClientInfo struct {
|
||||
ClientID uint8
|
||||
IsAvailable uint8
|
||||
SlotIsUsed uint8
|
||||
MACAddr [6]uint8
|
||||
LastPing uint32
|
||||
LastSuccessfulPing uint32
|
||||
Version uint16
|
||||
}
|
||||
|
||||
type PayloadClientInput struct {
|
||||
}
|
||||
8
goTool/config.go
Normal file
8
goTool/config.go
Normal file
@ -0,0 +1,8 @@
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
Port int
|
||||
Host string
|
||||
UartPort string
|
||||
Baudrate int
|
||||
}
|
||||
64
goTool/eventbus/bus.go
Normal file
64
goTool/eventbus/bus.go
Normal file
@ -0,0 +1,64 @@
|
||||
package eventbus
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type EventBus interface {
|
||||
Subscribe(topic string) chan any
|
||||
Publish(topic string, data any)
|
||||
Unsubscribe(topic string, ch chan any)
|
||||
}
|
||||
|
||||
type EBus struct {
|
||||
mu sync.RWMutex
|
||||
topics map[string][]chan any
|
||||
}
|
||||
|
||||
func New() *EBus {
|
||||
return &EBus{
|
||||
mu: sync.RWMutex{},
|
||||
topics: map[string][]chan any{},
|
||||
}
|
||||
}
|
||||
|
||||
func (eb *EBus) Subscribe(topic string) chan any {
|
||||
eb.mu.Lock()
|
||||
defer eb.mu.Unlock()
|
||||
|
||||
ch := make(chan any, 20)
|
||||
eb.topics[topic] = append(eb.topics[topic], ch)
|
||||
return ch
|
||||
}
|
||||
|
||||
func (eb *EBus) Publish(topic string, data any) {
|
||||
eb.mu.RLock()
|
||||
defer eb.mu.RUnlock()
|
||||
for _, ch := range eb.topics[topic] {
|
||||
select {
|
||||
case ch <- data:
|
||||
default:
|
||||
log.Printf("[Event Bus]: Could not pass Message %v to %v channel full", data, topic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (eb *EBus) Unsubscribe(topic string, c chan any) {
|
||||
eb.mu.Lock()
|
||||
defer eb.mu.Unlock()
|
||||
|
||||
channels, ok := eb.topics[topic]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for i, ch := range channels {
|
||||
if ch != c {
|
||||
eb.topics[topic] = append(channels[:i], channels[i+1:]...) // example: 5 channels max i=3 channels[:3] (0,1,2) + channels[3+1:] (4,5)
|
||||
|
||||
close(ch)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,71 +1,102 @@
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"alox.tool/api"
|
||||
"alox.tool/eventbus"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
//go:embed www
|
||||
var staticFiles embed.FS
|
||||
|
||||
func home(w http.ResponseWriter, r *http.Request) {
|
||||
content, err := staticFiles.ReadFile("www/index.html")
|
||||
var upgrader = websocket.Upgrader{}
|
||||
|
||||
type FServer struct {
|
||||
bus eventbus.EventBus
|
||||
mux *http.ServeMux
|
||||
}
|
||||
|
||||
func New(bus eventbus.EventBus) *FServer {
|
||||
fsrv := &FServer{
|
||||
bus: bus,
|
||||
mux: http.NewServeMux(),
|
||||
}
|
||||
fsrv.routes()
|
||||
return fsrv
|
||||
}
|
||||
|
||||
func (fsrv *FServer) routes() {
|
||||
// Statische Dateien aus dem Embed-FS
|
||||
// "www" Präfix entfernen, damit index.html unter / verfügbar ist
|
||||
root, _ := fs.Sub(staticFiles, "www")
|
||||
fsrv.mux.Handle("/", http.FileServer(http.FS(root)))
|
||||
|
||||
// WebSocket Endpunkt
|
||||
fsrv.mux.HandleFunc("/ws", fsrv.handleWS)
|
||||
}
|
||||
|
||||
func (fsrv *FServer) handleWS(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Printf("Could not Read file %v", err)
|
||||
}
|
||||
fmt.Fprintf(w, string(content))
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{} // use default options
|
||||
|
||||
type ValueChangeResp struct {
|
||||
Cmd string `json:"cmd"`
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func echo(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
type resp struct {
|
||||
Cmd string `json:"cmd"`
|
||||
Arg string `json:"arg"`
|
||||
}
|
||||
|
||||
c, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Print("upgrade:", err)
|
||||
log.Printf("Upgrade error: %v", err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
defer conn.Close()
|
||||
|
||||
// Kanäle für die Hardware-Events abonnieren
|
||||
rxChan := fsrv.bus.Subscribe(api.TopicUARTRx)
|
||||
txChan := fsrv.bus.Subscribe(api.TopicUARTTx)
|
||||
|
||||
// Context nutzen, um Goroutinen zu stoppen, wenn die Verbindung abreißt
|
||||
ctx, cancel := context.WithCancel(r.Context())
|
||||
defer cancel()
|
||||
|
||||
// WRITER: Send Events to Browser
|
||||
go func() {
|
||||
for {
|
||||
_, message, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("read:", err)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case f := <-rxChan:
|
||||
if err := conn.WriteJSON(map[string]any{"type": "rx", "frame": f}); err != nil {
|
||||
return
|
||||
}
|
||||
case f := <-txChan:
|
||||
if err := conn.WriteJSON(map[string]any{"type": "tx", "frame": f}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// READER: Commands from Browser
|
||||
for {
|
||||
var cmd api.FrontendCmd
|
||||
if err := conn.ReadJSON(&cmd); err != nil {
|
||||
log.Printf("WS Read Error: %v", err)
|
||||
break
|
||||
}
|
||||
log.Printf("recv: %s", message)
|
||||
|
||||
jsonResp := resp{
|
||||
Cmd: "echo",
|
||||
Arg: string(message),
|
||||
}
|
||||
|
||||
err = c.WriteJSON(jsonResp)
|
||||
if err != nil {
|
||||
log.Println("write:", err)
|
||||
break
|
||||
}
|
||||
fsrv.bus.Publish(api.TopicFrontendCmd, cmd)
|
||||
log.Printf("Browser Action: %s auf ID 0x%02X", cmd.Action, cmd.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func StartServer() {
|
||||
http.Handle("/www/", http.FileServer(http.FS(staticFiles)))
|
||||
http.HandleFunc("/", home)
|
||||
http.HandleFunc("/echo", echo)
|
||||
|
||||
http.ListenAndServe("0.0.0.0:8000", nil)
|
||||
func (fsrv *FServer) Start(addr string) error {
|
||||
server := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: fsrv.mux,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
log.Printf("Frontend Server gestartet auf %s", addr)
|
||||
return server.ListenAndServe()
|
||||
}
|
||||
|
||||
585
goTool/main.go
585
goTool/main.go
@ -1,560 +1,85 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"alox.tool/api"
|
||||
"alox.tool/eventbus"
|
||||
"alox.tool/frontend"
|
||||
"github.com/pterm/pterm"
|
||||
"go.bug.st/serial"
|
||||
)
|
||||
|
||||
type ParserState int
|
||||
|
||||
const (
|
||||
// MISC
|
||||
UART_ECHO = 0x01
|
||||
UART_VERSION = 0x02
|
||||
UART_CLIENT_INFO = 0x03
|
||||
UART_CLIENT_INPUT = 0x04
|
||||
|
||||
// OTA
|
||||
UART_OTA_START = 0x10
|
||||
UART_OTA_PAYLOAD = 0x11
|
||||
UART_OTA_END = 0x12
|
||||
UART_OTA_STATUS = 0x13
|
||||
UART_OTA_START_ESPNOW = 0x14
|
||||
)
|
||||
|
||||
const (
|
||||
WAITING_FOR_START_BYTE ParserState = iota
|
||||
ESCAPED_MESSAGE_ID
|
||||
GET_MESSAGE_ID
|
||||
IN_PAYLOD
|
||||
ESCAPED_PAYLOAD_BYTE
|
||||
)
|
||||
|
||||
const (
|
||||
START_BYTE = 0xAA
|
||||
ESCAPE_BYTE = 0xBB
|
||||
END_BYTE = 0xCC
|
||||
)
|
||||
|
||||
type ParseError int
|
||||
|
||||
const (
|
||||
WRONG_CHECKSUM ParseError = iota
|
||||
UNEXPECETD_BYTE
|
||||
)
|
||||
|
||||
type MessageReceive struct {
|
||||
raw_message []byte
|
||||
parsed_message []byte
|
||||
checksum byte
|
||||
error ParseError
|
||||
state ParserState
|
||||
write_index int
|
||||
raw_write_index int
|
||||
}
|
||||
|
||||
type OTASyncManager struct {
|
||||
OTA_MessageCounter int
|
||||
OTA_PayloadMessageSequence int
|
||||
NewOTAMessage chan MessageReceive
|
||||
TimeoutMessage time.Duration
|
||||
}
|
||||
|
||||
func (ot *OTASyncManager) WaitForNextMessageTimeout() (*MessageReceive, error) {
|
||||
select {
|
||||
case msg := <-ot.NewOTAMessage:
|
||||
return &msg, nil
|
||||
case <-time.After(ot.TimeoutMessage):
|
||||
return nil, fmt.Errorf("Message Timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func initMessageReceive(mr *MessageReceive) {
|
||||
mr.raw_message = make([]byte, 1024*4)
|
||||
mr.parsed_message = make([]byte, 1024*4)
|
||||
mr.checksum = 0
|
||||
mr.error = 0
|
||||
mr.write_index = 0
|
||||
mr.raw_write_index = 0
|
||||
mr.state = WAITING_FOR_START_BYTE
|
||||
}
|
||||
|
||||
func addByteToRawBuffer(mr *MessageReceive, pbyte byte) {
|
||||
mr.raw_message[mr.raw_write_index] = pbyte
|
||||
mr.raw_write_index += 1
|
||||
}
|
||||
func addByteToParsedBuffer(mr *MessageReceive, pbyte byte) {
|
||||
mr.parsed_message[mr.write_index] = pbyte
|
||||
mr.write_index += 1
|
||||
mr.checksum ^= pbyte
|
||||
}
|
||||
|
||||
func parse_uart_ota_payload_payload(payloadBuffer []byte, payload_len int) {
|
||||
//fmt.Printf("RAW BUFFER: % 02X", payloadBuffer[:payload_len])
|
||||
if payload_len != 4 {
|
||||
fmt.Printf("Payload should be 4 is %v", payload_len)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Sequence %v, WriteIndex %v", binary.LittleEndian.Uint16(payloadBuffer[0:1]), binary.LittleEndian.Uint16(payloadBuffer[2:3]))
|
||||
}
|
||||
|
||||
func parse_uart_version_payload(payloadBuffer []byte, payload_len int) {
|
||||
type payload_data struct {
|
||||
Version uint16
|
||||
BuildHash [7]uint8
|
||||
}
|
||||
|
||||
tableHeaders := pterm.TableData{
|
||||
{"Version", "Buildhash"},
|
||||
}
|
||||
|
||||
tableData := tableHeaders
|
||||
|
||||
tableData = append(tableData, []string{
|
||||
fmt.Sprintf("%d", binary.LittleEndian.Uint16(payloadBuffer[1:3])),
|
||||
fmt.Sprintf("%s", payloadBuffer[3:10]),
|
||||
})
|
||||
|
||||
err := pterm.DefaultTable.WithHasHeader().WithBoxed().WithData(tableData).Render()
|
||||
if err != nil {
|
||||
fmt.Printf("Fehler beim Rendern der Tabelle: %s\n", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func parse_uart_client_info_payload(payloadBuffer []byte, payload_len int) {
|
||||
|
||||
type payload_data struct {
|
||||
ClientID uint8
|
||||
IsAvailable uint8
|
||||
SlotIsUsed uint8
|
||||
MACAddr [6]uint8
|
||||
LastPing uint32
|
||||
LastSuccessfulPing uint32
|
||||
Version uint16
|
||||
}
|
||||
|
||||
tableHeaders := pterm.TableData{
|
||||
{"Client ID", "Verfügbar", "Genutzt", "MAC-Adresse", "Last Ping", "Letzter Erfolg Ping", "Version"},
|
||||
}
|
||||
|
||||
tableData := tableHeaders
|
||||
|
||||
currentOffset := 2
|
||||
|
||||
const (
|
||||
ENTRY_LEN = 19
|
||||
OFFSET_MAC_ADDR = 3
|
||||
OFFSET_LAST_PING = 9
|
||||
OFFSET_LAST_SUCCESS_PING = 13
|
||||
OFFSET_VERSION = 17
|
||||
)
|
||||
|
||||
for i := 0; i < int(payloadBuffer[1]); i++ {
|
||||
if currentOffset+ENTRY_LEN > payload_len {
|
||||
fmt.Printf("Fehler: Payload zu kurz für Client-Eintrag %d\n", i)
|
||||
break
|
||||
}
|
||||
|
||||
entryBytes := payloadBuffer[currentOffset : currentOffset+ENTRY_LEN]
|
||||
|
||||
var clientData payload_data
|
||||
|
||||
clientData.ClientID = entryBytes[0]
|
||||
clientData.IsAvailable = entryBytes[1]
|
||||
clientData.SlotIsUsed = entryBytes[2]
|
||||
|
||||
copy(clientData.MACAddr[:], entryBytes[OFFSET_MAC_ADDR:OFFSET_MAC_ADDR+6])
|
||||
|
||||
clientData.LastPing = binary.LittleEndian.Uint32(entryBytes[OFFSET_LAST_PING : OFFSET_LAST_PING+4])
|
||||
clientData.LastSuccessfulPing = binary.LittleEndian.Uint32(entryBytes[OFFSET_LAST_SUCCESS_PING : OFFSET_LAST_SUCCESS_PING+4])
|
||||
clientData.Version = binary.LittleEndian.Uint16(entryBytes[OFFSET_VERSION : OFFSET_VERSION+2])
|
||||
|
||||
// Füge die geparsten Daten als String-Slice zur Tabelle hinzu
|
||||
tableData = append(tableData, []string{
|
||||
fmt.Sprintf("%d", clientData.ClientID),
|
||||
fmt.Sprintf("%d", clientData.IsAvailable),
|
||||
fmt.Sprintf("%d", clientData.SlotIsUsed),
|
||||
fmt.Sprintf("%X:%X:%X:%X:%X:%X",
|
||||
clientData.MACAddr[0], clientData.MACAddr[1], clientData.MACAddr[2],
|
||||
clientData.MACAddr[3], clientData.MACAddr[4], clientData.MACAddr[5]),
|
||||
fmt.Sprintf("%d", clientData.LastPing),
|
||||
fmt.Sprintf("%d", clientData.LastSuccessfulPing),
|
||||
fmt.Sprintf("%d", clientData.Version),
|
||||
})
|
||||
|
||||
currentOffset += ENTRY_LEN
|
||||
}
|
||||
err := pterm.DefaultTable.WithHasHeader().WithBoxed().WithData(tableData).Render()
|
||||
if err != nil {
|
||||
fmt.Printf("Fehler beim Rendern der Tabelle: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func parse_uart_client_input(payloadBuffer []byte, payload_len int) {
|
||||
|
||||
clientCount := payloadBuffer[1]
|
||||
fmt.Printf("Client Count %d\n", clientCount)
|
||||
clientInputLen := 13
|
||||
|
||||
for i := 0; i < int(clientCount); i++ {
|
||||
offset := 2 + (i * clientInputLen)
|
||||
|
||||
// --- Client ID (uint8) ---
|
||||
clientID := payloadBuffer[offset]
|
||||
offset += 1
|
||||
|
||||
fmt.Printf("Client: %d\n", clientID)
|
||||
|
||||
// --- Lage X (float32) ---
|
||||
xBits := binary.LittleEndian.Uint32(payloadBuffer[offset : offset+4])
|
||||
lageX := math.Float32frombits(xBits)
|
||||
offset += 4
|
||||
|
||||
fmt.Printf("\tLAGE_X: %f\n", lageX)
|
||||
|
||||
// --- Lage Y (float32) ---
|
||||
yBits := binary.LittleEndian.Uint32(payloadBuffer[offset : offset+4])
|
||||
lageY := math.Float32frombits(yBits)
|
||||
offset += 4
|
||||
|
||||
fmt.Printf("\tLAGE_Y: %f\n", lageY)
|
||||
|
||||
// --- Bitmask (int32) ---
|
||||
maskBits := binary.LittleEndian.Uint32(payloadBuffer[offset : offset+4])
|
||||
bitmask := uint32(maskBits)
|
||||
offset += 4
|
||||
|
||||
fmt.Printf("\tBITMASK: %032b\n", bitmask)
|
||||
}
|
||||
}
|
||||
|
||||
func message_receive_callback(mr MessageReceive) {
|
||||
log.Printf("Message Received: % 02X\n", mr.raw_message[:mr.raw_write_index])
|
||||
switch mr.parsed_message[0] {
|
||||
case byte(UART_ECHO):
|
||||
case UART_VERSION:
|
||||
parse_uart_version_payload(mr.parsed_message, mr.write_index)
|
||||
case UART_CLIENT_INFO:
|
||||
parse_uart_client_info_payload(mr.parsed_message, mr.write_index)
|
||||
case UART_OTA_START:
|
||||
OTA_UpdateHandler.NewOTAMessage <- mr
|
||||
case UART_OTA_PAYLOAD:
|
||||
parse_uart_ota_payload_payload(mr.parsed_message, mr.write_index)
|
||||
OTA_UpdateHandler.NewOTAMessage <- mr
|
||||
case UART_OTA_END:
|
||||
OTA_UpdateHandler.NewOTAMessage <- mr
|
||||
case UART_OTA_STATUS:
|
||||
OTA_UpdateHandler.NewOTAMessage <- mr
|
||||
case UART_CLIENT_INPUT:
|
||||
parse_uart_client_input(mr.parsed_message, mr.write_index)
|
||||
}
|
||||
}
|
||||
|
||||
func message_receive_failed_callback(mr MessageReceive) {
|
||||
log.Printf("Error Message Received: % 02X\n", mr.raw_message[:mr.raw_write_index])
|
||||
}
|
||||
|
||||
func parseByte(mr *MessageReceive, pbyte byte) {
|
||||
addByteToRawBuffer(mr, pbyte)
|
||||
switch mr.state {
|
||||
case WAITING_FOR_START_BYTE:
|
||||
if pbyte == START_BYTE {
|
||||
initMessageReceive(mr)
|
||||
mr.state = GET_MESSAGE_ID
|
||||
addByteToRawBuffer(mr, pbyte)
|
||||
}
|
||||
// ignore every other byte
|
||||
case GET_MESSAGE_ID:
|
||||
if pbyte == ESCAPE_BYTE {
|
||||
mr.state = ESCAPED_MESSAGE_ID
|
||||
} else {
|
||||
addByteToParsedBuffer(mr, pbyte)
|
||||
mr.state = IN_PAYLOD
|
||||
}
|
||||
case ESCAPED_MESSAGE_ID:
|
||||
addByteToParsedBuffer(mr, pbyte)
|
||||
mr.state = IN_PAYLOD
|
||||
case IN_PAYLOD:
|
||||
if pbyte == ESCAPE_BYTE {
|
||||
mr.state = ESCAPED_PAYLOAD_BYTE
|
||||
break
|
||||
}
|
||||
if pbyte == START_BYTE {
|
||||
mr.error = UNEXPECETD_BYTE
|
||||
go message_receive_failed_callback(*mr)
|
||||
initMessageReceive(mr)
|
||||
return
|
||||
}
|
||||
if pbyte == END_BYTE {
|
||||
if mr.checksum != 0 { // checksum wrong
|
||||
mr.error = WRONG_CHECKSUM
|
||||
go message_receive_failed_callback(*mr)
|
||||
initMessageReceive(mr)
|
||||
return
|
||||
}
|
||||
go message_receive_callback(*mr)
|
||||
initMessageReceive(mr)
|
||||
break
|
||||
}
|
||||
// normal case
|
||||
addByteToParsedBuffer(mr, pbyte)
|
||||
case ESCAPED_PAYLOAD_BYTE:
|
||||
addByteToParsedBuffer(mr, pbyte)
|
||||
mr.state = IN_PAYLOD
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected main.ParserState: %#v", mr.state))
|
||||
}
|
||||
}
|
||||
|
||||
func buildMessage(payloadBuffer []byte, payload_len int, sendBuffer []byte) int {
|
||||
var writeIndex int
|
||||
checksum := byte(0x00)
|
||||
writeIndex = 0
|
||||
sendBuffer[writeIndex] = START_BYTE
|
||||
writeIndex++
|
||||
for i := range payload_len {
|
||||
b := payloadBuffer[i]
|
||||
if b == START_BYTE || b == ESCAPE_BYTE || b == END_BYTE {
|
||||
sendBuffer[writeIndex] = ESCAPE_BYTE
|
||||
writeIndex++
|
||||
}
|
||||
sendBuffer[writeIndex] = b
|
||||
writeIndex++
|
||||
checksum ^= b
|
||||
}
|
||||
if checksum == START_BYTE || checksum == ESCAPE_BYTE || checksum == END_BYTE {
|
||||
sendBuffer[writeIndex] = ESCAPE_BYTE
|
||||
writeIndex++
|
||||
}
|
||||
sendBuffer[writeIndex] = checksum
|
||||
writeIndex++
|
||||
sendBuffer[writeIndex] = END_BYTE
|
||||
writeIndex++
|
||||
return writeIndex
|
||||
}
|
||||
|
||||
func sendMessage(port serial.Port, sendBuffer []byte) {
|
||||
n, err := port.Write(sendBuffer)
|
||||
if err != nil {
|
||||
log.Printf("Could not Send %v to Serial Port", sendBuffer)
|
||||
}
|
||||
if n < len(sendBuffer) {
|
||||
log.Printf("Did not send all data %v, only send %v", len(sendBuffer), n)
|
||||
}
|
||||
fmt.Printf("Send Message % 02X\n", sendBuffer[:n])
|
||||
}
|
||||
|
||||
var (
|
||||
updatePath string
|
||||
OTA_UpdateHandler OTASyncManager
|
||||
"alox.tool/uart"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
frontend.StartServer()
|
||||
os.Exit(0)
|
||||
|
||||
flag.StringVar(&updatePath, "update", "", "Path to Updatefile")
|
||||
flag.Parse()
|
||||
|
||||
OTA_UpdateHandler = OTASyncManager{
|
||||
OTA_MessageCounter: 0,
|
||||
OTA_PayloadMessageSequence: 0,
|
||||
NewOTAMessage: make(chan MessageReceive),
|
||||
TimeoutMessage: time.Millisecond * 30000,
|
||||
config := Config{
|
||||
Port: 8000,
|
||||
Host: "0.0.0.0",
|
||||
UartPort: "/dev/ttyUSB0",
|
||||
Baudrate: 115200,
|
||||
}
|
||||
StartApp(config)
|
||||
}
|
||||
|
||||
mode := &serial.Mode{
|
||||
//BaudRate: 115200,
|
||||
BaudRate: 921600,
|
||||
}
|
||||
port, err := serial.Open("/dev/ttyUSB0", mode)
|
||||
func StartApp(config Config) {
|
||||
bus := eventbus.New()
|
||||
|
||||
com, err := uart.Connect(bus, config.UartPort, config.Baudrate)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Printf("Could not Connect with Uart Device %v", err)
|
||||
}
|
||||
|
||||
ctx, cancle := context.WithCancel(context.Background())
|
||||
defer cancle()
|
||||
defer com.Close()
|
||||
|
||||
go func() {
|
||||
buff := make([]byte, 1024)
|
||||
mr := MessageReceive{}
|
||||
initMessageReceive(&mr)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
n, err := port.Read(buff)
|
||||
case msg := <-bus.Subscribe(api.TopicUARTTx):
|
||||
log.Printf("MSG[TX]: % X", msg)
|
||||
case msg := <-bus.Subscribe(api.TopicUARTRx):
|
||||
log.Printf("MSG[RX]: % X", msg)
|
||||
val, ok := msg.(api.Frame)
|
||||
if !ok {
|
||||
log.Printf("val is not type api.Frame its %T", val)
|
||||
continue
|
||||
}
|
||||
log.Printf("[%d] Frame: %X, % X", val.Time, val.ID, val.Data)
|
||||
|
||||
switch val.ID {
|
||||
case api.CmdEcho:
|
||||
case api.CmdVersion:
|
||||
v, err := uart.ParseFrameVersion(val)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
break
|
||||
log.Printf("Could not Parse Version %v", err)
|
||||
continue
|
||||
}
|
||||
if n == 0 {
|
||||
fmt.Println("\nEOF")
|
||||
break
|
||||
log.Printf("Version Info %d %X", v.Version, v.Buildhash)
|
||||
case api.CmdClientInfo:
|
||||
v, err := uart.ParseFrameClientInfo(val)
|
||||
if err != nil {
|
||||
log.Printf("Could not Parse Client Info %v", err)
|
||||
continue
|
||||
}
|
||||
for _, c := range v {
|
||||
log.Printf("Client ID %d", c.ClientID)
|
||||
log.Printf("\tIsAvailable %d", c.IsAvailable)
|
||||
log.Printf("\tLastPing %d", c.LastPing)
|
||||
log.Printf("\tLastSuccessfulPing %d", c.LastSuccessfulPing)
|
||||
log.Printf("\tSlotIsUsed %d", c.SlotIsUsed)
|
||||
log.Printf("\tVersion %d", c.Version)
|
||||
log.Printf("\tMACAddr % X", c.MACAddr)
|
||||
}
|
||||
for _, b := range buff[:n] {
|
||||
parseByte(&mr, b)
|
||||
}
|
||||
//fmt.Printf("Empfangen: % 02X\n", string(buff[:n]))
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if updatePath != "" {
|
||||
// start update
|
||||
update, err := os.ReadFile(updatePath)
|
||||
if err != nil {
|
||||
log.Printf("Could not read Update file %v", err)
|
||||
return
|
||||
}
|
||||
log.Printf("Update Buffer read, update size %v", len(update))
|
||||
log.Printf("Gonna break it down in 200 Bytes packages will send %v packages", len(update)/200)
|
||||
time.Sleep(time.Millisecond * 5)
|
||||
|
||||
// start
|
||||
payload_buffer := make([]byte, 1024)
|
||||
send_buffer := make([]byte, 1024)
|
||||
payload_buffer[0] = UART_OTA_START
|
||||
n := buildMessage(payload_buffer, 1, send_buffer)
|
||||
sendMessage(port, send_buffer[:n])
|
||||
msg, err := OTA_UpdateHandler.WaitForNextMessageTimeout()
|
||||
if err != nil {
|
||||
log.Printf("Error Message not acked %v", err)
|
||||
} else {
|
||||
if msg.parsed_message[2] != 0x00 {
|
||||
log.Printf("Update Start failed %v", msg.parsed_message[2])
|
||||
return
|
||||
} else {
|
||||
log.Printf("Update Start confirmed Updating Partition %v", msg.parsed_message[1])
|
||||
}
|
||||
}
|
||||
com.Send(api.CmdEcho, make([]byte, 0))
|
||||
com.Send(api.CmdVersion, make([]byte, 0))
|
||||
com.Send(api.CmdClientInfo, make([]byte, 0))
|
||||
|
||||
update_write_index := 0
|
||||
// write update parts
|
||||
for update_write_index < len(update) {
|
||||
payload_buffer = make([]byte, 1024)
|
||||
send_buffer = make([]byte, 1024)
|
||||
payload_buffer[0] = UART_OTA_PAYLOAD
|
||||
|
||||
write_len := min(200, len(update)-update_write_index)
|
||||
|
||||
//end_payload_len := min(update_write_index+200, len(update))
|
||||
|
||||
copy(payload_buffer[1:write_len+1], update[update_write_index:update_write_index+write_len])
|
||||
n = buildMessage(payload_buffer, write_len+1, send_buffer)
|
||||
sendMessage(port, send_buffer[:n])
|
||||
msg, err := OTA_UpdateHandler.WaitForNextMessageTimeout()
|
||||
if err != nil {
|
||||
log.Printf("Error Message not acked %v", err)
|
||||
return
|
||||
} else {
|
||||
seqCounter := binary.LittleEndian.Uint16(msg.parsed_message[1:3])
|
||||
buff_write_index := binary.LittleEndian.Uint16(msg.parsed_message[3:5])
|
||||
log.Printf("Sequenzce Counter: %d, Update buffer Write Index: %d", seqCounter, buff_write_index)
|
||||
}
|
||||
update_write_index += 200
|
||||
}
|
||||
|
||||
log.Printf("Update übertragen beende hier!!!")
|
||||
// end
|
||||
payload_buffer = make([]byte, 1024)
|
||||
send_buffer = make([]byte, 1024)
|
||||
payload_buffer[0] = UART_OTA_END
|
||||
n = buildMessage(payload_buffer, 1, send_buffer)
|
||||
sendMessage(port, send_buffer[:n])
|
||||
|
||||
_, err = OTA_UpdateHandler.WaitForNextMessageTimeout()
|
||||
if err != nil {
|
||||
log.Printf("Error Message not acked %v", err)
|
||||
return
|
||||
} else {
|
||||
log.Printf("Message Waiting hat funktionioert")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
var input string
|
||||
var input2 string
|
||||
_, err := fmt.Scanln(&input, &input2)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not read from stdin %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Input %v", input)
|
||||
|
||||
switch input {
|
||||
case "q":
|
||||
return
|
||||
case "1":
|
||||
payload_buffer := make([]byte, 1024)
|
||||
send_buffer := make([]byte, 1024)
|
||||
payload_buffer[0] = UART_ECHO
|
||||
n := buildMessage(payload_buffer, 1, send_buffer)
|
||||
sendMessage(port, send_buffer[:n])
|
||||
case "2":
|
||||
payload_buffer := make([]byte, 1024)
|
||||
send_buffer := make([]byte, 1024)
|
||||
payload_buffer[0] = UART_VERSION
|
||||
n := buildMessage(payload_buffer, 1, send_buffer)
|
||||
sendMessage(port, send_buffer[:n])
|
||||
case "3":
|
||||
payload_buffer := make([]byte, 1024)
|
||||
send_buffer := make([]byte, 1024)
|
||||
payload_buffer[0] = UART_CLIENT_INFO
|
||||
n := buildMessage(payload_buffer, 1, send_buffer)
|
||||
sendMessage(port, send_buffer[:n])
|
||||
case "4": // start update
|
||||
payload_buffer := make([]byte, 1024)
|
||||
send_buffer := make([]byte, 1024)
|
||||
payload_buffer[0] = UART_OTA_START
|
||||
n := buildMessage(payload_buffer, 1, send_buffer)
|
||||
sendMessage(port, send_buffer[:n])
|
||||
case "5": // send payload
|
||||
payload_buffer := make([]byte, 1024)
|
||||
send_buffer := make([]byte, 1024)
|
||||
payload_buffer[0] = UART_OTA_PAYLOAD
|
||||
for i := range 200 {
|
||||
payload_buffer[i+1] = byte(i)
|
||||
}
|
||||
n := buildMessage(payload_buffer, 201, send_buffer)
|
||||
sendMessage(port, send_buffer[:n])
|
||||
case "6": // end update
|
||||
case "7": // Start OTA for ESP-NOW clients
|
||||
payload_buffer := make([]byte, 1024)
|
||||
send_buffer := make([]byte, 1024)
|
||||
payload_buffer[0] = UART_OTA_START_ESPNOW
|
||||
n := buildMessage(payload_buffer, 1, send_buffer)
|
||||
sendMessage(port, send_buffer[:n])
|
||||
case "8": // Get Fake Client Input
|
||||
payload_buffer := make([]byte, 1024)
|
||||
send_buffer := make([]byte, 1024)
|
||||
payload_buffer[0] = UART_CLIENT_INPUT
|
||||
seed, err := strconv.Atoi(input2)
|
||||
if err != nil {
|
||||
log.Printf("Could not parse %v to a number", input2)
|
||||
return
|
||||
}
|
||||
payload_buffer[1] = byte(seed)
|
||||
n := buildMessage(payload_buffer, 2, send_buffer)
|
||||
sendMessage(port, send_buffer[:n])
|
||||
default:
|
||||
fmt.Printf("Not a valid input")
|
||||
}
|
||||
}
|
||||
url := fmt.Sprintf("%s:%d", config.Host, config.Port)
|
||||
fserver := frontend.New(bus)
|
||||
fserver.Start(url)
|
||||
}
|
||||
|
||||
48
goTool/testrunner/runner.go
Normal file
48
goTool/testrunner/runner.go
Normal file
@ -0,0 +1,48 @@
|
||||
package testrunner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"alox.tool/api"
|
||||
"alox.tool/eventbus"
|
||||
"alox.tool/uart"
|
||||
)
|
||||
|
||||
type TestRunner struct {
|
||||
bus eventbus.EventBus
|
||||
com *uart.Com
|
||||
}
|
||||
|
||||
func New(bus eventbus.EventBus, com *uart.Com) *TestRunner {
|
||||
return &TestRunner{
|
||||
bus: bus,
|
||||
com: com,
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *TestRunner) Expect(idToSend byte, payload []byte, expectedID byte, timeout time.Duration) (*api.Frame, error) {
|
||||
rxChan := tr.bus.Subscribe(api.TopicUARTRx)
|
||||
defer tr.bus.Unsubscribe(api.TopicUARTRx, rxChan)
|
||||
|
||||
if err := tr.com.Send(idToSend, payload); err != nil {
|
||||
return nil, fmt.Errorf("send error: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, fmt.Errorf("timeout waiting for ID 0x%02X", expectedID)
|
||||
case frame := <-rxChan:
|
||||
f := frame.(api.Frame)
|
||||
if f.ID == expectedID {
|
||||
return &f, nil
|
||||
}
|
||||
// Ignore other IDs and Messages on the Bus
|
||||
}
|
||||
}
|
||||
}
|
||||
21
goTool/testrunner/tests.go
Normal file
21
goTool/testrunner/tests.go
Normal file
@ -0,0 +1,21 @@
|
||||
package testrunner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"alox.tool/api"
|
||||
)
|
||||
|
||||
func (tr *TestRunner) RunVersionTest() error {
|
||||
fmt.Println("Starte Version Test...")
|
||||
|
||||
// Sende VersionRequest (0x02), erwarte VersionResponse (0x02)
|
||||
frame, err := tr.Expect(api.CmdVersion, nil, api.CmdVersion, 1*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Test bestanden! Hardware Version ist: %s\n", string(frame.Data))
|
||||
return nil
|
||||
}
|
||||
101
goTool/uart/com.go
Normal file
101
goTool/uart/com.go
Normal file
@ -0,0 +1,101 @@
|
||||
package uart
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"alox.tool/api"
|
||||
"alox.tool/eventbus"
|
||||
"go.bug.st/serial"
|
||||
)
|
||||
|
||||
type Com struct {
|
||||
bus eventbus.EventBus
|
||||
port serial.Port
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func Connect(bus eventbus.EventBus, portName string, baudrate int) (*Com, error) {
|
||||
mode := &serial.Mode{BaudRate: baudrate}
|
||||
port, err := serial.Open(portName, mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
drv := New(bus)
|
||||
|
||||
go func() {
|
||||
buff := make([]byte, 1024)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
n, err := port.Read(buff)
|
||||
if err != nil {
|
||||
log.Print("Read Error:", err)
|
||||
return // Loop beenden bei Hardware-Fehler
|
||||
}
|
||||
if n > 0 {
|
||||
for _, b := range buff[:n] {
|
||||
log.Printf("[RAW][RX] % X", b)
|
||||
drv.ParseByte(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return &Com{
|
||||
bus: bus,
|
||||
port: port,
|
||||
cancel: cancel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Com) Close() {
|
||||
c.cancel()
|
||||
c.port.Close()
|
||||
}
|
||||
|
||||
func packFrame(id byte, payload []byte) []byte {
|
||||
out := make([]byte, 0, len(payload)+5) // Guessing extra Puffer size
|
||||
checksum := id
|
||||
|
||||
out = append(out, StartByte)
|
||||
|
||||
// Helper für Escaping
|
||||
writeEscaped := func(b byte) {
|
||||
if b == StartByte || b == EscapeByte || b == EndByte {
|
||||
out = append(out, EscapeByte)
|
||||
}
|
||||
out = append(out, b)
|
||||
}
|
||||
|
||||
writeEscaped(id)
|
||||
for _, b := range payload {
|
||||
writeEscaped(b)
|
||||
checksum ^= b
|
||||
}
|
||||
|
||||
writeEscaped(checksum)
|
||||
out = append(out, EndByte)
|
||||
return out
|
||||
}
|
||||
|
||||
func (c *Com) Send(id byte, payload []byte) error {
|
||||
raw := packFrame(id, payload)
|
||||
|
||||
log.Printf("RAW: % X", raw)
|
||||
_, err := c.port.Write(raw)
|
||||
|
||||
c.bus.Publish(api.TopicUARTTx, api.Frame{
|
||||
Time: uint64(time.Now().UnixNano()),
|
||||
ID: id,
|
||||
Data: payload,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
109
goTool/uart/commands.go
Normal file
109
goTool/uart/commands.go
Normal file
@ -0,0 +1,109 @@
|
||||
package uart
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
|
||||
"alox.tool/api"
|
||||
)
|
||||
|
||||
func ParseFrameVersion(frame api.Frame) (api.PayloadVersion, error) {
|
||||
if len(frame.Data) != 10 {
|
||||
return api.PayloadVersion{}, fmt.Errorf("payload wrong size: got %d bytes, want 10", len(frame.Data))
|
||||
}
|
||||
|
||||
v := api.PayloadVersion{
|
||||
Version: binary.LittleEndian.Uint16(frame.Data[0:2]),
|
||||
Buildhash: [7]uint8(frame.Data[2:10])}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func parseFrameClientInfoPart(data []byte) (api.PayloadClientInfo, error) {
|
||||
if len(data) != 19 {
|
||||
return api.PayloadClientInfo{}, fmt.Errorf("payload wrong size: got %d bytes, want 19", len(data))
|
||||
}
|
||||
|
||||
v := api.PayloadClientInfo{
|
||||
ClientID: data[0],
|
||||
IsAvailable: data[1],
|
||||
SlotIsUsed: data[2],
|
||||
MACAddr: [6]uint8(data[3:9]),
|
||||
LastPing: binary.LittleEndian.Uint32(data[9:13]),
|
||||
LastSuccessfulPing: binary.LittleEndian.Uint32(data[13:17]),
|
||||
Version: binary.LittleEndian.Uint16(data[17:19]),
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func ParseFrameClientInfo(frame api.Frame) ([]api.PayloadClientInfo, error) {
|
||||
if len(frame.Data) == 0 {
|
||||
return nil, fmt.Errorf("empty frame data")
|
||||
}
|
||||
|
||||
clientCount := int(frame.Data[0])
|
||||
|
||||
log.Printf("Clients %d", clientCount)
|
||||
|
||||
expectedLen := 1 + (clientCount * 19)
|
||||
|
||||
if len(frame.Data) < expectedLen {
|
||||
return nil, fmt.Errorf("frame data too short: got %d, want %d", len(frame.Data), expectedLen)
|
||||
}
|
||||
|
||||
clientList := make([]api.PayloadClientInfo, 0, clientCount)
|
||||
|
||||
for i := 0; i < clientCount; i++ {
|
||||
start := 1 + (i * 19)
|
||||
end := start + 19
|
||||
|
||||
client, err := parseFrameClientInfoPart(frame.Data[start:end])
|
||||
if err != nil {
|
||||
log.Printf("Could not parse client part %d: %v", i, err)
|
||||
continue
|
||||
}
|
||||
clientList = append(clientList, client)
|
||||
}
|
||||
|
||||
return clientList, nil
|
||||
}
|
||||
|
||||
func parse_uart_client_input(payloadBuffer []byte, payload_len int) {
|
||||
|
||||
clientCount := payloadBuffer[1]
|
||||
fmt.Printf("Client Count %d\n", clientCount)
|
||||
clientInputLen := 13
|
||||
|
||||
for i := 0; i < int(clientCount); i++ {
|
||||
offset := 2 + (i * clientInputLen)
|
||||
|
||||
// --- Client ID (uint8) ---
|
||||
clientID := payloadBuffer[offset]
|
||||
offset += 1
|
||||
|
||||
fmt.Printf("Client: %d\n", clientID)
|
||||
|
||||
// --- Lage X (float32) ---
|
||||
xBits := binary.LittleEndian.Uint32(payloadBuffer[offset : offset+4])
|
||||
lageX := math.Float32frombits(xBits)
|
||||
offset += 4
|
||||
|
||||
fmt.Printf("\tLAGE_X: %f\n", lageX)
|
||||
|
||||
// --- Lage Y (float32) ---
|
||||
yBits := binary.LittleEndian.Uint32(payloadBuffer[offset : offset+4])
|
||||
lageY := math.Float32frombits(yBits)
|
||||
offset += 4
|
||||
|
||||
fmt.Printf("\tLAGE_Y: %f\n", lageY)
|
||||
|
||||
// --- Bitmask (int32) ---
|
||||
maskBits := binary.LittleEndian.Uint32(payloadBuffer[offset : offset+4])
|
||||
bitmask := uint32(maskBits)
|
||||
offset += 4
|
||||
|
||||
fmt.Printf("\tBITMASK: %032b\n", bitmask)
|
||||
}
|
||||
}
|
||||
128
goTool/uart/parser.go
Normal file
128
goTool/uart/parser.go
Normal file
@ -0,0 +1,128 @@
|
||||
package uart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"alox.tool/api"
|
||||
"alox.tool/eventbus"
|
||||
)
|
||||
|
||||
const (
|
||||
StartByte = 0xAA
|
||||
EscapeByte = 0xBB
|
||||
EndByte = 0xCC
|
||||
)
|
||||
|
||||
type parserState int
|
||||
|
||||
const (
|
||||
stateWaitingForStart parserState = iota
|
||||
stateGetID
|
||||
stateEscapedID
|
||||
stateInPayload
|
||||
stateEscapedPayload
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
bus eventbus.EventBus
|
||||
state parserState
|
||||
parsedData []byte
|
||||
rawCapture []byte
|
||||
checksum byte
|
||||
}
|
||||
|
||||
func New(bus eventbus.EventBus) *Parser {
|
||||
return &Parser{
|
||||
bus: bus,
|
||||
state: stateWaitingForStart,
|
||||
parsedData: make([]byte, 0, 1024*4),
|
||||
rawCapture: make([]byte, 0, 1024*4),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) reset() {
|
||||
p.state = stateWaitingForStart
|
||||
p.parsedData = p.parsedData[:0]
|
||||
p.rawCapture = p.rawCapture[:0]
|
||||
p.checksum = 0
|
||||
}
|
||||
|
||||
func (p *Parser) pushError(reason string) {
|
||||
// Throw Error on the Bus befor resetting
|
||||
p.bus.Publish(api.TopicUARTError, fmt.Errorf("%s: %02X", reason, p.rawCapture))
|
||||
p.reset()
|
||||
}
|
||||
|
||||
func (p *Parser) emitFrame() {
|
||||
if len(p.parsedData) == 0 {
|
||||
p.reset()
|
||||
return
|
||||
}
|
||||
|
||||
// Copy Data for Message Frame
|
||||
dataCopy := make([]byte, len(p.parsedData)-1) // Exclude ID
|
||||
copy(dataCopy, p.parsedData[1:])
|
||||
|
||||
f := api.Frame{
|
||||
Time: uint64(time.Now().UnixNano()),
|
||||
ID: p.parsedData[0],
|
||||
Data: dataCopy,
|
||||
}
|
||||
|
||||
p.bus.Publish(api.TopicUARTRx, f)
|
||||
p.reset()
|
||||
}
|
||||
|
||||
func (p *Parser) addByte(b byte) {
|
||||
p.parsedData = append(p.parsedData, b)
|
||||
p.checksum ^= b
|
||||
}
|
||||
|
||||
func (p *Parser) ParseByte(b byte) {
|
||||
p.rawCapture = append(p.rawCapture, b)
|
||||
|
||||
switch p.state {
|
||||
case stateWaitingForStart:
|
||||
if b == StartByte {
|
||||
p.reset()
|
||||
p.rawCapture = append(p.rawCapture, b) // Start Byte behalten
|
||||
p.state = stateGetID
|
||||
}
|
||||
|
||||
case stateGetID:
|
||||
if b == EscapeByte {
|
||||
p.state = stateEscapedID
|
||||
} else {
|
||||
p.addByte(b)
|
||||
p.state = stateInPayload
|
||||
}
|
||||
|
||||
case stateEscapedID:
|
||||
p.addByte(b)
|
||||
p.state = stateInPayload
|
||||
|
||||
case stateInPayload:
|
||||
if b == EscapeByte {
|
||||
p.state = stateEscapedPayload
|
||||
return
|
||||
}
|
||||
if b == StartByte {
|
||||
p.pushError("unexpected start byte")
|
||||
return
|
||||
}
|
||||
if b == EndByte {
|
||||
if p.checksum != 0 {
|
||||
p.pushError("checksum mismatch")
|
||||
return
|
||||
}
|
||||
p.emitFrame()
|
||||
return
|
||||
}
|
||||
p.addByte(b)
|
||||
|
||||
case stateEscapedPayload:
|
||||
p.addByte(b)
|
||||
p.state = stateInPayload
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user