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
|
package frontend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"alox.tool/api"
|
||||||
|
"alox.tool/eventbus"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed www
|
//go:embed www
|
||||||
var staticFiles embed.FS
|
var staticFiles embed.FS
|
||||||
|
|
||||||
func home(w http.ResponseWriter, r *http.Request) {
|
var upgrader = websocket.Upgrader{}
|
||||||
content, err := staticFiles.ReadFile("www/index.html")
|
|
||||||
if err != nil {
|
type FServer struct {
|
||||||
log.Printf("Could not Read file %v", err)
|
bus eventbus.EventBus
|
||||||
}
|
mux *http.ServeMux
|
||||||
fmt.Fprintf(w, string(content))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{} // use default options
|
func New(bus eventbus.EventBus) *FServer {
|
||||||
|
fsrv := &FServer{
|
||||||
type ValueChangeResp struct {
|
bus: bus,
|
||||||
Cmd string `json:"cmd"`
|
mux: http.NewServeMux(),
|
||||||
Name string `json:"name"`
|
}
|
||||||
Value string `json:"value"`
|
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 {
|
// WebSocket Endpunkt
|
||||||
Cmd string `json:"cmd"`
|
fsrv.mux.HandleFunc("/ws", fsrv.handleWS)
|
||||||
Arg string `json:"arg"`
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
log.Print("upgrade:", err)
|
log.Printf("Upgrade error: %v", err)
|
||||||
return
|
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 {
|
for {
|
||||||
_, message, err := c.ReadMessage()
|
select {
|
||||||
if err != nil {
|
case <-ctx.Done():
|
||||||
log.Println("read:", err)
|
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
|
break
|
||||||
}
|
}
|
||||||
log.Printf("recv: %s", message)
|
|
||||||
|
|
||||||
jsonResp := resp{
|
fsrv.bus.Publish(api.TopicFrontendCmd, cmd)
|
||||||
Cmd: "echo",
|
log.Printf("Browser Action: %s auf ID 0x%02X", cmd.Action, cmd.ID)
|
||||||
Arg: string(message),
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.WriteJSON(jsonResp)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("write:", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartServer() {
|
func (fsrv *FServer) Start(addr string) error {
|
||||||
http.Handle("/www/", http.FileServer(http.FS(staticFiles)))
|
server := &http.Server{
|
||||||
http.HandleFunc("/", home)
|
Addr: addr,
|
||||||
http.HandleFunc("/echo", echo)
|
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()
|
||||||
}
|
}
|
||||||
|
|||||||
585
goTool/main.go
585
goTool/main.go
@ -1,560 +1,85 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"alox.tool/api"
|
||||||
|
"alox.tool/eventbus"
|
||||||
"alox.tool/frontend"
|
"alox.tool/frontend"
|
||||||
"github.com/pterm/pterm"
|
"alox.tool/uart"
|
||||||
"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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
config := Config{
|
||||||
frontend.StartServer()
|
Port: 8000,
|
||||||
os.Exit(0)
|
Host: "0.0.0.0",
|
||||||
|
UartPort: "/dev/ttyUSB0",
|
||||||
flag.StringVar(&updatePath, "update", "", "Path to Updatefile")
|
Baudrate: 115200,
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
OTA_UpdateHandler = OTASyncManager{
|
|
||||||
OTA_MessageCounter: 0,
|
|
||||||
OTA_PayloadMessageSequence: 0,
|
|
||||||
NewOTAMessage: make(chan MessageReceive),
|
|
||||||
TimeoutMessage: time.Millisecond * 30000,
|
|
||||||
}
|
}
|
||||||
|
StartApp(config)
|
||||||
|
}
|
||||||
|
|
||||||
mode := &serial.Mode{
|
func StartApp(config Config) {
|
||||||
//BaudRate: 115200,
|
bus := eventbus.New()
|
||||||
BaudRate: 921600,
|
|
||||||
}
|
com, err := uart.Connect(bus, config.UartPort, config.Baudrate)
|
||||||
port, err := serial.Open("/dev/ttyUSB0", mode)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Printf("Could not Connect with Uart Device %v", err)
|
||||||
}
|
}
|
||||||
|
defer com.Close()
|
||||||
ctx, cancle := context.WithCancel(context.Background())
|
|
||||||
defer cancle()
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
buff := make([]byte, 1024)
|
|
||||||
mr := MessageReceive{}
|
|
||||||
initMessageReceive(&mr)
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case msg := <-bus.Subscribe(api.TopicUARTTx):
|
||||||
return
|
log.Printf("MSG[TX]: % X", msg)
|
||||||
default:
|
case msg := <-bus.Subscribe(api.TopicUARTRx):
|
||||||
n, err := port.Read(buff)
|
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 {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Printf("Could not Parse Version %v", err)
|
||||||
break
|
continue
|
||||||
}
|
}
|
||||||
if n == 0 {
|
log.Printf("Version Info %d %X", v.Version, v.Buildhash)
|
||||||
fmt.Println("\nEOF")
|
case api.CmdClientInfo:
|
||||||
break
|
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 != "" {
|
time.Sleep(time.Millisecond * 5)
|
||||||
// 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)
|
|
||||||
|
|
||||||
// start
|
com.Send(api.CmdEcho, make([]byte, 0))
|
||||||
payload_buffer := make([]byte, 1024)
|
com.Send(api.CmdVersion, make([]byte, 0))
|
||||||
send_buffer := make([]byte, 1024)
|
com.Send(api.CmdClientInfo, make([]byte, 0))
|
||||||
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])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update_write_index := 0
|
url := fmt.Sprintf("%s:%d", config.Host, config.Port)
|
||||||
// write update parts
|
fserver := frontend.New(bus)
|
||||||
for update_write_index < len(update) {
|
fserver.Start(url)
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
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