Added Working Prototype for Websocket Communication
This commit is contained in:
parent
2070001f4c
commit
6edf7e6e5f
42
goTool/FrontendHandler/frontend.go
Normal file
42
goTool/FrontendHandler/frontend.go
Normal file
@ -0,0 +1,42 @@
|
||||
package frontendhandler
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:embed templates
|
||||
var templatesFs embed.FS
|
||||
|
||||
//go:embed templates/static
|
||||
var staticFiles embed.FS
|
||||
|
||||
func StartStaticFileServer() {
|
||||
staticFS, err := fs.Sub(staticFiles, "templates/static")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
http.HandleFunc("/", IndexHandler)
|
||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
|
||||
|
||||
err = http.ListenAndServe(":8000", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
fileContent, err := templatesFs.ReadFile("templates/index.html")
|
||||
if err != nil {
|
||||
log.Printf("Error Reading index.html %v", err)
|
||||
http.Error(w, "Could not read index.html", 500)
|
||||
}
|
||||
_, err = w.Write(fileContent)
|
||||
if err != nil {
|
||||
log.Printf("Could not Write to ReponseWrite %v", err)
|
||||
http.Error(w, "Could not write data", 500)
|
||||
}
|
||||
}
|
||||
79
goTool/FrontendHandler/templates/index.html
Normal file
79
goTool/FrontendHandler/templates/index.html
Normal file
@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Alox PowerPods Debug/Test Tool</title>
|
||||
<script defer src="/static/alpinejs.cdn.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Alox PowerPods Debug/Test Tool</h1>
|
||||
|
||||
<div x-data="websocketApp()">
|
||||
|
||||
<div style="margin-bottom: 1rem;">
|
||||
Status: <span x-text="status" :style="{ color: isConnected ? 'green' : 'red' }"></span>
|
||||
</div>
|
||||
|
||||
<input type="text" x-model="inputMessage" placeholder="Message" @keyup.enter="sendMessage()" />
|
||||
<button @click="sendMessage()" :disabled="!isConnected">Send</button>
|
||||
|
||||
<hr>
|
||||
|
||||
<div id="messages">
|
||||
<template x-for="(msg, index) in messages" :key="index">
|
||||
<p x-text="msg"></p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function websocketApp() {
|
||||
return {
|
||||
socket: null,
|
||||
inputMessage: '',
|
||||
messages: [],
|
||||
status: 'Disconnected',
|
||||
isConnected: false,
|
||||
|
||||
init() {
|
||||
this.connect();
|
||||
},
|
||||
|
||||
connect() {
|
||||
this.socket = new WebSocket("ws://localhost:8080/cmd");
|
||||
|
||||
this.socket.onopen = () => {
|
||||
this.status = 'Connected';
|
||||
this.isConnected = true;
|
||||
this.addLog('Connected to Server');
|
||||
};
|
||||
|
||||
this.socket.onmessage = (event) => {
|
||||
this.addLog(event.data);
|
||||
};
|
||||
|
||||
this.socket.onclose = () => {
|
||||
this.status = 'Disconnected';
|
||||
this.isConnected = false;
|
||||
this.addLog('Disconnected from Server');
|
||||
};
|
||||
},
|
||||
|
||||
sendMessage() {
|
||||
if (this.inputMessage && this.isConnected) {
|
||||
this.socket.send(this.inputMessage);
|
||||
this.inputMessage = ''; // Input leeren nach Senden
|
||||
}
|
||||
},
|
||||
|
||||
addLog(text) {
|
||||
this.messages.push(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
5
goTool/FrontendHandler/templates/static/alpinejs.cdn.min.js
vendored
Normal file
5
goTool/FrontendHandler/templates/static/alpinejs.cdn.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
46
goTool/FrontendHandler/web.go
Normal file
46
goTool/FrontendHandler/web.go
Normal file
@ -0,0 +1,46 @@
|
||||
package frontendhandler
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type WebHandler struct {
|
||||
MessageC chan string
|
||||
ErrorC chan string
|
||||
}
|
||||
|
||||
var addr = flag.String("addr", "localhost:8080", "http service address")
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
}
|
||||
|
||||
func cmdHandler(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Print("upgrade:", err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
for {
|
||||
mt, message, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("read:", err)
|
||||
break
|
||||
}
|
||||
log.Printf("recv: %s", message)
|
||||
err = c.WriteMessage(mt, message)
|
||||
if err != nil {
|
||||
log.Println("write:", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StartServer() {
|
||||
http.HandleFunc("/cmd", cmdHandler)
|
||||
log.Fatal(http.ListenAndServe(*addr, nil))
|
||||
}
|
||||
358
goTool/UARTCom/com.go
Normal file
358
goTool/UARTCom/com.go
Normal file
@ -0,0 +1,358 @@
|
||||
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,6 +11,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
frontendhandler "alox.tool/FrontendHandler"
|
||||
"github.com/pterm/pterm"
|
||||
"go.bug.st/serial"
|
||||
)
|
||||
@ -362,6 +363,11 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
go frontendhandler.StartStaticFileServer()
|
||||
frontendhandler.StartServer()
|
||||
os.Exit(1)
|
||||
|
||||
flag.StringVar(&updatePath, "update", "", "Path to Updatefile")
|
||||
flag.Parse()
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user