From 0767ddac381096de7c012d44713655678a43e9b8 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 27 Jan 2026 16:23:51 +0100 Subject: [PATCH] Big Gotool Refactoring - Added Event Bus - Reworked Package Parsing - Rewokred Frame Parsing --- goTool/UARTCom/com.go | 358 ---------------------- goTool/api/frontend.go | 11 + goTool/api/uart.go | 39 +++ goTool/config.go | 8 + goTool/eventbus/bus.go | 64 ++++ goTool/frontend/server.go | 111 ++++--- goTool/main.go | 589 ++++-------------------------------- goTool/testrunner/runner.go | 48 +++ goTool/testrunner/tests.go | 21 ++ goTool/uart/com.go | 101 +++++++ goTool/uart/commands.go | 109 +++++++ goTool/uart/parser.go | 128 ++++++++ 12 files changed, 657 insertions(+), 930 deletions(-) delete mode 100644 goTool/UARTCom/com.go create mode 100644 goTool/api/frontend.go create mode 100644 goTool/api/uart.go create mode 100644 goTool/config.go create mode 100644 goTool/eventbus/bus.go create mode 100644 goTool/testrunner/runner.go create mode 100644 goTool/testrunner/tests.go create mode 100644 goTool/uart/com.go create mode 100644 goTool/uart/commands.go create mode 100644 goTool/uart/parser.go diff --git a/goTool/UARTCom/com.go b/goTool/UARTCom/com.go deleted file mode 100644 index 24cd2b7..0000000 --- a/goTool/UARTCom/com.go +++ /dev/null @@ -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 -) diff --git a/goTool/api/frontend.go b/goTool/api/frontend.go new file mode 100644 index 0000000..dbdff01 --- /dev/null +++ b/goTool/api/frontend.go @@ -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"` +} diff --git a/goTool/api/uart.go b/goTool/api/uart.go new file mode 100644 index 0000000..4d30cc8 --- /dev/null +++ b/goTool/api/uart.go @@ -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 { +} diff --git a/goTool/config.go b/goTool/config.go new file mode 100644 index 0000000..b134d03 --- /dev/null +++ b/goTool/config.go @@ -0,0 +1,8 @@ +package main + +type Config struct { + Port int + Host string + UartPort string + Baudrate int +} diff --git a/goTool/eventbus/bus.go b/goTool/eventbus/bus.go new file mode 100644 index 0000000..b2cb946 --- /dev/null +++ b/goTool/eventbus/bus.go @@ -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 + } + } +} diff --git a/goTool/frontend/server.go b/goTool/frontend/server.go index 621fdfc..1572b19 100644 --- a/goTool/frontend/server.go +++ b/goTool/frontend/server.go @@ -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") - if err != nil { - log.Printf("Could not Read file %v", err) - } - fmt.Fprintf(w, string(content)) +var upgrader = websocket.Upgrader{} + +type FServer struct { + bus eventbus.EventBus + mux *http.ServeMux } -var upgrader = websocket.Upgrader{} // use default options - -type ValueChangeResp struct { - Cmd string `json:"cmd"` - Name string `json:"name"` - Value string `json:"value"` +func New(bus eventbus.EventBus) *FServer { + fsrv := &FServer{ + bus: bus, + mux: http.NewServeMux(), + } + fsrv.routes() + return fsrv } -func echo(w http.ResponseWriter, r *http.Request) { +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))) - type resp struct { - Cmd string `json:"cmd"` - Arg string `json:"arg"` - } + // WebSocket Endpunkt + fsrv.mux.HandleFunc("/ws", fsrv.handleWS) +} - c, err := upgrader.Upgrade(w, r, nil) +func (fsrv *FServer) handleWS(w http.ResponseWriter, r *http.Request) { + conn, 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 { + 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 { - _, message, err := c.ReadMessage() - if err != nil { - log.Println("read:", err) + 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) +func (fsrv *FServer) Start(addr string) error { + server := &http.Server{ + Addr: addr, + Handler: fsrv.mux, + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } - http.ListenAndServe("0.0.0.0:8000", nil) + log.Printf("Frontend Server gestartet auf %s", addr) + return server.ListenAndServe() } diff --git a/goTool/main.go b/goTool/main.go index 55cbdaf..7a34a24 100644 --- a/goTool/main.go +++ b/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) - if err != nil { - log.Print(err) - break + 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 } - if n == 0 { - fmt.Println("\nEOF") - break + 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.Printf("Could not Parse Version %v", err) + continue + } + 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) } diff --git a/goTool/testrunner/runner.go b/goTool/testrunner/runner.go new file mode 100644 index 0000000..1b961a7 --- /dev/null +++ b/goTool/testrunner/runner.go @@ -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 + } + } +} diff --git a/goTool/testrunner/tests.go b/goTool/testrunner/tests.go new file mode 100644 index 0000000..184c729 --- /dev/null +++ b/goTool/testrunner/tests.go @@ -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 +} diff --git a/goTool/uart/com.go b/goTool/uart/com.go new file mode 100644 index 0000000..9800dfc --- /dev/null +++ b/goTool/uart/com.go @@ -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 +} diff --git a/goTool/uart/commands.go b/goTool/uart/commands.go new file mode 100644 index 0000000..ccf5324 --- /dev/null +++ b/goTool/uart/commands.go @@ -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) + } +} diff --git a/goTool/uart/parser.go b/goTool/uart/parser.go new file mode 100644 index 0000000..0128e3f --- /dev/null +++ b/goTool/uart/parser.go @@ -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 + } +}