Compare commits

...
72 Commits
Author SHA1 Message Date
skruecken 9b2474c261 Improved GoTool 2026-03-21 15:11:14 +01:00
skruecken 4628041b55 Updated SDK 2026-03-21 15:10:59 +01:00
skruecken 864ffbbd7f Added Network Rebuild Test 2026-03-03 17:42:51 +01:00
skruecken 22a72ee02d Adjusted new Com Connect Logic 2026-02-10 15:58:44 +01:00
skruecken f8aa19d331 Added many Eventbus Structs for the Frontend Handling 2026-02-08 23:49:22 +01:00
skruecken 7534459e73 First api draft for ui communication 2026-02-08 19:11:49 +01:00
skruecken f4f3c695af Refactored Frontend with gemini 2026-02-08 19:11:24 +01:00
skruecken 2fd19db88d Typo 2026-02-08 17:39:34 +01:00
skruecken 0b27d68c0c Installed Formatter and extracted javascript window logic from index 2026-02-08 17:14:37 +01:00
skruecken 0bcacb4a53 Improved Go Tool so that the OTA Update Works again 2026-02-08 17:00:41 +01:00
skruecken c656d4afd7 Added Naming Convetions 2026-02-08 17:00:08 +01:00
skruecken d0ff63783f Removed RAW Logs 2026-02-03 15:34:54 +01:00
skruecken 028b6feae8 Added Dummy Tests 2026-01-27 17:36:41 +01:00
skruecken 3c011e272b Added ClientInput
- refactored some magic numbers
2026-01-27 16:42:24 +01:00
skruecken 0767ddac38 Big Gotool Refactoring
- Added Event Bus
- Reworked Package Parsing
- Rewokred Frame Parsing
2026-01-27 16:23:51 +01:00
skruecken 9efef034f0 Changed to new Frontend for UI Components 2026-01-06 16:14:51 +01:00
skruecken 6edf7e6e5f Added Working Prototype for Websocket Communication 2025-12-30 16:19:43 +01:00
skruecken 2070001f4c Added Config for Tap/Acceleration Sensor 2025-12-30 14:11:33 +01:00
skruecken 50586f2b50 Moved to the bma456h lib 2025-12-30 14:11:12 +01:00
skruecken de747ef463 BMA456 HW Interrupt after 2 Steps working 2025-12-16 17:47:58 +01:00
skruecken 6521e290d6 Prototyped the first configuration and read of the BMA456 Sensor 2025-12-13 13:54:44 +01:00
skruecken f2296a33e6 Create a simpler version of the OTA Update
Using no Broadcast logic for speed but its working now.
There is to much Acks going on but for the prototyp that is okay
2025-09-28 20:52:36 +02:00
skruecken 7097e9e7ab Added Test Data Message for Game Development Testing 2025-09-27 15:57:52 +02:00
skruecken df35def702 Added Function to Read App Image size from Partition 2025-09-27 14:26:32 +02:00
skruecken 337976e637 OTA Update nearly working but could not get image correctly transefered 2025-09-14 13:07:08 +02:00
skruecken d8716c232e Updated Message Handling to new MessageBroker 2025-08-23 21:14:18 +02:00
skruecken a3e330ed77 Added Simple Switch Command Between C3 and S3 2025-08-23 21:13:56 +02:00
skruecken 672267b991 WIP First working version of registered callback esp now logic 2025-08-18 22:38:16 +02:00
skruecken 8398442544 Reworked ESPNOW MessageBrokerTask 2025-08-18 20:27:30 +02:00
Skruecken b29512d922 WIP changes to ota update 2025-08-18 19:56:14 +02:00
Skruecken 6e4525df38 Updated Readme 2025-08-15 14:19:31 +02:00
skruecken 60a304a93d Boilerplate for OTA Update over ESPNOW 2025-08-10 19:43:54 +02:00
skruecken f504553ab6 Added Header Definitions 2025-08-10 15:17:22 +02:00
skruecken bbfe61a9ed Fixed const pointer 2025-08-10 15:14:36 +02:00
skruecken 73bc078465 Moved OTA Functionality to functions to reuse it 2025-08-10 15:08:48 +02:00
skruecken 8d4f1da028 Fixed UART Version output and visualized it in go tool 2025-08-10 13:20:11 +02:00
skruecken 1d36a757c0 Added pagebreak in readme for printing 2025-08-10 13:17:08 +02:00
skruecken 648e201f5e Fixed Readme Layout 2025-08-10 13:05:33 +02:00
skruecken 400d308f4a Fixed Readme Layout 2025-08-10 13:03:40 +02:00
skruecken 1c9120a197 Updated Readme with actual UART Protocol 2025-08-10 12:22:45 +02:00
skruecken 3b560799af Working OTA Update over UART to the Master 2025-08-03 22:52:01 +02:00
skruecken 3abdd8816c Tool Adjustments for OTA Update 2025-08-02 16:13:08 +02:00
skruecken cf42e86322 First Prototype of OTA Uart Update Protkol, not working in this state!!! 2025-08-02 16:12:41 +02:00
skruecken 59dbd7b035 Adjustes UART Message Length to 512 2025-08-02 16:12:09 +02:00
skruecken d3e44125a2 Added Defines, fixed broken function call 2025-07-26 10:42:31 +02:00
skruecken ebb739a3a0 Removed old vibe coded Python Test Tool 2025-07-26 10:39:11 +02:00
skruecken 441347fc95 Added UART MSG IDs and Prep work for OTA 2025-07-26 10:38:26 +02:00
skruecken a9779cbade Added Version to Client Infos 2025-07-26 10:37:57 +02:00
skruecken 704d1c9c0b Added Test of NVS and Partion API 2025-07-26 10:36:31 +02:00
skruecken 95bfcaa4d2 Added OTA Update Strategie writedown 2025-07-26 10:35:40 +02:00
skruecken 01d0be7004 Rebuild Python Tool in Go 2025-07-26 10:35:21 +02:00
skruecken a8c7c42471 Added new Payload Structs for Preperation of OTA Update 2025-07-24 16:11:26 +02:00
skruecken 0934105952 Reworked MAC to String logic to many little buffers 2025-07-24 14:32:25 +02:00
skruecken 55228fff8d Added MultiBoard Support
Support ESP32S3 and ESP32C3 while compiling
2025-07-24 14:15:53 +02:00
skruecken 2bc6686d90 Adjustes Target to ESP32S3 and made uart payload build static 2025-07-24 11:39:29 +02:00
skruecken 73d3e24786 Improved Tool for Testing with Threading 2025-07-23 17:36:28 +02:00
skruecken fad6a0aee2 Added Python Test tool 2025-07-23 16:49:17 +02:00
skruecken 50ee8009fc Fixed Bug in UART Protokol 2025-07-23 16:48:55 +02:00
skruecken beef75f31c Added Message Builder with Tests 2025-07-22 14:31:24 +02:00
skruecken c564fedf65 Reworked Message Parsing and UART Protkol with Tests 2025-07-22 14:29:41 +02:00
skruecken b4d9f24f0e Added Git Hash To Build 2025-07-22 14:22:49 +02:00
skruecken 94b5fd47a4 TMP: Working on a Prototyp of UART Communication
The Prototype will be used as Template for the Code Generation
but first we need an working example
2025-05-20 22:00:33 +02:00
skruecken a3a2c35fed Added Code Generated Proto 2025-05-19 21:58:26 +02:00
skruecken 72486619f2 Reverted Readme 2025-04-16 18:35:54 +02:00
skruecken 21984cff51 Fixed Readme 2025-04-16 18:35:08 +02:00
skruecken 4a50233b7d Fixed Readme 2025-04-16 18:33:05 +02:00
skruecken 51f67458bb Added Start of Uart Protokoll Definition 2025-04-16 18:31:57 +02:00
skruecken b4ce1a5055 Added Client Status Message/Task to send over UART 2025-04-15 17:36:59 +02:00
skruecken d3b6e63cdb Changed ROM Size to 4MB for the ESP32C3 and added OTA Update Partitions 2025-04-15 17:36:05 +02:00
skruecken 185587cdb6 Reworked esp32 wroom to esp32c3 zero board
- Fixed minor Bug from Testing with multiple Nodes
2025-04-06 16:22:25 +02:00
skruecken b09819e76f Refactored Communication Handling 2025-03-22 13:41:15 +01:00
skruecken d26390ea75 Refactored Uart in own module 2025-03-22 13:09:19 +01:00
62 changed files with 13307 additions and 667 deletions
+2
View File
@@ -1,2 +1,4 @@
build/ build/
.cache .cache
alox.protogen
.vscode/
+45
View File
@@ -4,15 +4,57 @@ all:
export: export:
source ~/esp/esp-idf/export.fish && fish source ~/esp/esp-idf/export.fish && fish
get_code_gen:
wget git.aloxdrim.de/skruecken/protgen/actions/runs/latest/artifacts/alox.protogen -O alox.protogen.zip
unzip -o alox.protogen.zip
rm alox.protogen.zip
chmod +x alox.protogen
gen_prot:
./alox.protogen -i prot.json -o main/uart
switch_to_s3:
idf.py set-target esp32s3
cp sdkconfig.s3 sdkconfig
idf.py build
switch_to_c3:
idf.py set-target esp32c3
cp sdkconfig.c3 sdkconfig
idf.py build
buildIdf: buildIdf:
idf.py build idf.py build
flashMini: flashMini:
idf.py flash -p /dev/ttyACM0 idf.py flash -p /dev/ttyACM0
flashMini2:
idf.py flash -p /dev/ttyACM1
flashMini3:
idf.py flash -p /dev/ttyACM2
flashCluster:
idf.py flash -p /dev/ttyACM1
idf.py flash -p /dev/ttyACM2
idf.py flash -p /dev/ttyACM3
idf.py flash -p /dev/ttyACM4
idf.py flash -p /dev/ttyACM5
idf.py flash -p /dev/ttyACM6
idf.py flash -p /dev/ttyACM7
idf.py flash -p /dev/ttyACM8
monitorMini: monitorMini:
idf.py monitor -p /dev/ttyACM0 idf.py monitor -p /dev/ttyACM0
monitorMini1:
idf.py monitor -p /dev/ttyACM1
monitorMini2:
idf.py monitor -p /dev/ttyACM2
flash0: flash0:
idf.py flash -p /dev/ttyUSB0 idf.py flash -p /dev/ttyUSB0
@@ -30,3 +72,6 @@ monitor1:
monitor2: monitor2:
idf.py monitor -p /dev/ttyUSB2 idf.py monitor -p /dev/ttyUSB2
flash_second_ota:
parttool.py --port /dev/ttyACM0 write_partition --partition-name="ota_1" --input build/espAlox.bin
+6
View File
@@ -0,0 +1,6 @@
nvs, data, nvs, 0x9000, 0x4000
otadata, data, ota, 0xd000, 0x2000
phy_init, data, phy, 0xf000, 0x1000
ota_0, app, ota_0, 0x10000, 1900K
ota_1, app, ota_1, , 1900K
nvs_data, data, nvs, , 200K
1 nvs data nvs 0x9000 0x4000
2 otadata data ota 0xd000 0x2000
3 phy_init data phy 0xf000 0x1000
4 ota_0 app ota_0 0x10000 1900K
5 ota_1 app ota_1 1900K
6 nvs_data data nvs 200K
+55
View File
@@ -0,0 +1,55 @@
package api
const (
TopicFrontendCmd = "front:cmd"
)
const (
CmdUpdateValue = "update_value"
CmdInitState = "init_state"
CmdConnect = "connect"
CmdDisconnect = "disconnect"
CmdSendMessage = "send"
CmdRX = "uart_rx"
CmdTX = "uart_tx"
)
var MessageReceiveRegistry = map[string]func() any{
CmdConnect: func() any { return &WsUartConnect{} },
CmdDisconnect: func() any { return &WsUartDisconnect{} },
CmdSendMessage: func() any { return &WsUartSendMessage{} },
}
type WsMessage struct {
Cmd string `json:"cmd"`
Payload []byte `json:"payload,omitempty"`
}
type SystemState struct {
Adapters []string `json:"adapters"`
SelectedAdapter string `json:"selected_adapter"`
Baudrates string `json:"baudrates"`
SelectedBaudrate string `json:"selected_baudrate"`
UartConnected bool `json:"uart_connected"`
}
type WsUartConnect struct {
SelectedAdapter string `json:"selected_adapter"`
Baudrate int `json:"baudrate"`
}
type WsUartDisconnect struct {
}
type WsUartSendMessage struct {
MsgId byte `json:"msg_id"`
Data []byte `json:"data"`
}
type WsUartRX struct {
Data []byte `json:"data"`
}
type WsUartTX struct {
Data []byte `json:"data"`
}
+110
View File
@@ -0,0 +1,110 @@
package api
// Topics
const (
TopicUARTRx = "uart:rx"
TopicUARTTx = "uart:tx"
TopicUARTError = "uart:error"
TopicUartAction = "uart:action"
TopicOTA = "ota"
)
type Frame struct {
Time uint64
ID byte
Data []byte
}
const (
CmdEcho byte = 0x01
CmdVersion byte = 0x02
CmdClientInfo byte = 0x03
CmdClientInput byte = 0x04
CmdOtaStart byte = 0x10
CmdOtaPayload byte = 0x11
CmdOtaEnd byte = 0x12
CmdOtaStatus byte = 0x13
CmdOtaStartEspNow byte = 0x14
)
const (
ClientCountOffset = 1
// Payload Sizes
PayloadVersionSize = 10
PayloadClientInfoSize = 19
PayloadClientInputSize = 13
)
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 {
ClientID byte
X float32
Y float32
InputMask uint32
}
type PayloadOtaStatus struct {
SequenzCounter uint16
WriteIndex uint16
Data []byte
}
type PayloadOtaStart struct {
Data []byte
Parition byte
Error byte
}
type PayloadOtaEnd struct {
Data []byte
}
type PayloadOtaPayload struct {
SequenzCounter uint16
WriteIndex uint16
Data []byte
Error byte
}
type PayloadOtaStartEspNow struct {
Data []byte
}
type ActionUartConnect struct {
Adapter string
Baudrate int
}
type ActionUartConnected struct {
Adapter string
Baudrate int
Error error
}
type ActionUartDisconnect struct {
}
type ActionUartDisconnected struct {
}
type ActionUartSendMessage struct {
MsgId byte
Data []byte
}
+8
View File
@@ -0,0 +1,8 @@
package main
type Config struct {
Port int
Host string
UartPort string
Baudrate int
}
+64
View 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
}
}
}
+175
View File
@@ -0,0 +1,175 @@
package frontend
import (
"context"
"embed"
"encoding/json"
"io/fs"
"log"
"net/http"
"time"
"alox.tool/api"
"alox.tool/eventbus"
"github.com/gorilla/websocket"
)
//go:embed www
var staticFiles embed.FS
var upgrader = websocket.Upgrader{}
type FServer struct {
bus eventbus.EventBus
mux *http.ServeMux
}
func New(bus eventbus.EventBus) *FServer {
fsrv := &FServer{
bus: bus,
mux: http.NewServeMux(),
}
fsrv.routes()
return fsrv
}
func (fsrv *FServer) routes() {
// Static files from the Embed-FS
// remove "www" prefix, so index.html is reachable over /
root, _ := fs.Sub(staticFiles, "www")
fsrv.mux.Handle("/", http.FileServer(http.FS(root)))
fsrv.mux.HandleFunc("/ws", fsrv.handleWS)
}
func (fs *FServer) handleWS(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Upgrade error: %v", err)
return
}
defer conn.Close()
// 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 fs.HandleAppEvents(ctx, conn)
// READER: Commands from Browser
// This Function is Blocking
fs.GetFrontendEvents(ctx, conn)
}
func (fs *FServer) Start(addr string) error {
server := &http.Server{
Addr: addr,
Handler: fs.mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
ctx, cancle := context.WithCancel(context.Background())
defer cancle()
go fs.HandleFrontendEvents(ctx)
log.Printf("Frontend Server gestartet auf %s", addr)
return server.ListenAndServe()
}
func (fs *FServer) HandleAppEvents(ctx context.Context, conn *websocket.Conn) error {
// Kanäle für die Hardware-Events abonnieren
rxChan := fs.bus.Subscribe(api.TopicUARTRx)
txChan := fs.bus.Subscribe(api.TopicUARTTx)
UartActions := fs.bus.Subscribe(api.TopicUartAction)
for {
select {
case <-ctx.Done():
return nil
case f := <-rxChan:
if err := conn.WriteJSON(map[string]any{"type": "rx", "frame": f}); err != nil {
return nil
}
case f := <-txChan:
if err := conn.WriteJSON(map[string]any{"type": "tx", "frame": f}); err != nil {
return nil
}
case msgT := <-UartActions:
switch msg := msgT.(type) {
case api.ActionUartConnected:
// TODO: nicht hier die daten nachhaltig speichern damit sie ans frontend gesendet werden können
// TODO: das muss irgendwo central passieren nicht für jeden client
if msg.Error != nil {
}
continue
case api.ActionUartDisconnected:
continue
}
}
}
}
func (fs *FServer) GetFrontendEvents(ctx context.Context, conn *websocket.Conn) error {
for {
select {
case <-ctx.Done():
return nil
default:
var cmd api.WsMessage
if err := conn.ReadJSON(&cmd); err != nil {
log.Printf("WS Read Error: %v", err)
return err
}
val, ok := api.MessageReceiveRegistry[cmd.Cmd]
if !ok {
log.Printf("No Message Type mapped to %v", cmd.Cmd)
continue
}
valM := val()
err := json.Unmarshal(cmd.Payload, valM)
if err != nil {
log.Printf("Could not Unmarshal payload %v", cmd.Payload)
}
fs.bus.Publish(api.TopicFrontendCmd, valM)
log.Printf("Browser Action: %s auf with %v", cmd.Cmd, cmd.Payload)
}
}
}
func (fs *FServer) HandleFrontendEvents(ctx context.Context) error {
fChan := fs.bus.Subscribe(api.TopicFrontendCmd)
for {
select {
case <-ctx.Done():
return nil
case msg := <-fChan:
switch msgT := msg.(type) {
case api.WsUartSendMessage:
log.Printf("Sending Uart Data % X", msgT.Data)
fs.bus.Publish(api.TopicUartAction, api.ActionUartSendMessage{
MsgId: msgT.MsgId,
Data: msgT.Data,
})
continue
case api.WsUartConnect:
log.Printf("Connect with %s : %d", msgT.SelectedAdapter, msgT.Baudrate)
fs.bus.Publish(api.TopicUartAction, api.ActionUartConnect{
Adapter: msgT.SelectedAdapter,
Baudrate: msgT.Baudrate,
})
continue
case api.WsUartDisconnect:
log.Printf("Disconnect from Uart Adapter")
fs.bus.Publish(api.TopicUartAction, api.ActionUartDisconnect{})
continue
}
}
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+32
View File
@@ -0,0 +1,32 @@
<div x-data="windowBox('window_id' ,100, 100)"
@mousemove.window="onDrag"
@mouseup.window="stopDrag"
@mousedown="focus"
class="card shadow-lg position-absolute"
:class="{ 'w-100 h-100 m-0 shadow-none': fullscreen }"
:style="`left: ${pos.x}px; top: ${pos.y}px; z-index: ${zIndex}; width: ${fullscreen ? '100vw' : '400px'};`"
x-cloak>
<div class="card-header bg-dark text-white d-flex justify-content-between align-items-center drag-handle"
@mousedown="startDrag" @dblclick="toggleFullscreen">
<h6 class="mb-0">CAN Interface</h6>
<div class="d-flex align-items-center gap-1">
<span class="badge me-1" :class="socket.readyState === 1 ? 'bg-success' : 'bg-danger'">WS</span>
<button class="btn btn-sm btn-outline-light py-0 px-2" @click="toggleFullscreen">
<span>▢</span>
</button>
<button class="btn btn-sm btn-outline-light py-0 px-2" @click="minimized = !minimized">
<span x-text="minimized ? '+' : ''"></span>
</button>
</div>
</div>
<div x-show="!minimized" class="flex-grow-1 overflow-auto">
<div class="card-body">
<p>HIER DER INHALT DER COMPONENT</p>
</div>
</div>
</div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

+241
View File
@@ -0,0 +1,241 @@
<!doctype html>
<html>
<head>
<link href="bootstrap.min.css" rel="stylesheet" />
<script src="bootstrap.bundle.min.js"></script>
<script defer src="windows.js"></script>
<script defer src="alpinejs.min.js"></script>
<style>
[x-cloak] {
display: none !important;
}
body {
background-color: #f8f9fa;
background-image:
linear-gradient(90deg, rgba(0, 0, 0, 0.03) 1px, transparent 1px),
linear-gradient(rgba(0, 0, 0, 0.03) 1px, transparent 1px);
background-size: 20px 20px;
height: 100vh;
margin: 0;
padding-top: 56px; /* Space for navbar */
overflow: hidden;
}
.drag-handle {
cursor: move;
}
.card[style*="cursor: move"] {
transition: none;
}
.navbar {
z-index: 2000; /* Above windows */
}
.ws-indicator {
width: 12px;
height: 12px;
border: 1px solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
}
.glow-success {
box-shadow: 0 0 10px #198754;
}
.glow-danger {
box-shadow: 0 0 10px #dc3545;
}
</style>
<script>
document.addEventListener("alpine:init", () => {
Alpine.store("sys", {
ws_connected: false,
adapters: ["/dev/ttyUSB0"],
selected_adapter: "",
baudrates: ["115200", "916000"],
selected_baudrate: "",
uart_connected: false,
});
});
</script>
<script>
let socket;
function connectWS() {
socket = new WebSocket("ws://" + window.location.host + "/ws");
socket.onopen = () => {
console.log("[open] Connection established");
Alpine.store("sys").ws_connected = true;
};
socket.onmessage = (event) => {
try {
let mes = JSON.parse(event.data);
if (mes && mes.cmd === "value") {
Alpine.store(mes.name, mes.value);
}
} catch (e) {
console.log("Invalid JSON:", event.data);
}
};
socket.onclose = () => {
console.log("[close] Connection died");
Alpine.store("sys").ws_connected = false;
setTimeout(connectWS, 2000);
};
socket.onerror = (error) => {
console.log("[error]");
socket.close();
};
}
connectWS();
</script>
</head>
<body x-data>
<!-- Top Navbar -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top shadow">
<div class="container-fluid">
<a class="navbar-brand fw-bold" href="#">
<span class="text-primary">Alox</span> Debug Tool
</a>
<div class="d-flex align-items-center gap-2">
<span class="text-light small opacity-75">Websocket:</span>
<div
class="rounded-circle ws-indicator"
:class="$store.sys.ws_connected ? 'bg-success glow-success' : 'bg-danger glow-danger'"
></div>
</div>
</div>
</nav>
<!-- UART Configuration Window -->
<yet-window
id="uart_config"
title="UART Configuration"
x="50"
y="80"
width="400px"
>
<label class="form-label small fw-bold text-uppercase text-muted"
>Interface</label
>
<div
class="input-group mb-3"
x-data="{ open: false }"
@click.outside="open = false"
>
<button
class="btn btn-outline-secondary dropdown-toggle"
type="button"
@click="open = !open"
:disabled="$store.sys.uart_connected"
>
Adapter
</button>
<ul class="dropdown-menu" :class="{ 'show': open }" x-show="open">
<template x-for="adapter in $store.sys.adapters">
<li>
<button
class="dropdown-item"
type="button"
x-text="adapter"
@click="$store.sys.selected_adapter = adapter; open = false"
></button>
</li>
</template>
</ul>
<input
type="text"
class="form-control bg-light"
readonly
:value="$store.sys.selected_adapter || 'Select Interface...'"
/>
</div>
<label class="form-label small fw-bold text-uppercase text-muted"
>Baudrate</label
>
<div
class="input-group mb-4"
x-data="{ open: false }"
@click.outside="open = false"
>
<button
class="btn btn-outline-secondary dropdown-toggle"
type="button"
@click="open = !open"
:disabled="$store.sys.uart_connected"
>
Speed
</button>
<ul class="dropdown-menu" :class="{ 'show': open }" x-show="open">
<template x-for="rate in $store.sys.baudrates">
<li>
<button
class="dropdown-item"
type="button"
x-text="rate"
@click="$store.sys.selected_baudrate = rate; open = false"
></button>
</li>
</template>
</ul>
<input
type="text"
class="form-control bg-light"
readonly
:value="$store.sys.selected_baudrate || 'Select Baudrate...'"
/>
</div>
<div class="d-grid">
<button
x-show="!$store.sys.uart_connected"
class="btn btn-primary btn-lg"
type="button"
:disabled="!$store.sys.selected_adapter || !$store.sys.selected_baudrate"
@click="socket.send(JSON.stringify({cmd: 'connect', adapter: $store.sys.selected_adapter, baudrate: parseInt($store.sys.selected_baudrate)}))"
>
Connect to UART
</button>
<button
x-show="$store.sys.uart_connected"
x-cloak
class="btn btn-danger btn-lg"
type="button"
@click="socket.send(JSON.stringify({cmd: 'disconnect'}))"
>
Disconnect
</button>
</div>
</yet-window>
<!-- UART Log Window -->
<yet-window
id="uart_log"
title="UART Log"
x="500"
y="80"
width="550px"
header-class="bg-primary text-white"
>
<div
class="bg-dark text-success font-monospace small p-2 rounded shadow-inner"
style="height: 400px; overflow-y: auto"
>
<div class="text-muted small mt-2">
// UART log data will appear here...
</div>
</div>
</yet-window>
</body>
</html>
+153
View File
@@ -0,0 +1,153 @@
// windows.js
document.addEventListener("alpine:init", () => {
// 1. Globaler Store for Window Managment
Alpine.store("Yet_WM", {
topZ: 1000,
getNewZ() {
return ++this.topZ;
},
});
Alpine.data("YetWindow", (id, initialX = 50, initialY = 50) => ({
id: id,
pos: { x: parseInt(initialX), y: parseInt(initialY) },
lastPos: { x: 0, y: 0 },
dragging: false,
minimized: false,
fullscreen: false,
zIndex: 1000,
offset: { x: 0, y: 0 },
init() {
// Lade gespeicherten Zustand (einheitlicher Key: yet_win_)
const saved = JSON.parse(localStorage.getItem(`yet_win_${this.id}`));
if (saved) {
this.pos = { x: saved.x, y: saved.y };
this.minimized = saved.min;
}
this.focus();
this.keepInBounds();
},
focus() {
this.zIndex = Alpine.store("Yet_WM").getNewZ();
},
startDrag(e) {
if (e.target.closest("button") || this.fullscreen) return;
// Verhindert Text-Markierung während des Verschiebens
e.preventDefault();
this.focus();
this.dragging = true;
this.offset.x = e.clientX - this.pos.x;
this.offset.y = e.clientY - this.pos.y;
},
onDrag(e) {
if (!this.dragging) return;
let newX = e.clientX - this.offset.x;
let newY = e.clientY - this.offset.y;
const margin = 20;
this.pos.x = Math.max(margin - 350, Math.min(newX, window.innerWidth - 50));
this.pos.y = Math.max(0, Math.min(newY, window.innerHeight - 40));
},
stopDrag() {
if (this.dragging) {
this.dragging = false;
this.save();
}
},
toggleMinimize() {
this.minimized = !this.minimized;
this.save();
},
toggleFullscreen() {
if (!this.fullscreen) {
this.lastPos = { ...this.pos };
this.pos = { x: 0, y: 0 };
this.fullscreen = true;
} else {
this.pos = { ...this.lastPos };
this.fullscreen = false;
}
this.focus();
},
save() {
localStorage.setItem(
`yet_win_${this.id}`,
JSON.stringify({
x: this.pos.x,
y: this.pos.y,
min: this.minimized,
})
);
},
keepInBounds() {
if (this.pos.x > window.innerWidth) this.pos.x = 50;
if (this.pos.y > window.innerHeight) this.pos.y = 50;
},
}));
});
// Definition der Web Component
class YetWindowElement extends HTMLElement {
connectedCallback() {
const id = this.getAttribute("id") || "win_" + Math.random().toString(36).substr(2, 9);
const title = this.getAttribute("title") || "Window";
const x = this.getAttribute("x") || "50";
const y = this.getAttribute("y") || "50";
const width = this.getAttribute("width") || "450px";
const headerClass = this.getAttribute("header-class") || "bg-dark text-white";
const content = this.innerHTML;
this.innerHTML = `
<div
x-data="YetWindow('${id}', ${x}, ${y})"
@mousemove.window="onDrag"
@mouseup.window="stopDrag"
@mousedown="focus"
class="card shadow-lg position-absolute"
:class="{ 'w-100 h-100 m-0 shadow-none': fullscreen }"
:style="\`left: \${pos.x}px; top: \${pos.y}px; z-index: \${zIndex}; width: \${fullscreen ? '100vw' : '${width}'}; user-select: \${dragging ? 'none' : 'auto'};\`"
x-cloak
>
<div
class="card-header d-flex justify-content-between align-items-center drag-handle ${headerClass}"
@mousedown="startDrag"
@dblclick="toggleFullscreen"
style="user-select: none;"
>
<h6 class="mb-0">${title}</h6>
<div class="d-flex align-items-center gap-1">
<button class="btn btn-sm btn-outline-light py-0 px-2" @click="toggleFullscreen">
<span></span>
</button>
<button class="btn btn-sm btn-outline-light py-0 px-2" @click="toggleMinimize">
<span x-text="minimized ? '+' : ''"></span>
</button>
</div>
</div>
<div x-show="!minimized" class="flex-grow-1 overflow-auto">
<div class="card-body">
${content}
</div>
</div>
</div>
`;
}
}
customElements.define("yet-window", YetWindowElement);
+25
View File
@@ -0,0 +1,25 @@
module alox.tool
go 1.24.5
require (
github.com/gorilla/websocket v1.5.3
github.com/pterm/pterm v0.12.81
go.bug.st/serial v1.6.4
)
require (
atomicgo.dev/cursor v0.2.0 // indirect
atomicgo.dev/keyboard v0.2.9 // indirect
atomicgo.dev/schedule v0.1.0 // indirect
github.com/containerd/console v1.0.5 // indirect
github.com/creack/goselect v0.1.2 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/lithammer/fuzzysearch v1.1.8 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect
)
+126
View File
@@ -0,0 +1,126 @@
atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg=
atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ=
atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw=
atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU=
atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=
atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs=
atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=
github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI=
github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4=
github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY=
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
github.com/pterm/pterm v0.12.81 h1:ju+j5I2++FO1jBKMmscgh5h5DPFDFMB7epEjSoKehKA=
github.com/pterm/pterm v0.12.81/go.mod h1:TyuyrPjnxfwP+ccJdBTeWHtd/e0ybQHkOS/TakajZCw=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+128
View File
@@ -0,0 +1,128 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"time"
"alox.tool/api"
"alox.tool/eventbus"
"alox.tool/frontend"
"alox.tool/state"
"alox.tool/testrunner"
"alox.tool/uart"
)
var (
Tests bool
Baudrate uint
)
func main() {
flag.BoolVar(&Tests, "t", false, "Tests")
flag.UintVar(&Baudrate, "b", 921600, "Baudrate") // 115200
flag.Parse()
log.Printf("Starting with Params %v", Baudrate)
config := Config{
Port: 8000,
Host: "0.0.0.0",
UartPort: "/dev/ttyUSB0",
Baudrate: int(Baudrate),
}
if Tests {
StartTests(config)
return
}
StartApp(config)
}
func StartTests(config Config) {
bus := eventbus.New()
com, err := uart.NewCom(bus)
if err != nil {
log.Printf("Could not Create COM %v", err)
}
err = com.Connect(config.UartPort, config.Baudrate)
if err != nil {
log.Printf("Could not Connect with Uart Device %v", err)
}
defer com.Close()
tr := testrunner.New(bus, com)
testTest := make(map[string]func() error)
testTest["Echo Test"] = tr.RunEchoTest
testTest["Version Test"] = tr.RunVersionTest
testTest["Info Test"] = tr.RunClientInfoTest
testTest["Input Test"] = tr.RunClientInputTest
testTest["Rebuild Network"] = tr.RebuildNetwork
tr.RunTestSet(testTest)
}
func StartApp(config Config) {
bus := eventbus.New()
com, err := uart.NewCom(bus)
ctx, cancle := context.WithCancel(context.Background())
defer cancle()
go com.EventbusHandler(ctx)
if err != nil {
log.Printf("Could not Create Com with Uart Device %v", err)
return
}
err = com.Connect(config.UartPort, config.Baudrate)
if err != nil {
log.Printf("Could not Connect with Uart Device %v", err)
}
defer com.Close()
update, err := os.ReadFile("../espAlox.bin")
if err != nil {
log.Printf("Could not read Update file %v", err)
return
}
updateSlices := SliceUpdate(update, 200)
oManager := NewOTAManager(bus, com, updateSlices)
espHandle := state.New()
espHandle.Start(ctx, bus)
oManager.StartUpdateHandler(ctx)
time.Sleep(time.Millisecond * 5)
//tr := testrunner.New(bus, com)
//tr.RunVersionTest()
time.Sleep(time.Millisecond * 5)
//com.Send(api.CmdEcho, make([]byte, 0))
//com.Send(api.CmdVersion, make([]byte, 0))
com.Send(api.CmdClientInfo, make([]byte, 0))
//com.Send(api.CmdClientInput, make([]byte, 0))
//com.Send(api.CmdOtaStart, make([]byte, 0))
//com.Send(api.CmdOtaStartEspNow, make([]byte, 0))
url := fmt.Sprintf("%s:%d", config.Host, config.Port)
fserver := frontend.New(bus)
fserver.Start(url)
}
func SliceUpdate(update []byte, maxlen int) [][]byte {
updateSlices := [][]byte{}
for i := 0; i < len(update); i += 200 {
end := min(i+200, len(update))
updateSlices = append(updateSlices, update[i:end])
}
return updateSlices
}
+136
View File
@@ -0,0 +1,136 @@
package main
import (
"context"
"log"
"time"
"alox.tool/api"
"alox.tool/eventbus"
"alox.tool/uart"
)
type OTAManager struct {
Bus eventbus.EventBus
Com *uart.Com
Update [][]byte
CurrentSlice uint16
Partition byte
StartTime time.Time
EndTime time.Time
}
func NewOTAManager(bus eventbus.EventBus, com *uart.Com, update [][]byte) OTAManager {
return OTAManager{
Bus: bus,
Com: com,
Update: update,
CurrentSlice: 0,
}
}
func (om *OTAManager) StartUpdateHandler(ctx context.Context) {
RXC := om.Bus.Subscribe(api.TopicUARTRx)
defer om.Bus.Unsubscribe(api.TopicUARTRx, RXC)
go func() {
for {
select {
case <-ctx.Done():
return
case msg := <-RXC:
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)
om.processFrame(val)
}
}
}()
}
func (om *OTAManager) processFrame(val api.Frame) {
switch val.ID {
case api.CmdOtaStart:
msgT, err := uart.ParseFrameOtaStart(val)
if err != nil {
log.Printf("Could not Parse Client Input %v", err)
return
}
// Send First Payload
om.StartTime = time.Now()
om.Partition = msgT.Parition
err = om.Com.Send(api.CmdOtaPayload, om.Update[om.CurrentSlice])
if err != nil {
log.Printf("Error Sending Update Step!: %v", err)
return
}
om.CurrentSlice = om.CurrentSlice + 1
log.Printf("First Update Step %d", om.CurrentSlice)
log.Printf("%v", msgT)
case api.CmdOtaPayload:
msgT, err := uart.ParseFrameOtaPayload(val)
if err != nil {
log.Printf("Could not Parse Client Input %v", err)
return
}
// Send Next Payload until there is no more then send end package
log.Printf("msgT %v", msgT)
if msgT.Error != 0x00 {
log.Printf("Error in Sending Update! Check ESP Log")
return
}
log.Printf("NEXT PAYLOAD")
if om.CurrentSlice == uint16(len(om.Update)) {
log.Printf("LAST PAYLOAD SEND ENDING")
om.Com.Send(api.CmdOtaEnd, make([]byte, 1))
return
}
err = om.Com.Send(api.CmdOtaPayload, om.Update[om.CurrentSlice])
if err != nil {
log.Printf("Error Sending Update Step!: %v", err)
return
}
om.CurrentSlice = om.CurrentSlice + 1
log.Printf("UPDATE CURRENT SLICE %d/%d", om.CurrentSlice, len(om.Update))
log.Printf("UPDATE Part/WriteIndex %d/%d", msgT.SequenzCounter, msgT.WriteIndex)
log.Printf("Progress: %05.2f%%", (float32(om.CurrentSlice)/float32(len(om.Update)))*100)
case api.CmdOtaStatus:
v, err := uart.ParseFrameOtaStatus(val)
if err != nil {
log.Printf("Could not Parse Client Input %v", err)
return
}
log.Printf("%v", v)
// Update State Machine
case api.CmdOtaEnd:
msgT, err := uart.ParseFrameOtaEnd(val)
if err != nil {
log.Printf("Could not Parse Client Input %v", err)
return
}
// End bestätigung
om.EndTime = time.Now()
duration := om.EndTime.Sub(om.StartTime)
log.Printf("Partition %d Update done in %f.2s!", om.Partition, duration.Seconds())
log.Printf("%v", msgT)
case api.CmdOtaStartEspNow:
v, err := uart.ParseFrameOtaStartEspNow(val)
if err != nil {
log.Printf("Could not Parse Client Input %v", err)
return
}
//bus.Publish(api.TopicOTA, v)
log.Printf("%v", v)
}
}
+186
View File
@@ -0,0 +1,186 @@
package state
import (
"context"
"log"
"sync"
"alox.tool/api"
"alox.tool/eventbus"
"alox.tool/uart"
)
type ESPStateHandler struct {
RWMutex sync.RWMutex
Master MasterESP
}
type ESPVersion struct {
Version uint16
Buildhash [7]byte
}
type BaseESP struct {
ClientId byte
IsUpdating bool
UpdateProgress float32
Version ESPVersion
RunningPartition int
MAC [6]byte
}
type MasterESP struct {
BaseESP
Slaves map[byte]*SlaveESP
}
type SlaveESP struct {
BaseESP
SlotAvailable bool
SlotUsed bool
Bitmask uint32
LageX float32
LageY float32
LastPing uint32
LastSuccessfullPing uint32
}
func New() *ESPStateHandler {
return &ESPStateHandler{
RWMutex: sync.RWMutex{},
Master: NewMasterESP(),
}
}
func NewMasterESP() MasterESP {
return MasterESP{
BaseESP: BaseESP{
ClientId: 0,
IsUpdating: false,
UpdateProgress: 0,
Version: ESPVersion{
Version: 0,
Buildhash: [7]byte{},
},
RunningPartition: 0,
MAC: [6]byte{},
},
Slaves: map[byte]*SlaveESP{},
}
}
func NewSlaveESP() *SlaveESP {
return &SlaveESP{
BaseESP: BaseESP{
ClientId: 0,
IsUpdating: false,
UpdateProgress: 0,
Version: ESPVersion{
Version: 0,
Buildhash: [7]byte{},
},
RunningPartition: 0,
MAC: [6]byte{},
},
SlotAvailable: false,
SlotUsed: false,
Bitmask: 0,
LageX: 0,
LageY: 0,
LastPing: 0,
LastSuccessfullPing: 0,
}
}
func (s *ESPStateHandler) Start(ctx context.Context, bus eventbus.EventBus) {
go func() {
RXC := bus.Subscribe(api.TopicUARTRx)
defer bus.Unsubscribe(api.TopicUARTRx, RXC)
for {
select {
case <-ctx.Done():
return
case msg := <-RXC:
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)
s.processFrame(val)
}
}
}()
}
func (s *ESPStateHandler) processFrame(val api.Frame) {
s.RWMutex.Lock()
defer s.RWMutex.Unlock()
switch val.ID {
case api.CmdEcho:
log.Printf("Echo %v", val)
case api.CmdVersion:
v, err := uart.ParseFrameVersion(val)
if err != nil {
log.Printf("Could not Parse Version %v", err)
return
}
log.Printf("Version Info %d %s", v.Version, v.Buildhash)
// Update State
s.Master.Version.Version = v.Version
s.Master.Version.Buildhash = v.Buildhash
case api.CmdClientInfo:
v, err := uart.ParseFrameClientInfo(val)
if err != nil {
log.Printf("Could not Parse Client Info %v", err)
return
}
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)
_, ok := s.Master.Slaves[c.ClientID]
if !ok {
s.Master.Slaves[c.ClientID] = NewSlaveESP()
}
s.Master.Slaves[c.ClientID].SlotAvailable = c.IsAvailable != 0
s.Master.Slaves[c.ClientID].SlotUsed = c.SlotIsUsed != 0
s.Master.Slaves[c.ClientID].LastPing = c.LastPing
s.Master.Slaves[c.ClientID].LastSuccessfullPing = c.LastSuccessfulPing
s.Master.Slaves[c.ClientID].Version.Version = c.Version
s.Master.Slaves[c.ClientID].MAC = c.MACAddr
}
case api.CmdClientInput:
v, err := uart.ParseFrameClientInput(val)
if err != nil {
log.Printf("Could not Parse Client Input %v", err)
return
}
for _, c := range v {
log.Printf("Client ID %d", c.ClientID)
log.Printf("\tX %f", c.X)
log.Printf("\tY %f", c.Y)
log.Printf("\tBitmask %08b", c.InputMask)
_, ok := s.Master.Slaves[c.ClientID]
if !ok {
s.Master.Slaves[c.ClientID] = NewSlaveESP()
}
s.Master.Slaves[c.ClientID].LageX = c.X
s.Master.Slaves[c.ClientID].LageY = c.Y
s.Master.Slaves[c.ClientID].Bitmask = c.InputMask
}
}
}
+18
View File
@@ -0,0 +1,18 @@
package testrunner
import (
"os/exec"
)
const (
ESPTOOLPATH = "/home/simon/.espressif/python_env/idf5.5_py3.14_env/bin/esptool.py"
)
func RestartESPOnPort(port string) error {
cmd := exec.Command(ESPTOOLPATH, "--port", port, "run")
err := cmd.Run()
if err != nil {
return err
}
return nil
}
+82
View File
@@ -0,0 +1,82 @@
package testrunner
import (
"context"
"fmt"
"io"
"log"
"os"
"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) RunTestSet(TSet map[string]func() error) {
null, _ := os.OpenFile(os.DevNull, os.O_WRONLY, 0)
defer null.Close()
// Backups der originalen Deskriptoren
oldStdout := os.Stdout
oldStderr := os.Stderr
oldLogOut := log.Writer()
for name, f := range TSet {
// 1. Output komplett abdrehen
os.Stdout = null
os.Stderr = null
log.SetOutput(io.Discard)
err := f()
// 2. Sofort wiederherstellen für das Log-Resultat
os.Stdout = oldStdout
os.Stderr = oldStderr
log.SetOutput(oldLogOut)
if err != nil {
log.Printf("[%s]: \t\tFailed: %v", name, err)
continue
}
log.Printf("[%s]: \t\tSucceeded", name)
}
}
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
}
}
}
+139
View File
@@ -0,0 +1,139 @@
package testrunner
import (
"fmt"
"slices"
"time"
"alox.tool/api"
"alox.tool/uart"
)
func (tr *TestRunner) RunEchoTest() error {
frame, err := tr.Expect(api.CmdEcho, []byte{0x01}, api.CmdEcho, 1*time.Second)
if err != nil {
return err
}
if frame.Data[0] != 0x01 {
return fmt.Errorf("Got % X expected % X", frame.Data[0], 0x01)
}
return nil
}
func (tr *TestRunner) RunVersionTest() error {
frame, err := tr.Expect(api.CmdVersion, nil, api.CmdVersion, 1*time.Second)
if err != nil {
return err
}
// TODO: Check Version and Buildhash?
_, err = uart.ParseFrameVersion(*frame)
if err != nil {
return err
}
v, err := uart.ParseFrameVersion(*frame)
if v.Version != 1 {
return fmt.Errorf("Got %d expected %d", v.Version, 1)
}
return nil
}
func (tr *TestRunner) RunClientInfoTest() error {
_, err := tr.Expect(api.CmdClientInfo, nil, api.CmdClientInfo, 1*time.Second)
if err != nil {
return err
}
// TODO: Real Check
return nil
}
func (tr *TestRunner) RunClientInputTest() error {
_, err := tr.Expect(api.CmdClientInput, nil, api.CmdClientInput, 1*time.Second)
if err != nil {
return err
}
// TODO: Real Check
return nil
}
func (tr *TestRunner) RebuildNetwork() error {
// Hardcoded for now
Clients := map[string]string{
"C1": "/dev/ttyACM1",
"C2": "/dev/ttyACM2",
"C3": "/dev/ttyACM3",
"C4": "/dev/ttyACM4",
}
// Hardcoded for now
macAddresses := [][6]byte{
{0x50, 0x78, 0x7D, 0x18, 0x85, 0xA0},
{0x50, 0x78, 0x7D, 0x18, 0x7D, 0x74},
{0x50, 0x78, 0x7D, 0x18, 0x1D, 0x14},
{0x50, 0x78, 0x7D, 0x18, 0x00, 0x10},
}
// Hardcoded for now
err := RestartESPOnPort("/dev/ttyACM0")
if err != nil {
return fmt.Errorf("Could not Restart Master!")
}
time.Sleep(500 * time.Millisecond)
for c, p := range Clients {
fmt.Printf("Restarting Client %v", c)
err = RestartESPOnPort(p)
if err != nil {
fmt.Printf("Could not Restart Client %v on Port %v", c, p)
return err
}
time.Sleep(500 * time.Millisecond)
}
success := false
var frame *api.Frame
var clientInfos []api.PayloadClientInfo
var lastError string
for i := 0; i <= 5; i++ {
time.Sleep(1000 * time.Millisecond) // inital sleep for first network setup
var err error // for local error scope
frame, err = tr.Expect(api.CmdClientInfo, nil, api.CmdClientInfo, 1*time.Second)
if err != nil {
lastError = "No Uart Message"
continue
}
clientInfos, err = uart.ParseFrameClientInfo(*frame)
if err != nil {
lastError = fmt.Sprintf("Could not parse Message: %v", err)
continue
}
if len(clientInfos) != len(macAddresses) {
lastError = fmt.Sprintf("Want %v Clients, got %v Clients", len(macAddresses), len(clientInfos))
continue
}
success = true
break
}
if !success {
return fmt.Errorf("%v", lastError)
}
for _, client := range clientInfos {
if !slices.Contains(macAddresses, client.MACAddr) {
return fmt.Errorf("Client %v not found in expected list %v", client.MACAddr, macAddresses)
}
}
return nil
}
+138
View File
@@ -0,0 +1,138 @@
package uart
import (
"context"
"fmt"
"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 NewCom(bus eventbus.EventBus) (*Com, error) {
return &Com{
bus: bus,
port: nil,
cancel: nil,
}, nil
}
func (c *Com) Connect(portName string, baudrate int) error {
if c.port != nil {
return fmt.Errorf("Port already connected")
}
mode := &serial.Mode{BaudRate: baudrate}
port, err := serial.Open(portName, mode)
if err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
drv := New(c.bus)
go func() {
buff := make([]byte, 1024)
for {
select {
case <-ctx.Done():
return
default:
n, err := port.Read(buff)
if err != nil {
log.Print("[Warning]: 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)
}
}
}
}
}()
c.port = port
c.cancel = cancel
return 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]: %v", raw)
//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
}
func (c *Com) EventbusHandler(ctx context.Context) error {
UActions := c.bus.Subscribe(api.TopicUartAction)
for {
select {
case <-ctx.Done():
return nil
case msgT := <-UActions:
switch msg := msgT.(type) {
case api.ActionUartConnect:
err := c.Connect(msg.Adapter, msg.Baudrate)
c.bus.Publish(api.TopicUartAction, api.ActionUartConnected{
Adapter: msg.Adapter,
Baudrate: msg.Baudrate,
Error: err,
})
case api.ActionUartDisconnect:
c.Close()
c.bus.Publish(api.TopicUartAction, api.ActionUartDisconnected{})
case api.ActionUartSendMessage:
c.Send(msg.MsgId, msg.Data)
}
}
}
}
+189
View File
@@ -0,0 +1,189 @@
package uart
import (
"encoding/binary"
"fmt"
"log"
"math"
"alox.tool/api"
)
func ParseFrameVersion(frame api.Frame) (api.PayloadVersion, error) {
if len(frame.Data) != api.PayloadVersionSize {
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) != api.PayloadClientInfoSize {
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 * api.PayloadClientInfoSize)
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 * api.PayloadClientInfoSize)
end := start + api.PayloadClientInfoSize
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 parseFrameClientInputPart(data []byte) (api.PayloadClientInput, error) {
if len(data) != api.PayloadClientInputSize {
return api.PayloadClientInput{}, fmt.Errorf("payload wrong size: got %d bytes, want 13", len(data))
}
v := api.PayloadClientInput{
ClientID: data[0],
X: math.Float32frombits(binary.LittleEndian.Uint32(data[1 : 1+4])),
Y: math.Float32frombits(binary.LittleEndian.Uint32(data[5 : 5+4])),
InputMask: binary.LittleEndian.Uint32(data[9 : 9+4]),
}
return v, nil
}
func ParseFrameClientInput(frame api.Frame) ([]api.PayloadClientInput, 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 * api.PayloadClientInputSize)
if len(frame.Data) < expectedLen {
return nil, fmt.Errorf("frame data too short: got %d, want %d", len(frame.Data), expectedLen)
}
clientList := make([]api.PayloadClientInput, 0, clientCount)
for i := 0; i < clientCount; i++ {
start := 1 + (i * api.PayloadClientInputSize)
end := start + api.PayloadClientInputSize
client, err := parseFrameClientInputPart(frame.Data[start:end])
if err != nil {
log.Printf("Could not parse client art %d: %v", i, err)
continue
}
clientList = append(clientList, client)
}
return clientList, nil
}
// Dummy for now Just get Data
func ParseFrameOtaPayload(frame api.Frame) (api.PayloadOtaPayload, error) {
if len(frame.Data) == 0 {
return api.PayloadOtaPayload{}, fmt.Errorf("empty frame data")
}
status := api.PayloadOtaPayload{
Data: frame.Data,
SequenzCounter: binary.LittleEndian.Uint16(frame.Data[0:2]),
WriteIndex: binary.LittleEndian.Uint16(frame.Data[2:4]),
Error: frame.Data[4],
}
return status, nil
}
// Dummy for now Just get Data
func ParseFrameOtaStatus(frame api.Frame) (api.PayloadOtaStatus, error) {
if len(frame.Data) == 0 {
return api.PayloadOtaStatus{}, fmt.Errorf("empty frame data")
}
status := api.PayloadOtaStatus{
Data: frame.Data,
SequenzCounter: binary.LittleEndian.Uint16(frame.Data[0:2]),
WriteIndex: binary.LittleEndian.Uint16(frame.Data[2:4]),
}
return status, nil
}
// Dummy for now Just get Data
func ParseFrameOtaStart(frame api.Frame) (api.PayloadOtaStart, error) {
if len(frame.Data) == 0 {
return api.PayloadOtaStart{}, fmt.Errorf("empty frame data")
}
status := api.PayloadOtaStart{
Data: frame.Data,
Parition: frame.Data[0],
Error: frame.Data[1],
}
return status, nil
}
// Dummy for now Just get Data
func ParseFrameOtaEnd(frame api.Frame) (api.PayloadOtaEnd, error) {
if len(frame.Data) == 0 {
return api.PayloadOtaEnd{}, fmt.Errorf("empty frame data")
}
status := api.PayloadOtaEnd{
Data: frame.Data,
}
return status, nil
}
// Dummy for now Just get Data
func ParseFrameOtaStartEspNow(frame api.Frame) (api.PayloadOtaStartEspNow, error) {
if len(frame.Data) == 0 {
return api.PayloadOtaStartEspNow{}, fmt.Errorf("empty frame data")
}
status := api.PayloadOtaStartEspNow{
Data: frame.Data,
}
return status, nil
}
+128
View 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
}
}
View File
+22 -1
View File
@@ -1,3 +1,24 @@
idf_component_register(SRCS "main.c" idf_component_register(SRCS "main.c" "uart_handler.c" "communication_handler.c" "client_handler.c" "message_parser.c" "message_builder.c" "message_handler.c" "ota_update.c" "i2c.c"
INCLUDE_DIRS ".") INCLUDE_DIRS ".")
# Get the short Git commit hash of the current HEAD.
# If not in a Git repository or git command fails, it will default to "N/A".
execute_process(
COMMAND git rev-parse --short HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_HASH_SHORT
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE GIT_HASH_RESULT
)
# Fallback if git is not available or not in a git repo
if(GIT_HASH_RESULT_CODE)
set(GIT_COMMIT_HASH_SHORT "N/A")
endif()
# Add the Git hash as a preprocessor definition to your component.
# This makes BUILD_GIT_HASH available in your C/C++ source files.
target_compile_definitions(${COMPONENT_LIB} PRIVATE
BUILD_GIT_HASH="${GIT_COMMIT_HASH_SHORT}"
)
+57
View File
@@ -0,0 +1,57 @@
#include "client_handler.h"
#include "esp_log.h"
#include "freertos/task.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
int get_client_id(ClientList *list, const uint8_t *client_mac) {
for (int i = 0; i < MAX_CLIENTS; i++) {
if (memcmp(client_mac, list->Clients[i].macAddr, MAC_LENGTH) == 0) {
return i;
}
}
return CLIENT_DOES_NOT_EXISTS;
}
// TODO: Sanity check when list full then list->count should be MAX_CLIENTS
int get_next_free_slot(ClientList *list) {
for (int i = 0; i < MAX_CLIENTS; i++) {
// if slot is not used return index
if (!list->Clients[i].slotIsUsed) {
return i;
}
}
// list is full
return CLIENT_LIST_FULL;
}
int add_client(ClientList *list, const uint8_t *client_mac) {
if (get_client_id(list, client_mac) >= 0) {
// Client already exists dont add to list
return CLIENT_EXISTS;
}
int slot = get_next_free_slot(list);
if (slot < 0) {
// Client list full
return CLIENT_LIST_FULL;
}
list->Clients[slot].slotIsUsed = true;
list->Clients[slot].isAvailable = true;
list->Clients[slot].last_seen = xTaskGetTickCount();
list->Clients[slot].retry_counter = 0;
memcpy(list->Clients[slot].macAddr, client_mac, MAC_LENGTH);
list->ClientCount++;
return CLIENT_OK;
}
int remove_client(ClientList *list, const uint8_t client_id) {
if (client_id >= MAX_CLIENTS)
return CLIENT_INVALID_ID; // invalid index
list->Clients[client_id].slotIsUsed = false;
list->ClientCount--;
return CLIENT_OK;
}
+60
View File
@@ -0,0 +1,60 @@
#ifndef CLIENT_HANDLER_H
#define CLIENT_HANDLER_H
#include "freertos/FreeRTOS.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/_intsup.h>
#include <sys/types.h>
#define MAX_CLIENTS 16
#define MAC_LENGTH 6
enum ClientErrors {
CLIENT_OK = 0,
CLIENT_EXISTS = -1,
CLIENT_DOES_NOT_EXISTS = -2,
CLIENT_LIST_FULL = -3,
CLIENT_INVALID_ID = -4,
};
typedef enum {
OTA_IDLE,
OTA_AWAITING_ACK,
OTA_PREPARING,
OTA_READY,
OTA_UPDATING,
OTA_FAILED,
OTA_SUCCESS,
} ota_status_t;
typedef struct {
bool slotIsUsed;
bool isAvailable;
uint8_t clientID;
uint8_t macAddr[MAC_LENGTH];
TickType_t lastSuccessfullPing;
TickType_t lastPing;
uint16_t clientVersion;
ota_status_t ota_status;
uint16_t current_block_id;
uint32_t chunk_bitmask;
uint32_t resent_chunks_counter;
uint8_t retry_counter;
TickType_t last_seen;
} ClientInfo;
typedef struct {
ClientInfo Clients[MAX_CLIENTS];
uint8_t ClientCount;
} ClientList;
int get_client_id(ClientList *list, const uint8_t *client_mac);
int add_client(ClientList *list, const uint8_t *client_mac);
int remove_client(ClientList *list, const uint8_t clientid);
int get_next_free_slot(ClientList *list);
#endif
+432
View File
@@ -0,0 +1,432 @@
#include "communication_handler.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_now.h"
#include "esp_ota_ops.h"
#include "esp_partition.h"
#include "esp_timer.h"
#include "freertos/idf_additions.h"
#include "freertos/task.h"
#include "message_structs.h"
#include "ota_update.h"
#include "client_handler.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
uint8_t broadcast_address[ESP_NOW_ETH_ALEN] = {0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF};
static const char *TAG = "ALOX - COM";
static QueueHandle_t messageQueue = NULL;
static struct ESP_MessageBroker mr;
static QueueHandle_t ESP_recieved_message_queue;
void free_ESPNOW_MessageInfo(ESPNOW_MessageInfo *msg) {
if (msg->esp_now_info.src_addr) {
free(msg->esp_now_info.src_addr);
msg->esp_now_info.src_addr = NULL;
}
if (msg->esp_now_info.des_addr) {
free(msg->esp_now_info.des_addr);
msg->esp_now_info.des_addr = NULL;
}
if (msg->esp_now_info.rx_ctrl) {
free(msg->esp_now_info.rx_ctrl);
msg->esp_now_info.rx_ctrl = NULL;
}
if (msg->data) {
free(msg->data);
msg->data = NULL;
}
}
void ESP_InitMessageBroker(QueueHandle_t msg_queue_handle) {
mr.num_direct_callbacks = 0;
mr.num_task_callbacks = 0;
ESP_recieved_message_queue = msg_queue_handle;
return;
}
void ESP_RegisterFunction(CommandPages command,
ESP_RegisterFunctionCallback callback) {
mr.FunctionList[mr.num_direct_callbacks].MSGID = command;
mr.FunctionList[mr.num_direct_callbacks].callback = callback;
mr.num_direct_callbacks++;
return;
}
void ESP_RegisterTask(CommandPages command, ESP_RegisterTaskCallback callback) {
mr.TaskList[mr.num_task_callbacks].MSGID = command;
mr.TaskList[mr.num_task_callbacks].task = callback;
mr.num_task_callbacks++;
}
void ESP_MessageBrokerTask(void *param) {
ESPNOW_MessageInfo received_msg;
ESP_MessageBrokerTaskParams_t *task_params =
(ESP_MessageBrokerTaskParams_t *)param;
QueueHandle_t msg_queue = task_params->message_queue;
if (msg_queue == NULL) {
ESP_LOGE(TAG, "Message queue not initialized. Terminating task.");
vTaskDelete(NULL);
}
ESP_LOGI(TAG, "Message broker task started.");
while (1) {
if (xQueueReceive(msg_queue, &received_msg, portMAX_DELAY)) {
const BaseMessage *message = (const BaseMessage *)received_msg.data;
for (int i = 0; i < mr.num_direct_callbacks; i++) {
if (mr.FunctionList[i].MSGID == message->commandPage) {
mr.FunctionList[i].callback(&received_msg.esp_now_info,
received_msg.data, received_msg.data_len);
free_ESPNOW_MessageInfo(&received_msg);
}
}
}
}
}
static bool hasMaster = false;
static ClientList *esp_client_list;
static uint8_t channelNumber = 0;
int init_com(ClientList *clients, uint8_t wifi_channel) {
messageQueue = xQueueCreate(MESSAGE_QUEUE_SIZE, sizeof(ESPNOW_MessageInfo));
if (messageQueue == NULL) {
ESP_LOGE(TAG, "Message queue creation failed");
return -1;
}
esp_client_list = clients;
hasMaster = false;
channelNumber = wifi_channel;
return 0;
}
int add_peer(uint8_t *macAddr) {
esp_now_peer_info_t peerInfo = {
.channel = channelNumber,
.ifidx = ESP_IF_WIFI_STA,
.encrypt = false,
};
memcpy(peerInfo.peer_addr, macAddr, ESP_NOW_ETH_ALEN);
esp_err_t result = esp_now_add_peer(&peerInfo);
if (result == ESP_OK) {
ESP_LOGI(TAG, "Peer added");
if (!IS_BROADCAST_ADDR(macAddr)) {
int ret = add_client(esp_client_list, peerInfo.peer_addr);
if (ret < 0) {
ESP_LOGE(TAG, "Client could not be added to client handler, removing "
"it from esp now client list!");
esp_now_del_peer(peerInfo.peer_addr);
return -1;
}
ESP_LOGI(TAG, "New client added.");
}
} else if (result == ESP_ERR_ESPNOW_EXIST) {
ESP_LOGW(TAG, "Peer already exists.");
int id = get_client_id(esp_client_list, peerInfo.peer_addr);
if (id >= 0) {
esp_client_list->Clients[id].isAvailable = true;
}
} else {
ESP_LOGE(TAG, "Failed to add peer: %s", esp_err_to_name(result));
return -1;
}
return 0;
}
BaseMessage MessageBuilder(CommandPages commandPage, PayloadUnion payload,
size_t payload_size) {
BaseMessage message;
message.commandPage = commandPage;
message.version = 1;
message.length = (uint16_t)payload_size;
memset(&message.payload, 0, sizeof(message.payload));
memcpy(&message.payload, &payload, payload_size);
return message;
}
void master_broadcast_task(void *param) {
while (1) {
BroadCastPayload payload = {};
BaseMessage message = MessageBuilder(
BroadCastPage, *(PayloadUnion *)&payload, sizeof(payload));
ESP_ERROR_CHECK(esp_now_send(broadcast_address, (uint8_t *)&message,
sizeof(BaseMessage)));
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
void master_broadcast_ping(void *param) {
while (1) {
PingPayload payload = {};
payload.timestamp = esp_timer_get_time();
BaseMessage message =
MessageBuilder(PingPage, *(PayloadUnion *)&payload, sizeof(payload));
ESP_ERROR_CHECK(esp_now_send(broadcast_address, (uint8_t *)&message,
sizeof(BaseMessage)));
vTaskDelay(pdMS_TO_TICKS(2500));
}
}
void master_ping_task(void *param) {
while (1) {
for (size_t i = 0; i < MAX_CLIENTS; i++) {
if (esp_client_list->Clients[i].isAvailable) {
PingPayload payload = {};
payload.timestamp = esp_timer_get_time();
BaseMessage message = MessageBuilder(
PingPage, *(PayloadUnion *)&payload, sizeof(payload));
esp_now_send(esp_client_list->Clients[i].macAddr, (uint8_t *)&message,
sizeof(BaseMessage));
}
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void master_StatusCallback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len) {
const BaseMessage *message = (const BaseMessage *)data;
ESP_LOGI(TAG, "SRC");
ESP_LOGI(TAG,
"Status Message Received: status: %d, runningPartition: %d, uptime: "
"%d, version: %d",
message->payload.status_payload.status,
message->payload.status_payload.runningPartition,
message->payload.status_payload.uptime,
message->payload.status_payload.version);
}
void master_RegisterCallback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len) {
BaseMessage replyMessage = {};
esp_now_peer_info_t checkPeerInfo;
esp_err_t checkPeer =
esp_now_get_peer(esp_now_info->src_addr, &checkPeerInfo);
switch (checkPeer) {
case (ESP_OK):
int id = get_client_id(esp_client_list, esp_now_info->src_addr);
esp_client_list->Clients[id].isAvailable = true;
esp_client_list->Clients[id].lastSuccessfullPing = xTaskGetTickCount();
break;
case (ESP_ERR_ESPNOW_NOT_FOUND):
add_peer(esp_now_info->src_addr);
GetStatusPayload payload = {};
replyMessage = MessageBuilder(GetStatusPage, *(PayloadUnion *)&payload,
sizeof(payload));
esp_now_send(esp_now_info->src_addr, (uint8_t *)&replyMessage,
sizeof(BaseMessage));
break;
default:
break;
}
}
void master_pingCallback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len) {
const BaseMessage *message = (const BaseMessage *)data;
uint32_t currentTime = esp_timer_get_time();
uint32_t diff = currentTime - message->payload.ping_payload.timestamp;
int id = get_client_id(esp_client_list, esp_now_info->src_addr);
if (id >= 0) {
esp_client_list->Clients[id].lastSuccessfullPing = xTaskGetTickCount();
esp_client_list->Clients[id].lastPing = (diff / 1000);
}
}
void master_broadcastCallback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len) {}
void ESPNOW_RegisterMasterCallbacks() {
ESP_RegisterFunction(StatusPage, master_StatusCallback);
ESP_RegisterFunction(RegisterPage, master_RegisterCallback);
ESP_RegisterFunction(PingPage, master_pingCallback);
ESP_RegisterFunction(BroadCastPage, master_broadcastCallback);
}
void slave_broadcastCallback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len) {
if (!hasMaster) {
if (IS_BROADCAST_ADDR(esp_now_info->des_addr)) {
add_peer(esp_now_info->src_addr);
BaseMessage replyMessage = {};
replyMessage =
MessageBuilder(RegisterPage, *(PayloadUnion *)&replyMessage.payload,
sizeof(replyMessage.payload));
esp_now_send(esp_now_info->src_addr, (uint8_t *)&replyMessage,
sizeof(BaseMessage));
hasMaster = true;
}
}
}
void slave_getstatusCallback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len) {
StatusPayload payload = {
.status = 1,
.runningPartition = 1,
.uptime = 100,
.version = 0x0002,
};
BaseMessage replyMessage =
MessageBuilder(StatusPage, *(PayloadUnion *)&payload, sizeof(payload));
esp_now_send(esp_now_info->src_addr, (uint8_t *)&replyMessage,
sizeof(BaseMessage));
}
void slave_pingCallback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len) {
if (!hasMaster)
return;
const BaseMessage *message = (const BaseMessage *)data;
BaseMessage replyMessage = MessageBuilder(
PingPage, *(PayloadUnion *)&message->payload, sizeof(message->payload));
esp_now_send(esp_now_info->src_addr, (uint8_t *)&replyMessage,
sizeof(BaseMessage));
}
void ESPNOW_RegisterSlaveCallbacks() {
ESP_RegisterFunction(BroadCastPage, slave_broadcastCallback);
ESP_RegisterFunction(GetStatusPage, slave_getstatusCallback);
ESP_RegisterFunction(PingPage, slave_pingCallback);
}
void master_receive_callback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len) {
uint8_t *copied_data = (uint8_t *)malloc(data_len);
if (copied_data == NULL) {
return;
}
memcpy(copied_data, data, data_len);
ESPNOW_MessageInfo msg_info;
msg_info.esp_now_info.src_addr = malloc(6);
if (msg_info.esp_now_info.src_addr) {
memcpy(msg_info.esp_now_info.src_addr, esp_now_info->src_addr, 6);
}
msg_info.esp_now_info.des_addr = malloc(6);
if (msg_info.esp_now_info.des_addr) {
memcpy(msg_info.esp_now_info.des_addr, esp_now_info->des_addr, 6);
}
msg_info.esp_now_info.rx_ctrl = malloc(sizeof(wifi_pkt_rx_ctrl_t));
if (msg_info.esp_now_info.rx_ctrl) {
memcpy(msg_info.esp_now_info.rx_ctrl, esp_now_info->rx_ctrl,
sizeof(wifi_pkt_rx_ctrl_t));
}
msg_info.data = copied_data;
msg_info.data_len = data_len;
xQueueSend(ESP_recieved_message_queue, &msg_info, portMAX_DELAY);
}
void client_receive_callback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len) {
uint8_t *copied_data = (uint8_t *)malloc(data_len);
if (copied_data == NULL) {
return;
}
memcpy(copied_data, data, data_len);
ESPNOW_MessageInfo msg_info;
// Initialize the esp_now_info struct to zeros
memset(&msg_info.esp_now_info, 0, sizeof(esp_now_recv_info_t));
// Now, allocate and copy the data pointed to by the pointers within
// esp_now_info src_addr
msg_info.esp_now_info.src_addr = malloc(6);
if (msg_info.esp_now_info.src_addr) {
memcpy(msg_info.esp_now_info.src_addr, esp_now_info->src_addr, 6);
} else {
free(copied_data);
return;
}
// des_addr
msg_info.esp_now_info.des_addr = malloc(6);
if (msg_info.esp_now_info.des_addr) {
memcpy(msg_info.esp_now_info.des_addr, esp_now_info->des_addr, 6);
} else {
free(msg_info.esp_now_info.src_addr);
free(copied_data);
return;
}
msg_info.esp_now_info.rx_ctrl = NULL; // Set to NULL as we are not copying it
msg_info.data = copied_data;
msg_info.data_len = data_len;
xQueueSend(ESP_recieved_message_queue, &msg_info, portMAX_DELAY);
}
void client_monitor_task(void *pvParameters) {
TickType_t timeout_ticks = pdMS_TO_TICKS(CLIENT_TIMEOUT_MS);
TickType_t interval_ticks = pdMS_TO_TICKS(CHECK_INTERVAL_MS);
while (1) {
TickType_t now = xTaskGetTickCount();
for (int i = 0; i < MAX_CLIENTS; i++) {
if (esp_client_list->Clients[i].isAvailable) {
TickType_t time_diff =
now - esp_client_list->Clients[i].lastSuccessfullPing;
if (time_diff > timeout_ticks) {
esp_client_list->Clients[i].isAvailable = false;
}
}
}
vTaskDelay(interval_ticks);
}
}
const esp_partition_t *ota_update_partition = NULL;
void ESPNOW_RegisterOTAMaster() {
// Observe this States for all Slaves in ClientList
// OTA_SLAVE_PREPARING
// OTA_SLAVE_READY
// OTA_SLAVE_ERROR
// OTA_SLAVE_WRITE_FINISHED
// OTA_SLAVE_FINISHED
ESP_RegisterFunction(OTA_PREPARE_ACKNOWLEDGED,
master_ota_prepare_acknowledge_callback);
ESP_RegisterFunction(OTA_READY_TO_RECEIVE,
master_ota_ready_to_recieve_callback);
ESP_RegisterFunction(OTA_UPDATE_SLAVE_ACKED,
master_ota_update_slave_acknowledge_callback);
}
void ESPNOW_RegisterOTASlave() {
ESP_RegisterFunction(OTA_PREPARE_FOR_UPDATE, slave_Prep_Upgrade_Callback);
ESP_RegisterFunction(OTA_CHUNK, slave_Update_Chunk_Callback);
ESP_RegisterFunction(OTA_FINISH_UPDATE, slave_Update_Finished_Callback);
}
+122
View File
@@ -0,0 +1,122 @@
#ifndef COMMUNICATION_HANDLER_H
#define COMMUNICATION_HANDLER_H
#include "client_handler.h"
#include <esp_now.h>
#include <esp_wifi.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <freertos/task.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include "esp_partition.h"
#include "message_structs.h"
#define BROADCAST_INTERVAL_MS 500
#define CLIENT_TIMEOUT_MS 5000 // 5 Sekunden Timeout
#define CHECK_INTERVAL_MS 1000 // Jede Sekunde überprüfen
extern uint8_t broadcast_address[ESP_NOW_ETH_ALEN];
#define IS_BROADCAST_ADDR(addr) (memcmp(addr, broadcast_address, ESP_NOW_ETH_ALEN) == 0)
#define MESSAGE_QUEUE_SIZE 10
typedef union __attribute__((packed)) {
OTA_PREPARE_FOR_UPDATE_Payload ota_prepare_for_update_payload;
OTA_PREPARE_ACKNOWLEDGED_Payload ota_prepare_acknowledged_payload;
OTA_READY_TO_RECEIVE_Payload ota_ready_to_receive_payload;
OTA_CHUNK_Payload ota_chunk_payload;
OTA_REQUEST_BLOCK_STATUS_Payload ota_request_block_status_payload;
OTA_BLOCK_STATUS_REPORT_Payload ota_block_status_report_payload;
OTA_COMMIT_BLOCK_Payload ota_commit_block_payload;
OTA_BLOCK_COMMITTED_Payload ota_block_committed_payload;
OTA_FINISH_UPDATE_Payload ota_finish_update_payload;
OTA_UPDATE_STATUS_Payload ota_update_status_payload;
StatusPayload status_payload;
ConfigPayload config_payload;
PingPayload ping_payload;
BroadCastPayload broadcast_payload;
RegisterPayload register_payload;
FirmwarePrepPayload firmware_prep_payload;
FirmwarePayload firmware_payload;
} PayloadUnion;
typedef struct __attribute__((packed)) {
uint16_t version; // protcol version
CommandPages commandPage;
uint16_t length;
PayloadUnion payload;
} BaseMessage;
static_assert(sizeof(BaseMessage) <= 255,
"BaseMessage darf nicht größer als 255 sein");
typedef void (*ESP_RegisterFunctionCallback)(
const esp_now_recv_info_t *esp_now_info, const uint8_t *data, int data_len);
typedef void (*ESP_RegisterTaskCallback)(
const esp_now_recv_info_t *esp_now_info, const uint8_t *data, int data_len);
struct ESP_RegisterdFunction {
CommandPages MSGID;
ESP_RegisterFunctionCallback callback;
};
struct ESP_RegisterdTask {
CommandPages MSGID;
ESP_RegisterTaskCallback task;
};
struct ESP_MessageBroker {
struct ESP_RegisterdFunction FunctionList[64];
uint8_t num_direct_callbacks;
struct ESP_RegisterdTask TaskList[64];
uint8_t num_task_callbacks;
};
typedef struct {
QueueHandle_t message_queue;
} ESP_MessageBrokerTaskParams_t;
typedef struct {
esp_now_recv_info_t esp_now_info;
uint8_t *data;
int data_len;
} ESPNOW_MessageInfo;
void ESP_InitMessageBroker(QueueHandle_t msg_queue_handle);
void ESP_RegisterFunction(CommandPages command,
ESP_RegisterFunctionCallback callback);
void ESP_RegisterTask(CommandPages command, ESP_RegisterTaskCallback callback);
void ESP_MessageBrokerTask(void *param);
void ESPNOW_RegisterMasterCallbacks();
void ESPNOW_RegisterSlaveCallbacks();
void ESPNOW_RegisterOTAMaster();
void ESPNOW_RegisterOTASlave();
int init_com(ClientList *clients, uint8_t wifi_channel);
int getNextFreeClientId();
int add_peer(uint8_t *macAddr);
BaseMessage MessageBuilder(CommandPages commandPage, PayloadUnion payload,
size_t payload_size);
void master_broadcast_task(void *param);
void master_ping_task(void *param);
void master_broadcast_ping(void *param);
void master_receive_callback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len);
void client_receive_callback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len);
void client_data_sending_task(void *param);
void client_send_random_data_task(void *param);
void client_monitor_task(void *pvParameters);
void send_ota_block_chunks(uint8_t client_id, uint16_t block_id);
#endif
View File
View File
+278
View File
@@ -0,0 +1,278 @@
#include "i2c.h"
#include "bma4.h"
#include "bma456h.h"
#include "bma4_defs.h"
#include "driver/gpio.h"
#include "driver/i2c_master.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/idf_additions.h"
#include "hal/gpio_types.h"
#include "ota_update.h"
#include <rom/ets_sys.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
static i2c_master_bus_handle_t bus_handle;
static i2c_master_dev_handle_t bma456_dev_handle;
static struct bma4_dev bma456_struct;
volatile uint8_t interrupt_status = 0;
uint8_t int_line;
struct bma4_int_pin_config pin_config = {0};
uint16_t int_status = 0;
#define BMA4_READ_WRITE_LEN UINT8_C(46)
#define BMA456W_INT_PIN 7
static void interrupt_callback(void *) {
interrupt_status = 1;
// ESP_LOGI("INTERRUPT", "STEP DETECTED");
}
/******************************************************************************/
/*! User interface functions */
/*!
* I2C read function map to ESP platform
*/
BMA4_INTF_RET_TYPE bma4_i2c_read(uint8_t reg_addr, uint8_t *reg_data,
uint32_t len, void *intf_ptr) {
// ESP_ERROR_CHECK(i2c_master_receive(bma456_dev_handle, reg_data, len, -1));
esp_err_t err = i2c_master_transmit_receive(bma456_dev_handle, &reg_addr, 1,
reg_data, len, -1);
return (err == ESP_OK) ? BMA4_OK : BMA4_E_COM_FAIL;
// return BMA4_OK;
}
/*!
* I2C write function map to ESP platform
*/
BMA4_INTF_RET_TYPE bma4_i2c_write(uint8_t reg_addr, const uint8_t *reg_data,
uint32_t len, void *intf_ptr) {
// ESP_ERROR_CHECK(i2c_master_transmit(bma456_dev_handle, reg_data, len, -1));
// Bei Bosch muss zuerst das Register, dann die Daten in einem Transfer
// gesendet werden
uint8_t *buffer = malloc(len + 1);
if (!buffer)
return BMA4_E_NULL_PTR;
buffer[0] = reg_addr;
ESP_LOGI("I2CWrite", "Message Length: %d", len);
memcpy(&buffer[1], reg_data, len);
esp_err_t err = i2c_master_transmit(bma456_dev_handle, buffer, len + 1, -1);
free(buffer);
return (err == ESP_OK) ? BMA4_OK : BMA4_E_COM_FAIL;
// return BMA4_OK;
}
/*!
* Delay function map to ESP platform
*/
void bma4_delay_us(uint32_t period, void *intf_ptr) {
uint32_t wait_ms = period / 1000;
uint32_t wait_us = period % 1000;
if (wait_ms) {
vTaskDelay(pdMS_TO_TICKS(wait_ms));
}
ets_delay_us(wait_us);
}
/*!
* @brief Prints the execution status of the APIs.
*/
void bma4_error_codes_print_result(const char api_name[], int8_t rslt) {
if (rslt != BMA4_OK) {
ESP_LOGI("BMA4_I2C", "%s\t", api_name);
if (rslt == BMA4_E_NULL_PTR) {
ESP_LOGI("BMA4_I2C", "Error [%d] : Null pointer\r\n", rslt);
} else if (rslt == BMA4_E_COM_FAIL) {
ESP_LOGI("BMA4_I2C", "Error [%d] : Communication failure\r\n", rslt);
} else if (rslt == BMA4_E_CONFIG_STREAM_ERROR) {
ESP_LOGI("BMA4_I2C", "Error [%d] : Invalid configuration stream\r\n",
rslt);
} else if (rslt == BMA4_E_SELF_TEST_FAIL) {
ESP_LOGI("BMA4_I2C", "Error [%d] : Self test failed\r\n", rslt);
} else if (rslt == BMA4_E_INVALID_SENSOR) {
ESP_LOGI("BMA4_I2C", "Error [%d] : Device not found\r\n", rslt);
} else if (rslt == BMA4_E_OUT_OF_RANGE) {
ESP_LOGI("BMA4_I2C", "Error [%d] : Out of Range\r\n", rslt);
} else if (rslt == BMA4_E_AVG_MODE_INVALID_CONF) {
ESP_LOGI("BMA4_I2C",
"Error [%d] : Invalid bandwidth and ODR combination in Accel "
"Averaging mode\r\n",
rslt);
} else {
/* For more error codes refer "*_defs.h" */
ESP_LOGI("BMA4_I2C", "Error [%d] : Unknown error code\r\n", rslt);
}
}
}
void init_i2c() {
i2c_master_bus_config_t i2c_mst_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_PORT,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_io_num = I2C_MASTER_SDA_IO,
.glitch_ignore_cnt = 7,
//.flags.enable_internal_pullup = true,
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_mst_config, &bus_handle));
}
void read_sensor_task(void *params) {
int8_t ret;
struct bma4_accel sens_data = {0};
while (1) {
ret = bma4_read_accel_xyz(&sens_data, &bma456_struct);
bma4_error_codes_print_result("bma4_read_accel_xyz", ret);
ESP_LOGI("ACC", "X: %d, Y: %d, Z: %d", sens_data.x, sens_data.y,
sens_data.z);
if (interrupt_status) {
ESP_LOGI("INTERRUPT", "Da war der Interrupt resetting");
interrupt_status = 0;
ret = bma456h_read_int_status(&int_status, &bma456_struct);
bma4_error_codes_print_result("bma456w_step_counter_output status", ret);
int8_t rslt;
struct bma456h_out_state tap_out = {0};
rslt = bma456h_output_state(&tap_out, &bma456_struct);
if (BMA4_OK == rslt) {
/* Enters only if the obtained interrupt is single-tap */
if (tap_out.single_tap) {
ESP_LOGI("INTERRUPT", "Single Tap interrupt occurred\n");
}
/* Enters only if the obtained interrupt is double-tap */
else if (tap_out.double_tap) {
ESP_LOGI("INTERRUPT", "Double Tap interrupt occurred\n");
}
/* Enters only if the obtained interrupt is triple-tap */
else if (tap_out.triple_tap) {
ESP_LOGI("INTERRUPT", "Triple Tap interrupt occurred\n");
}
}
}
// ESP_LOGI("i2c", "X:%d, Y%d, Z%d", sens_data.x, sens_data.y, sens_data.z);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void init_bma456() {
i2c_device_config_t dev_cfg_bma456 = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = BMA456_ADDRESS,
.scl_speed_hz = 100000,
};
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_cfg_bma456,
&bma456_dev_handle));
bma456_struct.intf = BMA4_I2C_INTF;
bma456_struct.bus_read = bma4_i2c_read;
bma456_struct.bus_write = bma4_i2c_write;
bma456_struct.delay_us = bma4_delay_us;
bma456_struct.read_write_len = BMA4_READ_WRITE_LEN;
bma456_struct.intf_ptr = &bma456_dev_handle;
int8_t ret;
bma456_struct.chip_id = 0;
ret = bma456h_init(&bma456_struct);
bma4_error_codes_print_result("I2C Init", ret);
ESP_LOGI("I2C", "Chip Init ausgelesene CHIP ID %d", bma456_struct.chip_id);
ret = bma4_soft_reset(&bma456_struct);
bma4_error_codes_print_result("bma4_soft_reset", ret);
vTaskDelay(pdMS_TO_TICKS(20)); // Wartezeit nach Reset
ret = bma4_set_advance_power_save(BMA4_DISABLE, &bma456_struct);
bma4_error_codes_print_result("bma4_set_advance_power_save", ret);
vTaskDelay(pdMS_TO_TICKS(10));
ESP_LOGI("I2C", "Config SIZE %d", bma456_struct.config_size);
ESP_LOGI("I2C", "Config Pointer %p", bma456_struct.config_file_ptr);
ESP_LOGI("I2C", "Starte Config-File Upload...");
ret = bma456h_write_config_file(&bma456_struct);
bma4_error_codes_print_result("bma4_write_config_file", ret);
struct bma4_accel_config accel_config;
bma4_get_accel_config(&accel_config, &bma456_struct);
accel_config.range = BMA4_ACCEL_RANGE_2G;
ret = bma4_set_accel_config(&accel_config, &bma456_struct);
bma4_error_codes_print_result("bma4_set_accel_config status", ret);
/* Enable the accelerometer */
ret = bma4_set_accel_enable(BMA4_ENABLE, &bma456_struct);
bma4_error_codes_print_result("bma4_set_accel_enable status", ret);
struct bma456h_multitap_settings tap_settings = {0};
ret = bma456h_tap_get_parameter(&tap_settings, &bma456_struct);
bma4_error_codes_print_result("bma456h_tap_get_parameter status", ret);
tap_settings.tap_sens_thres = 0;
ret = bma456h_tap_set_parameter(&tap_settings, &bma456_struct);
bma4_error_codes_print_result("bma456h_tap_set_parameter status", ret);
ret = bma456h_feature_enable(
(BMA456H_SINGLE_TAP_EN | BMA456H_DOUBLE_TAP_EN | BMA456H_TRIPLE_TAP_EN),
BMA4_ENABLE, &bma456_struct);
bma4_error_codes_print_result("bma456w_feature_enable status", ret);
/* Setting watermark level 1, the output step resolution is 20 steps.
* Eg: 1 means, 1 * 20 = 20. Every 20 steps once output triggers
*/
ret = bma456h_step_counter_set_watermark(1, &bma456_struct);
bma4_error_codes_print_result("bma456w_step_counter_set_watermark status",
ret);
/* Hardware interrupt configuration */
int_line = BMA4_INTR2_MAP;
ret = bma4_get_int_pin_config(&pin_config, int_line, &bma456_struct);
bma4_error_codes_print_result("bma4_get_int_pin_config status", ret);
ret = bma456h_map_interrupt(int_line, BMA456H_TAP_OUT_INT, BMA4_ENABLE,
&bma456_struct);
bma4_error_codes_print_result("bma456w_map_interrupt status", ret);
pin_config.edge_ctrl = BMA4_EDGE_TRIGGER;
pin_config.output_en = BMA4_OUTPUT_ENABLE;
pin_config.lvl = BMA4_ACTIVE_HIGH;
pin_config.od = BMA4_PUSH_PULL;
pin_config.input_en = BMA4_INPUT_DISABLE;
ret = bma4_set_int_pin_config(&pin_config, int_line, &bma456_struct);
bma4_error_codes_print_result("bma4_set_int_pin_config status", ret);
gpio_reset_pin(BMA456W_INT_PIN);
gpio_set_direction(BMA456W_INT_PIN, GPIO_MODE_INPUT);
gpio_set_pull_mode(BMA456W_INT_PIN, GPIO_PULLDOWN_ONLY);
gpio_set_intr_type(BMA456W_INT_PIN, GPIO_INTR_POSEDGE);
gpio_intr_enable(BMA456W_INT_PIN);
gpio_install_isr_service(0);
gpio_isr_handler_add(BMA456W_INT_PIN, interrupt_callback,
(void *)BMA456W_INT_PIN);
xTaskCreate(read_sensor_task, "READ_SENSOR", 4096, NULL, 1, NULL);
}
void init_i2c_with_all_devices() {
init_i2c();
init_bma456();
}
+8
View File
@@ -0,0 +1,8 @@
#define I2C_PORT 0
#define I2C_MASTER_SCL_IO 5
#define I2C_MASTER_SDA_IO 6
#define BMA456_ADDRESS 0x18
void init_i2c();
void init_bma456();
void init_i2c_with_all_devices();
+297 -278
View File
@@ -1,317 +1,274 @@
#include "client_handler.h"
#include "driver/gpio.h" #include "driver/gpio.h"
#include "driver/uart.h" #include "driver/uart.h"
#include "esp_app_desc.h"
#include "esp_app_format.h"
#include "esp_err.h"
#include "esp_flash_partitions.h"
#include "esp_image_format.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_log_buffer.h"
#include "esp_ota_ops.h"
#include "esp_partition.h"
#include "esp_phy_init.h" #include "esp_phy_init.h"
#include "esp_rom_gpio.h" #include "esp_rom_gpio.h"
#include "esp_timer.h" #include "esp_timer.h"
#include "esp_wifi.h" #include "esp_wifi.h"
#include "freertos/idf_additions.h"
#include "hal/uart_types.h" #include "hal/uart_types.h"
#include "message_handler.h"
#include "message_parser.h"
#include "nvs.h"
#include "nvs_flash.h" #include "nvs_flash.h"
#include "communication_handler.h"
#include "main.h" #include "main.h"
// return any inactive client field for new usage #include "ota_update.h"
int getNextFreeClientId() { #include "uart_handler.h"
for (int i = 0; i < numClients; i++) { #include <math.h>
if (!clients[i].isAvailable) { #include <stdbool.h>
return i; #include <stddef.h>
} #include <stdint.h>
} #include <stdlib.h>
return 0; #include <string.h>
#include <sys/types.h>
#include "message_builder.h"
#include "uart_msg_ids.h"
#include "i2c.h"
static const char *TAG = "ALOX - MAIN";
static const uint16_t version = 0x0001;
static uint8_t send_message_buffer[1024];
static uint8_t send_message_payload_buffer[512];
uint32_t g_uart_firmware_total_size = 0;
static MessageBrokerTaskParams_t broker_task_params;
static MasterOTA_TaskParams_t master_ota_task_params;
static ESP_MessageBrokerTaskParams_t esp_broker_task_params;
ClientList clientList = {.Clients = {{0}}, .ClientCount = 0};
size_t build_ClientInfoPart(uint8_t clientid, float_t lagex, float_t lagey,
int32_t bitmask, uint8_t *outputArray,
size_t outputArrayOffset, size_t outputArraySize) {
size_t offset = outputArrayOffset;
memcpy(&outputArray[offset], &clientid, sizeof(clientid));
offset += sizeof(clientid);
// lagex (typischerweise 4 Bytes)
memcpy(&outputArray[offset], &lagex, sizeof(lagex));
offset += sizeof(lagex);
// lagey (typischerweise 4 Bytes)
memcpy(&outputArray[offset], &lagey, sizeof(lagey));
offset += sizeof(lagey);
// bitmask (4 Bytes)
memcpy(&outputArray[offset], &bitmask, sizeof(bitmask));
offset += sizeof(bitmask);
return offset - outputArrayOffset;
} }
void add_peer(uint8_t *macAddr) { void fakeDataCallback(uint8_t msgid, const uint8_t *payload, size_t payload_len,
esp_now_peer_info_t peerInfo = { uint8_t *send_payload_buffer,
.channel = size_t send_payload_buffer_size, uint8_t *send_buffer,
0, // Standardkanal, sollte mit den anderen Geräten übereinstimmen size_t send_buffer_size) {
.ifidx = ESP_IF_WIFI_STA,
.encrypt = false, // Keine Verschlüsselung (kann geändert werden)
};
memcpy(peerInfo.peer_addr, macAddr, ESP_NOW_ETH_ALEN);
esp_err_t result = esp_now_add_peer(&peerInfo); uint8_t seed = payload[1];
if (result == ESP_OK) { ESP_LOGI(TAG, "Sending Fake Client Infos with seed %d", seed);
ESP_LOGI(TAG, "Peer added: %02X:%02X:%02X:%02X:%02X:%02X", macAddr[0], srand(seed);
macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);
if (!IS_BROADCAST_ADDR(macAddr)) { size_t offset = 1;
send_payload_buffer[0] = 3; // Client Count
offset +=
build_ClientInfoPart(1, rand() * 1.0, rand() * 1.0, rand() * 1,
send_payload_buffer, 1, send_payload_buffer_size);
offset += build_ClientInfoPart(2, rand() * 2.0, rand() * 2.0, rand() * 2,
send_payload_buffer, offset,
send_payload_buffer_size);
offset += build_ClientInfoPart(3, rand() * 3.0, rand() * 3.0, rand() * 3,
send_payload_buffer, offset,
send_payload_buffer_size);
if (numClients >= MAX_CLIENTS) { int len = build_message(UART_CLIENT_INPUT, send_payload_buffer, offset,
ESP_LOGW(TAG, "Cannot add more clients, maximum reached."); send_buffer, send_buffer_size);
if (len < 0) {
ESP_LOGE(TAG,
"Error Building UART Message: payload_len, %d, sendbuffer_size: "
"%d, mes_len(error): %d",
payload_len, send_buffer_size, len);
return; return;
} }
ClientInfo newClient = {}; uart_write_bytes(MASTER_UART, send_buffer, len);
memcpy(newClient.macAddr, macAddr, ESP_NOW_ETH_ALEN);
newClient.isAvailable = true;
newClient.lastSuccessfullPing = xTaskGetTickCount();
clients[getNextFreeClientId()] = newClient;
ESP_LOGI(TAG, "New client added.");
} }
} else if (result == ESP_ERR_ESPNOW_EXIST) {
ESP_LOGW(TAG, "Peer already exists."); void echoCallback(uint8_t msgid, const uint8_t *payload, size_t payload_len,
// Überprüfen, ob der Client bereits existiert uint8_t *send_payload_buffer, size_t send_payload_buffer_size,
for (int i = 0; i < numClients; i++) { uint8_t *send_buffer, size_t send_buffer_size) {
if (memcmp(clients[i].macAddr, macAddr, ESP_NOW_ETH_ALEN) == 0) { ESP_LOGI(TAG, "Echo command 0x01...");
ESP_LOGI(TAG, "Client found again, welcome back!"); int len = build_message(UART_ECHO, payload, payload_len, send_buffer,
clients[i].isAvailable = true; // Reaktiviere den Client send_buffer_size);
break; if (len < 0) {
ESP_LOGE(TAG,
"Error Building UART Message: payload_len, %d, sendbuffer_size: "
"%d, mes_len(error): %d",
payload_len, send_buffer_size, len);
return;
} }
uart_write_bytes(MASTER_UART, send_buffer, len);
} }
} else {
ESP_LOGE(TAG, "Failed to add peer: %s", esp_err_to_name(result)); void versionCallback(uint8_t msgid, const uint8_t *payload, size_t payload_len,
uint8_t *send_payload_buffer,
size_t send_payload_buffer_size, uint8_t *send_buffer,
size_t send_buffer_size) {
ESP_LOGI(TAG, "Version command 0x02...");
size_t git_build_hash_len = strlen(BUILD_GIT_HASH);
size_t needed_buffer_size = 2 + git_build_hash_len;
if (send_payload_buffer_size < needed_buffer_size) {
ESP_LOGE(TAG, "send_payload_buffer to small size %d need %d",
send_payload_buffer_size, needed_buffer_size);
return;
}
send_payload_buffer[0] = (uint8_t)(version & 0xFF);
send_payload_buffer[1] = (uint8_t)((version >> 8) & 0xFF);
memcpy(&send_payload_buffer[2], &BUILD_GIT_HASH, git_build_hash_len);
int len = build_message(UART_VERSION, send_payload_buffer, needed_buffer_size,
send_buffer, send_buffer_size);
if (len < 0) {
ESP_LOGE(TAG,
"Error Building UART Message: payload_len, %d, sendbuffer_size: "
"%d, mes_len(error): %d",
payload_len, send_buffer_size, len);
return;
}
uart_write_bytes(MASTER_UART, send_buffer, len);
}
void clientInfoCallback(uint8_t msgid, const uint8_t *payload,
size_t payload_len, uint8_t *send_payload_buffer,
size_t send_payload_buffer_size, uint8_t *send_buffer,
size_t send_buffer_size) {
ESP_LOGI(TAG, "Client Info Command 0x03...");
static uint8_t entryLength = 19;
uint8_t needed_buffer_size = 1 + entryLength * clientList.ClientCount;
if (send_payload_buffer_size < needed_buffer_size) {
ESP_LOGE(TAG, "send_payload_buffer to small size %d need %d",
send_payload_buffer_size, needed_buffer_size);
return;
}
send_payload_buffer[0] = clientList.ClientCount;
uint8_t offsetMult = 0;
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clientList.Clients[i].slotIsUsed) {
size_t offset = 1 + (entryLength * offsetMult++);
send_payload_buffer[offset] = i;
send_payload_buffer[offset + 1] = clientList.Clients[i].isAvailable;
send_payload_buffer[offset + 2] = clientList.Clients[i].slotIsUsed;
memcpy(&send_payload_buffer[offset + 3], clientList.Clients[i].macAddr,
MAC_LENGTH);
memcpy(&send_payload_buffer[offset + 9], &clientList.Clients[i].lastPing,
4);
memcpy(&send_payload_buffer[offset + 13],
&clientList.Clients[i].lastSuccessfullPing, 4);
memcpy(&send_payload_buffer[offset + 17],
&clientList.Clients[i].clientVersion, 2);
} }
} }
// UNSAFE ACCROSS THREADS BUT EZ TO USE int len = build_message(UART_CLIENT_INFO, send_payload_buffer,
const char *MACtoString(uint8_t *macAddr) { needed_buffer_size, send_buffer, send_buffer_size);
static char output[18]; // 17 Zeichen + 1 für Nullterminierung
sprintf(output, "%02X:%02X:%02X:%02X:%02X:%02X", macAddr[0], macAddr[1], if (len < 0) {
macAddr[2], macAddr[3], macAddr[4], macAddr[5]); ESP_LOGE(TAG,
return output; "Error Building UART Message: payload_len, %d, sendbuffer_size: "
"%d, mes_len(error): %d",
needed_buffer_size, send_buffer_size, len);
return;
}
uart_write_bytes(MASTER_UART, send_buffer, len);
} }
BaseMessage MessageBuilder(CommandPages commandPage, PayloadUnion payload, bool g_ota_in_progress = false;
size_t payload_size) {
BaseMessage message;
// Initialisierung der BaseMessage void ota_monitor_task(void *param) {
message.commandPage = commandPage; const TickType_t timeout = pdMS_TO_TICKS(10000); // 10 seconds
message.version = 1;
message.length = (uint16_t)payload_size;
// Kopieren des Payloads in die Union while (g_ota_in_progress) {
memset(&message.payload, 0, sizeof(message.payload)); // Sicherheitsmaßnahme bool all_done = true;
memcpy(&message.payload, &payload, payload_size); for (int i = 0; i < MAX_CLIENTS; i++) {
if (clientList.Clients[i].slotIsUsed) {
if (clientList.Clients[i].ota_status != OTA_SUCCESS &&
clientList.Clients[i].ota_status != OTA_FAILED) {
all_done = false;
return message; if ((xTaskGetTickCount() - clientList.Clients[i].last_seen) >
timeout) {
ESP_LOGE(TAG, "Client %d timed out", i);
clientList.Clients[i].ota_status = OTA_FAILED;
}
} }
void master_broadcast_task(void *param) {
while (1) {
BroadCastPayload payload = {};
BaseMessage message = MessageBuilder(
BroadCastPage, *(PayloadUnion *)&payload, sizeof(payload));
ESP_ERROR_CHECK(esp_now_send(broadcast_address, (uint8_t *)&message,
sizeof(BaseMessage)));
ESP_LOGI(TAG, "Broadcast Message sent");
vTaskDelay(pdMS_TO_TICKS(5000));
} }
} }
void master_ping_task(void *param) { if (all_done) {
while (1) { g_ota_in_progress = false;
for (size_t i = 0; i < MAX_CLIENTS; ++i) {
if (clients[i].isAvailable) {
PingPayload payload = {};
payload.timestamp = esp_timer_get_time();
BaseMessage message = MessageBuilder(
PingPage, *(PayloadUnion *)&payload, sizeof(payload));
esp_now_send(clients[i].macAddr, (uint8_t *)&message,
sizeof(BaseMessage));
ESP_LOGI(TAG, "SENDING PING!!!!");
}
} }
vTaskDelay(pdMS_TO_TICKS(1000)); vTaskDelay(pdMS_TO_TICKS(1000));
} }
}
void master_receive_callback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len) {
ESP_LOGI(TAG, "MASTER GOT MESSAGE");
ESP_LOGI(TAG, "Message: %.*s", data_len, data);
const BaseMessage *message = (const BaseMessage *)data;
switch (message->commandPage) {
case StatusPage:
ESP_LOGI(TAG, "GOT STATUS MESSAGE");
break;
case PingPage:
ESP_LOGI(TAG, "GOT PING MESSAGE");
uint32_t currentTime = esp_timer_get_time();
uint32_t diff = currentTime - message->payload.ping_payload.timestamp;
ESP_LOGI(TAG, "Start: %lu, End: %lu, Diff: %lu, Ping: %lu",
message->payload.ping_payload.timestamp, currentTime, diff,
diff / 1000); // ping in ms
ESP_LOGI(TAG, "OTA Operation Finished.");
for (int i = 0; i < MAX_CLIENTS; i++) { for (int i = 0; i < MAX_CLIENTS; i++) {
// Überprüfen, ob der Client existiert und die MAC-Adresse übereinstimmt if (clientList.Clients[i].slotIsUsed) {
if (clients[i].isAvailable && ESP_LOGI(TAG, "Client %d [MAC: " MACSTR "]: %s, Resent Chunks: %d", i,
memcmp(clients[i].macAddr, esp_now_info->src_addr, MAC2STR(clientList.Clients[i].macAddr),
ESP_NOW_ETH_ALEN) == 0) { clientList.Clients[i].ota_status == OTA_SUCCESS ? "SUCCESS"
clients[i].lastSuccessfullPing = xTaskGetTickCount(); : "FAILED",
ESP_LOGI(TAG, "Updated client %d last ping time to %lu", i, clientList.Clients[i].resent_chunks_counter);
clients[i].lastSuccessfullPing);
break;
}
}
break;
case BroadCastPage:
ESP_LOGI(TAG, "MASTER SHOULD NOT GET BROADCAST MESSAGE, is there another "
"master calling?");
break;
case RegisterPage:
ESP_LOGI(TAG, "WILL REGISTER DEVICE");
esp_now_peer_info_t checkPeerInfo;
esp_err_t checkPeer =
esp_now_get_peer(esp_now_info->src_addr, &checkPeerInfo);
switch (checkPeer) {
case (ESP_OK):
ESP_LOGI(TAG, "CLIENT BEKANNT");
for (int i = 0; i < MAX_CLIENTS; i++) {
// client in liste wiederfinden
if (!clients[i].isAvailable &&
memcmp(clients[i].macAddr, esp_now_info->src_addr,
ESP_NOW_ETH_ALEN) == 0) {
clients[i].isAvailable = true;
clients[i].lastSuccessfullPing = xTaskGetTickCount();
ESP_LOGI(TAG, "Updated client %d last ping time to %lu", i,
clients[i].lastSuccessfullPing);
break;
}
}
break;
case (ESP_ERR_ESPNOW_NOT_INIT):
ESP_LOGI(TAG, "Not initalised");
break;
case (ESP_ERR_ESPNOW_ARG):
ESP_LOGI(TAG, "ESP ERR ESPNOW_ARG");
break;
case (ESP_ERR_ESPNOW_NOT_FOUND):
ESP_LOGI(TAG, "CLIENT WIRD IN DIE LISTE AUFGENOMMEN");
add_peer(esp_now_info->src_addr);
break;
default:
ESP_LOGI(TAG, "Unknown Message %i", checkPeer);
}
break;
default:
break;
} }
} }
void client_receive_callback(const esp_now_recv_info_t *esp_now_info, vTaskDelete(NULL);
const uint8_t *data, int data_len) {
ESP_LOGI(TAG, "SLAVE GOT MESSAGE");
ESP_LOGI(TAG, "Received message from: %02X:%02X:%02X:%02X:%02X:%02X",
esp_now_info->src_addr[0], esp_now_info->src_addr[1],
esp_now_info->src_addr[2], esp_now_info->src_addr[3],
esp_now_info->src_addr[4], esp_now_info->src_addr[5]);
ESP_LOGI(TAG, "Message: %.*s", data_len, data);
BaseMessage replyMessage = {};
const BaseMessage *message = (const BaseMessage *)data;
switch (message->commandPage) {
case StatusPage:
ESP_LOGI(TAG, "GOT STATUS MESSAGE");
break;
case PingPage:
ESP_LOGI(TAG, "GOT PING MESSAGE");
replyMessage = MessageBuilder(PingPage, *(PayloadUnion *)&message->payload,
sizeof(message->payload));
ESP_ERROR_CHECK(esp_now_send(
esp_now_info->src_addr, (uint8_t *)&replyMessage, sizeof(BaseMessage)));
break;
case BroadCastPage:
ESP_LOGI(TAG, "GOT BROADCAST MESSAGE");
if (!hasMaster) {
if (IS_BROADCAST_ADDR(esp_now_info->des_addr)) {
ESP_LOGI(TAG,
"GOT BROADCAST MESSAGE ATTEMPTING TO REGISTER TO MASTER!");
add_peer(esp_now_info->src_addr);
replyMessage =
MessageBuilder(RegisterPage, *(PayloadUnion *)&message->payload,
sizeof(message->payload));
ESP_ERROR_CHECK(esp_now_send(esp_now_info->src_addr,
(uint8_t *)&replyMessage,
sizeof(BaseMessage)));
hasMaster = true;
}
}
break;
case RegisterPage:
break;
default:
ESP_LOGI(TAG, "GOT UNKONW MESSAGE");
break;
}
} }
void client_data_sending_task(void *param) { void send_client_ota_start_message(uint8_t clientID, uint32_t app_size) {
while (1) { BaseMessage message = {};
const char *dataToSend = "DATA:42"; OTA_PREPARE_FOR_UPDATE_Payload ota_payload = {
ESP_LOGI(TAG, "SEND DATA"); .total_size = app_size,
esp_now_send(NULL, (uint8_t *)dataToSend, };
strlen(dataToSend)); // Sende Daten an Master message = MessageBuilder(OTA_PREPARE_FOR_UPDATE,
vTaskDelay(pdMS_TO_TICKS(5000)); *(PayloadUnion *)&ota_payload, sizeof(ota_payload));
}
}
void client_monitor_task(void *pvParameters) { esp_err_t err = esp_now_send(clientList.Clients[clientID].macAddr,
TickType_t timeout_ticks = (uint8_t *)&message, sizeof(BaseMessage));
pdMS_TO_TICKS(CLIENT_TIMEOUT_MS); // Timeout in Ticks if (err != ESP_OK) {
TickType_t interval_ticks = ESP_LOGE(TAG, "Could not send OTA PREPARE FOR UPDATE to " MACSTR ", %s",
pdMS_TO_TICKS(CHECK_INTERVAL_MS); // Prüfintervall in Ticks MAC2STR(clientList.Clients[clientID].macAddr),
esp_err_to_name(err));
while (1) { } else {
TickType_t now = xTaskGetTickCount(); // Aktuelle Zeit in Ticks ESP_LOGI(TAG, "Sent OTA PREPARE FOR UPDATE to " MACSTR,
MAC2STR(clientList.Clients[clientID].macAddr));
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].isAvailable) {
TickType_t time_diff = now - clients[i].lastSuccessfullPing;
// Prüfen, ob der Client als "nicht verfügbar" markiert werden soll
if (time_diff > timeout_ticks) {
clients[i].isAvailable = false;
ESP_LOGW(
TAG,
"Client %d (MAC: %02X:%02X:%02X:%02X:%02X:%02X) is unavailable",
i, clients[i].macAddr[0], clients[i].macAddr[1],
clients[i].macAddr[2], clients[i].macAddr[3],
clients[i].macAddr[4], clients[i].macAddr[5]);
} }
} }
}
// Task für das Prüfintervall anhalten
vTaskDelay(interval_ticks);
}
}
void uart_read_task(void *param) {
uint8_t *data = (uint8_t *)malloc(BUF_SIZE);
int len = 0;
while (1) {
len = 0;
len =
uart_read_bytes(MASTER_UART, data, BUF_SIZE, (20 / portTICK_PERIOD_MS));
if (len > 0) {
data[len] = '\0';
ESP_LOGI(TAG, "GOT UART DATA %s", data);
uart_write_bytes(MASTER_UART, data, len);
}
}
}
void init_uart() {
uart_config_t uart_config = {.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE};
uart_driver_install(MASTER_UART, BUF_SIZE * 2, 0, 0, NULL, 0);
uart_param_config(MASTER_UART, &uart_config);
uart_set_pin(MASTER_UART, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE,
UART_PIN_NO_CHANGE);
xTaskCreate(uart_read_task, "Read Uart", 4096, NULL, 1, NULL);
}
void app_main(void) { void app_main(void) {
ESP_LOGI(TAG, "Starting Alox Powerpod Version %d Build: %s", version,
BUILD_GIT_HASH);
esp_err_t ret = nvs_flash_init(); esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
@@ -320,12 +277,10 @@ void app_main(void) {
} }
ESP_ERROR_CHECK(ret); ESP_ERROR_CHECK(ret);
// GPIO-Pin für Moduserkennung
gpio_reset_pin(MASTER_MODE_PIN); gpio_reset_pin(MASTER_MODE_PIN);
gpio_set_direction(MASTER_MODE_PIN, GPIO_MODE_INPUT); gpio_set_direction(MASTER_MODE_PIN, GPIO_MODE_INPUT);
bool isMaster = (gpio_get_level(MASTER_MODE_PIN) == 0); bool isMaster = (gpio_get_level(MASTER_MODE_PIN) == 0);
// ESP-NOW Initialisieren
ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default()); ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
@@ -333,12 +288,11 @@ void app_main(void) {
wifi_config_t wifi_config = { wifi_config_t wifi_config = {
.sta = .sta =
{ {
.channel = 1, // Kanal 1, stelle sicher, dass alle Geräte .channel = 1,
// denselben Kanal verwenden
}, },
}; };
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start()); ESP_ERROR_CHECK(esp_wifi_start());
ESP_ERROR_CHECK(esp_now_init()); ESP_ERROR_CHECK(esp_now_init());
@@ -348,20 +302,85 @@ void app_main(void) {
ESP_ERROR_CHECK(esp_now_register_recv_cb(client_receive_callback)); ESP_ERROR_CHECK(esp_now_register_recv_cb(client_receive_callback));
} }
// Nachrichtenschlange initialisieren ret = init_com(&clientList, 1);
messageQueue = xQueueCreate(10, sizeof(char *)); if (ret < 0) {
ESP_LOGE(TAG, "Could not Init ESP NOW Communication!");
}
const esp_partition_t *running = esp_ota_get_running_partition();
ESP_LOGI(TAG, "OTA: Running Partition: %s", running->label);
esp_ota_img_states_t ota_state;
if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
bool diagnostic_is_ok = true; // TODO build in valid diagnostics
if (diagnostic_is_ok) {
esp_ota_mark_app_valid_cancel_rollback();
} else {
// esp_ota_mark_app_invalid_rollback(); Put this function at the start
// so when the esp crashes it can rollback
esp_ota_mark_app_invalid_rollback_and_reboot();
}
}
}
const esp_partition_t *next_ota_partition =
esp_ota_get_next_update_partition(NULL);
int app_size = get_app_size(next_ota_partition);
ESP_LOGE(TAG, "App Size in Other Partition %d", app_size);
QueueHandle_t espnow_message_queue =
xQueueCreate(10, sizeof(ESPNOW_MessageInfo));
ESP_InitMessageBroker(espnow_message_queue);
esp_broker_task_params.message_queue = espnow_message_queue;
xTaskCreate(ESP_MessageBrokerTask, "espnow_message_broker_task", 4096,
(void *)&esp_broker_task_params, 4, NULL);
init_ota();
// Tasks starten basierend auf Master/Client
if (isMaster) { if (isMaster) {
ESP_LOGI(TAG, "Started in Mastermode"); ESP_LOGI(TAG, "Started in Mastermode");
ESPNOW_RegisterMasterCallbacks();
ESPNOW_RegisterOTAMaster();
add_peer(broadcast_address); add_peer(broadcast_address);
xTaskCreate(master_broadcast_task, "MasterBroadcast", 4096, NULL, 1, NULL); xTaskCreate(master_broadcast_task, "MasterBroadcast", 4096, NULL, 1, NULL);
xTaskCreate(master_ping_task, "MasterPing", 4096, NULL, 1, NULL); xTaskCreate(master_broadcast_ping, "MasterBroadcastPing", 4096, NULL, 1,
xTaskCreate(client_monitor_task, "MonitorClientTask", 4096, NULL, 1, NULL); NULL);
init_uart();
QueueHandle_t parsed_message_queue =
xQueueCreate(10, sizeof(ParsedMessage_t));
init_uart(parsed_message_queue);
InitMessageBroker();
broker_task_params.message_queue = parsed_message_queue;
broker_task_params.send_buffer = send_message_buffer;
broker_task_params.send_buffer_size = sizeof(send_message_buffer);
broker_task_params.payload_buffer = send_message_payload_buffer;
broker_task_params.payload_buffer_size =
sizeof(send_message_payload_buffer);
xTaskCreate(MessageBrokerTask, "MessageHandler", 4096,
(void *)&broker_task_params, 5, NULL);
master_ota_task_params.client_list = &clientList;
xTaskCreate(MasterOTATask, "MasterOTATask", 4096,
(void *)&master_ota_task_params, 4, NULL);
RegisterCallback(UART_ECHO, echoCallback);
RegisterCallback(UART_VERSION, versionCallback);
RegisterCallback(UART_CLIENT_INFO, clientInfoCallback);
RegisterCallback(UART_CLIENT_INPUT, fakeDataCallback);
RegisterCallback(UART_OTA_START_ESPNOW, start_ota_update_espnow);
RegisterUART_OTAFunctions();
// init_i2c_with_all_devices();
} else { } else {
ESP_LOGI(TAG, "Started in Slavemode"); ESP_LOGI(TAG, "Started in Slavemode");
xTaskCreate(client_data_sending_task, "ClientDataSending", 4096, NULL, 1, ESPNOW_RegisterSlaveCallbacks();
NULL); xTaskCreate(slave_ota_task, "SlaveOTATask", 4096, NULL, 4, NULL);
ESPNOW_RegisterOTASlave();
} }
} }
+5 -72
View File
@@ -14,76 +14,9 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#define MASTER_MODE_PIN GPIO_NUM_23 // Jumper-Erkennungspin #ifdef CONFIG_IDF_TARGET_ESP32S3
#define MASTER_UART UART_NUM_2 #define MASTER_MODE_PIN GPIO_NUM_1 // Jumper-Erkennungspin
#define BROADCAST_INTERVAL_MS 500 #elif CONFIG_IDF_TARGET_ESP32C3
#define MASTER_MODE_PIN GPIO_NUM_0 // Jumper-Erkennungspin
#define BUF_SIZE (1024) #endif
#define TXD_PIN (GPIO_NUM_17)
#define RXD_PIN (GPIO_NUM_16)
#define CLIENT_TIMEOUT_MS 5000 // 5 Sekunden Timeout
#define CHECK_INTERVAL_MS 1000 // Jede Sekunde überprüfen
uint8_t broadcast_address[ESP_NOW_ETH_ALEN] = {0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF};
#define IS_BROADCAST_ADDR(addr) \
(memcmp(addr, broadcast_address, ESP_NOW_ETH_ALEN) == 0)
typedef enum {
BroadCastPage,
StatusPage,
PingPage,
RegisterPage,
} CommandPages;
typedef struct {
uint32_t uptime;
uint8_t status;
} StatusPayload;
typedef struct {
uint32_t timestamp;
} PingPayload;
typedef struct {
} BroadCastPayload;
typedef struct {
bool familierClient;
} RegisterPayload;
typedef union {
StatusPayload status_payload;
PingPayload ping_payload;
BroadCastPayload broadcast_payload;
RegisterPayload register_payload;
} PayloadUnion;
typedef struct {
uint16_t version;
CommandPages commandPage;
uint16_t length;
PayloadUnion payload;
} BaseMessage;
static_assert(sizeof(BaseMessage) <= 255,
"BaseMessage darf nicht größer als 255 sein");
QueueHandle_t messageQueue; // Warteschlange für empfangene Nachrichten
const char *TAG = "ALOX";
typedef struct {
uint8_t macAddr[ESP_NOW_ETH_ALEN];
int rssi;
bool isAvailable;
TickType_t lastSuccessfullPing;
} ClientInfo;
#define MAX_CLIENTS 19
ClientInfo clients[MAX_CLIENTS];
size_t numClients = 0;
size_t activeClients = 0;
bool hasMaster = false;
#endif #endif
+79
View File
@@ -0,0 +1,79 @@
#include "message_builder.h"
#include "esp_log.h"
#include "message_parser.h"
#include <stdbool.h>
#include <stddef.h>
bool needs_stuffing_byte(uint8_t byte) {
return (byte == StartByte || byte == EscapeByte || byte == EndByte);
}
bool add_byte_with_length_check(uint8_t byte, size_t write_index, uint8_t *data,
size_t max_length) {
if (write_index >= max_length) {
return false;
}
data[write_index] = byte;
return true;
}
int build_message(uint8_t msgid, const uint8_t *payload, size_t payload_len,
uint8_t *msg_buffer, size_t msg_buffer_size) {
//ESP_LOGE("BM", "payload_len %d, msg_buffer_size %d", payload_len + 4,
// msg_buffer_size);
if (payload_len + 4 > msg_buffer_size) {
return PayloadBiggerThenBuffer;
}
uint8_t checksum = 0;
size_t write_index = 0;
msg_buffer[write_index++] = StartByte;
if (needs_stuffing_byte(msgid)) {
if (!add_byte_with_length_check(EscapeByte, write_index, msg_buffer,
msg_buffer_size)) {
return BufferOverFlow;
}
write_index++;
}
if (!add_byte_with_length_check(msgid, write_index, msg_buffer,
msg_buffer_size)) {
return BufferOverFlow;
}
write_index++;
checksum ^= msgid;
for (size_t i = 0; i < payload_len; i++) {
if (needs_stuffing_byte(payload[i])) {
if (!add_byte_with_length_check(EscapeByte, write_index, msg_buffer,
msg_buffer_size)) {
return BufferOverFlow;
}
write_index++;
}
if (!add_byte_with_length_check(payload[i], write_index, msg_buffer,
msg_buffer_size)) {
return BufferOverFlow;
}
write_index++;
checksum ^= payload[i];
}
if (needs_stuffing_byte(checksum)) {
if (!add_byte_with_length_check(EscapeByte, write_index, msg_buffer,
msg_buffer_size)) {
return BufferOverFlow;
}
write_index++;
}
if (write_index + 2 > msg_buffer_size) {
return BufferOverFlow;
}
msg_buffer[write_index++] = checksum;
msg_buffer[write_index++] = EndByte;
return write_index;
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef _MESSAGE_BUILDER_HEADER
#define _MESSAGE_BUILDER_HEADER
#include "message_parser.h"
#include <stddef.h>
#include <stdint.h>
enum BuildMessageErrors {
NoBuildError = 0,
PayloadBiggerThenBuffer = -1,
BufferOverFlow = -2,
};
// returns the length of msg_buffer
int build_message(uint8_t msgid, const uint8_t *payload, size_t payload_len,
uint8_t *msg_buffer, size_t msg_buffer_length);
#endif
+74
View File
@@ -0,0 +1,74 @@
#include "message_handler.h"
#include "esp_log.h"
#include "freertos/idf_additions.h"
#include "uart_handler.h"
static struct MessageBroker mr;
static char *TAG = "ALOX - Message Handler";
void InitMessageBroker() {
mr.num_direct_callbacks = 0;
mr.num_task_callbacks = 0;
return;
}
void RegisterCallback(uint8_t msgid, RegisterFunctionCallback callback) {
ESP_LOGI(TAG, "Registerd Uart Callback for % X", msgid);
mr.FunctionList[mr.num_direct_callbacks].MSGID = msgid;
mr.FunctionList[mr.num_direct_callbacks].callback = callback;
mr.num_direct_callbacks++;
return;
}
void RegisterTask(uint8_t msgid, RegisterTaskCallback callback) {
mr.TaskList[mr.num_task_callbacks].MSGID = msgid;
mr.TaskList[mr.num_task_callbacks].task = callback;
mr.num_task_callbacks++;
return;
}
void MessageBrokerTask(void *param) {
ParsedMessage_t received_msg;
MessageBrokerTaskParams_t *task_params = (MessageBrokerTaskParams_t *)param;
// Extrahiere die einzelnen Parameter
QueueHandle_t msg_queue = task_params->message_queue;
uint8_t *send_message_buffer = task_params->send_buffer;
size_t send_message_buffer_size = task_params->send_buffer_size;
uint8_t *send_payload_buffer = task_params->payload_buffer;
size_t send_payload_buffer_size = task_params->payload_buffer_size;
if (msg_queue == NULL) {
ESP_LOGE(TAG, "Message queue not initialized. Terminating task.");
vTaskDelete(NULL);
}
ESP_LOGI(TAG, "Message broker task started.");
while (1) {
if (xQueueReceive(msg_queue, &received_msg, portMAX_DELAY)) {
// ESP_LOGI(TAG, "Received message from queue: MSGID=0x%02X, Length=%u",
// received_msg.msgid, received_msg.payload_len);
for (int i = 0; i < mr.num_direct_callbacks; i++) {
//ESP_LOGI(TAG, "Searching CALLBACK for %d", received_msg.msgid);
if (mr.FunctionList[i].MSGID == received_msg.msgid) {
//ESP_LOGI(TAG, "FOUND CALLBACK");
mr.FunctionList[i].callback(
received_msg.msgid, received_msg.data, received_msg.payload_len,
send_payload_buffer, send_payload_buffer_size,
send_message_buffer, send_message_buffer_size);
}
}
for (int i = 0; i < mr.num_direct_callbacks; i++) {
if (mr.FunctionList[i].MSGID == received_msg.msgid) {
// TODO: Not yet implemented
// Only send data to task, task should be created beforhead and wait
// for new data in the queue.
}
}
}
}
}
void SendMessage(const uint8_t *buffer, size_t length);
+55
View File
@@ -0,0 +1,55 @@
#ifndef _MESSAGE_HANDLER_HEADER
#define _MESSAGE_HANDLER_HEADER
#include "freertos/idf_additions.h"
#include <stddef.h>
#include <stdint.h>
typedef struct {
QueueHandle_t message_queue;
uint8_t *send_buffer;
size_t send_buffer_size;
uint8_t *payload_buffer;
size_t payload_buffer_size;
} MessageBrokerTaskParams_t;
typedef void (*RegisterFunctionCallback)(uint8_t msgid, const uint8_t *payload,
size_t payload_len,
uint8_t *send_payload_buffer,
size_t send_payload_buffer_size,
uint8_t *send_buffer,
size_t send_buffer_size);
typedef void (*RegisterTaskCallback)(uint8_t msgid, const uint8_t *payload,
size_t payload_len,
uint8_t *send_payload_buffer,
size_t send_payload_buffer_size,
uint8_t *send_buffer,
size_t send_buffer_size);
struct RegisterdFunction {
uint8_t MSGID;
RegisterFunctionCallback callback;
};
struct RegisterdTask {
uint8_t MSGID;
RegisterTaskCallback task;
};
struct MessageBroker {
struct RegisterdFunction FunctionList[64];
uint8_t num_direct_callbacks;
struct RegisterdTask TaskList[64];
uint8_t num_task_callbacks;
};
typedef void (*SendMessageHookCallback)(const uint8_t *buffer, size_t length);
void InitMessageBroker();
void RegisterCallback(uint8_t msgid, RegisterFunctionCallback callback);
void RegisterTask(uint8_t msgid, RegisterTaskCallback callback);
void SendMessage(const uint8_t *buffer, size_t length);
void MessageBrokerTask(void *param);
#endif
+112
View File
@@ -0,0 +1,112 @@
#include "message_parser.h"
#include <stdint.h>
#include <string.h>
MessageReceivedCallback on_message_received = NULL;
MessageFailCallback on_message_fail = NULL;
struct MessageReceive InitMessageReceive() {
struct MessageReceive mr = {
.state = WaitingForStartByte, // Startzustand des Parsers
.error = NoError, // Kein Fehler zu Beginn
.messageid = 0, // MSGID auf Standardwert setzen
// .message Array muss nicht explizit initialisiert werden, da es bei
// jedem Start geleert wird
.index = 0, // Index für das Nachrichten-Array initialisieren
.checksum = 0 // Checksumme initialisieren
};
return mr;
}
// Registrierungsfunktionen für die Callbacks
void register_message_callback(MessageReceivedCallback callback) {
on_message_received = callback;
}
void register_message_fail_callback(MessageFailCallback callback) {
on_message_fail = callback;
}
void parse_byte(struct MessageReceive *mr, uint8_t pbyte) {
switch (mr->state) {
case WaitingForStartByte:
if (pbyte == StartByte) {
mr->index = 0;
mr->checksum = 0;
mr->state = GetMessageType;
}
break;
case EscapedMessageType:
mr->messageid = pbyte;
mr->checksum ^= pbyte;
mr->state = InPayload;
break;
case GetMessageType:
if (pbyte == EscapeByte) {
mr->state = EscapedMessageType;
return;
}
if (pbyte == StartByte || pbyte == EndByte) {
mr->state = WaitingForStartByte;
mr->error = UnexpectedCommandByte;
if (on_message_received) {
on_message_fail(mr->messageid, mr->message, mr->index, mr->error);
}
return;
}
mr->messageid = pbyte;
mr->checksum ^= pbyte;
mr->state = InPayload;
break;
case EscapePayloadByte:
mr->message[mr->index++] = pbyte;
mr->checksum ^= pbyte;
mr->state = InPayload;
break;
case InPayload:
if (pbyte == EscapeByte) {
mr->state = EscapePayloadByte;
return;
}
if (pbyte == StartByte) {
mr->state = WaitingForStartByte;
mr->error = UnexpectedCommandByte;
if (on_message_received) {
on_message_fail(mr->messageid, mr->message, mr->index, mr->error);
}
return;
}
if (pbyte == EndByte) {
if (mr->checksum != 0x00) {
// Checksum failure
// The Checksum gets treated like a normal byte until the end byte
// accours. Therefore the last byte xor'ed to the checksum ist the
// checksum so the checksum must be Zero.
mr->state = WaitingForStartByte;
mr->error = WrongCheckSum;
if (on_message_received) {
on_message_fail(mr->messageid, mr->message, mr->index, mr->error);
}
return;
}
if (on_message_received) {
on_message_received(mr->messageid, mr->message,
mr->index - 1); // remove checksum byte by just
// setting the length of the message
}
mr->state = WaitingForStartByte;
}
if (mr->index < MAX_TOTAL_CONTENT_LENGTH) {
mr->message[mr->index++] = pbyte;
mr->checksum ^= pbyte;
} else {
mr->state = WaitingForStartByte;
mr->error = MessageToLong;
if (on_message_received) {
on_message_fail(mr->messageid, mr->message, mr->index, mr->error);
}
return;
}
break;
}
}
+52
View File
@@ -0,0 +1,52 @@
#ifndef _MESSAGE_PARSER_HEADER
#define _MESSAGE_PARSER_HEADER
#include <stddef.h>
#include <stdint.h>
#define MAX_MESSAGE_PAYLOAD_LENGTH 512
#define MAX_TOTAL_CONTENT_LENGTH (MAX_MESSAGE_PAYLOAD_LENGTH + 1)
enum ParserState {
WaitingForStartByte,
GetMessageType,
EscapedMessageType,
EscapePayloadByte,
InPayload,
};
enum ParserError {
NoError,
WrongCheckSum,
MessageToLong,
UnexpectedCommandByte,
};
typedef enum {
StartByte = 0xAA,
EscapeByte = 0xBB,
EndByte = 0xCC,
} MessageBytes;
struct MessageReceive {
enum ParserState state;
enum ParserError error;
uint8_t messageid;
uint8_t message[MAX_MESSAGE_PAYLOAD_LENGTH];
uint16_t index;
uint8_t checksum;
};
typedef void (*MessageReceivedCallback)(uint8_t msgid, const uint8_t *payload,
size_t payload_len);
typedef void (*MessageFailCallback)(uint8_t msgid, const uint8_t *payload,
size_t payload_len, enum ParserError error);
struct MessageReceive InitMessageReceive();
void register_message_callback(MessageReceivedCallback callback);
void register_message_fail_callback(MessageFailCallback callback);
void parse_byte(struct MessageReceive *mr, uint8_t pbyte);
#endif
+124
View File
@@ -0,0 +1,124 @@
#ifndef MESSAGE_STRUCTS_H
#define MESSAGE_STRUCTS_H
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]
#define MACSTR "%02X:%02X:%02X:%02X:%02X:%02X"
typedef enum {
OTA_PREPARE_FOR_UPDATE,
OTA_PREPARE_ACKNOWLEDGED,
OTA_READY_TO_RECEIVE,
OTA_CHUNK,
OTA_REQUEST_BLOCK_STATUS,
OTA_BLOCK_STATUS_REPORT,
OTA_COMMIT_BLOCK,
OTA_BLOCK_COMMITTED,
OTA_FINISH_UPDATE,
OTA_UPDATE_STATUS,
OTA_UPDATE_SLAVE_ACKED,
MASTER_READY_TO_SEND_CHUNKS,
StatusPage,
GetStatusPage,
ConfigPage,
PingPage,
BroadCastPage,
RegisterPage,
} CommandPages;
typedef struct __attribute__((packed)) {
uint32_t total_size;
uint32_t block_size;
} OTA_PREPARE_FOR_UPDATE_Payload;
typedef struct __attribute__((packed)) {
// Empty
} OTA_PREPARE_ACKNOWLEDGED_Payload;
typedef struct __attribute__((packed)) {
uint8_t status; // 0 = READY, 1 = ERROR
} OTA_READY_TO_RECEIVE_Payload;
typedef struct __attribute__((packed)) {
uint16_t block_id;
uint8_t chunk_id;
uint8_t data_len;
uint8_t data[200];
} OTA_CHUNK_Payload;
typedef struct __attribute__((packed)) {
uint16_t block_id;
} OTA_REQUEST_BLOCK_STATUS_Payload;
typedef struct __attribute__((packed)) {
uint16_t block_id;
uint32_t chunk_bitmask;
} OTA_BLOCK_STATUS_REPORT_Payload;
typedef struct __attribute__((packed)) {
uint16_t block_id;
} OTA_COMMIT_BLOCK_Payload;
typedef struct __attribute__((packed)) {
uint16_t block_id;
} OTA_BLOCK_COMMITTED_Payload;
typedef struct __attribute__((packed)) {
// Empty
} OTA_FINISH_UPDATE_Payload;
typedef struct __attribute__((packed)) {
uint8_t status; // 0 = SUCCESS, 1 = FAILED
} OTA_UPDATE_STATUS_Payload;
typedef struct __attribute__((packed)) {
uint16_t current_block_id;
uint16_t update_buffer_write_index;
uint32_t update_size;
uint16_t sequenz_counter; // how often the update buffer gets written
uint8_t status; // 0 = SUCCESS, 1 = FAILED
} OTA_UPDATE_ACK_Payload;
typedef struct __attribute__((packed)) {
uint16_t version; // software version
uint8_t runningPartition;
uint8_t status;
uint32_t uptime;
} StatusPayload;
typedef struct __attribute__((packed)) {
} GetStatusPayload;
typedef struct __attribute__((packed)) {
uint8_t timeslot;
} ConfigPayload;
typedef struct __attribute__((packed)) {
uint32_t timestamp;
} PingPayload;
typedef struct __attribute__((packed)) {
} BroadCastPayload;
typedef struct __attribute__((packed)) {
bool familierClient;
} RegisterPayload;
// TODO: Check checksum fields
typedef struct __attribute__((packed)) {
uint16_t length; // length of complete firmware
uint8_t checksum; // checksum of firmware
} FirmwarePrepPayload;
// TODO: Check checksum fields
typedef struct __attribute__((packed)) {
uint8_t length;
uint8_t checksum;
uint32_t address;
uint8_t payload[240]; // TODO: need a way to figure out a safe value for this
} FirmwarePayload;
#endif // MESSAGE_STRUCTS_H
+678
View File
@@ -0,0 +1,678 @@
#include "ota_update.h"
#include "client_handler.h"
#include "communication_handler.h"
#include "driver/uart.h"
#include "esp_app_format.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_log_buffer.h"
#include "esp_now.h"
#include "esp_ota_ops.h"
#include "esp_partition.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "message_builder.h"
#include "message_handler.h"
#include "message_structs.h"
#include "uart_handler.h"
#include "uart_msg_ids.h"
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
static const char *TAG = "ALOX - OTA";
static QueueHandle_t ota_task_queue = NULL;
static esp_ota_handle_t update_handle = 0;
static uint8_t update_buffer[UPDATE_BUFFER_SIZE];
static uint8_t update_buffer_chunk[250];
static uint8_t update_buffer_chunk_len;
static uint32_t chunk_bitmask;
static uint16_t current_block_id;
static uint16_t update_buffer_write_index;
static uint32_t update_size;
static uint16_t sequenz_counter; // how often the update buffer gets written
static esp_partition_t partition_to_read_update_from;
static uint32_t partition_to_read_from_read_index;
static ClientList *client_list;
static bool all_chunks_send;
static bool finished;
uint32_t get_app_size(const esp_partition_t *app_size_partition) {
esp_app_desc_t app_desc;
esp_ota_get_partition_description(app_size_partition, &app_desc);
esp_image_header_t header;
esp_partition_read(app_size_partition, 0, &header, sizeof(header));
if (header.magic != ESP_IMAGE_HEADER_MAGIC) {
ESP_LOGE(TAG, "KEIN VALIDER HEADER");
return 0;
}
uint32_t data_len = sizeof(header);
for (int i = 0; i < header.segment_count; i++) {
esp_image_segment_header_t segment_header;
esp_partition_read(app_size_partition, data_len, &segment_header,
sizeof(segment_header));
ESP_LOGI(TAG, "SEGMENT %d Address %d, Segment DataLen %d", i,
segment_header.load_addr, segment_header.data_len);
uint32_t padded_len = (segment_header.data_len + 3) & ~3;
data_len += padded_len + sizeof(segment_header);
}
data_len += 1;
data_len += 32;
uint32_t padding = (16 - (data_len % 16)) % 16;
data_len += padding;
return data_len;
}
int ota_send_finish(ClientList *client_list) {
// read flash and send data
BaseMessage replyMessage = {};
OTA_FINISH_UPDATE_Payload payload = {};
ESP_LOGI(TAG, "OTA SEND FINISH");
replyMessage = MessageBuilder(OTA_FINISH_UPDATE, *(PayloadUnion *)&payload,
sizeof(payload));
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_list->Clients[i].slotIsUsed) {
if (client_list->Clients[i].ota_status == OTA_UPDATING) {
esp_now_send(client_list->Clients[i].macAddr, (uint8_t *)&replyMessage,
sizeof(BaseMessage));
client_list->Clients[i].ota_status = OTA_AWAITING_ACK;
}
}
}
finished = true;
return 0;
}
int ota_send_next_update_chunk(ClientList *client_list) {
// read flash and send data
BaseMessage replyMessage = {};
OTA_CHUNK_Payload payload = {};
size_t actual_read = 200;
if (partition_to_read_from_read_index + actual_read > update_size) {
actual_read = update_size - partition_to_read_from_read_index;
}
esp_err_t err = esp_partition_read(&partition_to_read_update_from,
partition_to_read_from_read_index,
payload.data, actual_read);
if (actual_read < 200) {
ESP_LOG_BUFFER_HEX(TAG, payload.data, actual_read);
}
if (err != ESP_OK) {
ESP_LOGE(TAG, "Could not read partition");
}
partition_to_read_from_read_index += actual_read;
payload.data_len = actual_read;
ESP_LOGI(TAG, "READ %d Bytes Sendig it to all Clients waiting", actual_read);
ESP_LOGI(TAG, "READ PARTITION AT %d Bytes MAX Bytes %d",
partition_to_read_from_read_index, update_size);
replyMessage =
MessageBuilder(OTA_CHUNK, *(PayloadUnion *)&payload, sizeof(payload));
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_list->Clients[i].slotIsUsed) {
if (client_list->Clients[i].ota_status == OTA_UPDATING) {
esp_now_send(client_list->Clients[i].macAddr, (uint8_t *)&replyMessage,
sizeof(BaseMessage));
client_list->Clients[i].ota_status = OTA_AWAITING_ACK;
}
}
}
if (partition_to_read_from_read_index == update_size)
return 1; // last update chunk send now finish it!
return 0;
}
void MasterOTATask(void *pvParameter) {
ESP_LOGI(TAG, "master_ota_task started");
ota_task_queue_message_t msg;
MasterOTA_TaskParams_t task_params = *(MasterOTA_TaskParams_t *)pvParameter;
client_list = task_params.client_list;
while (1) {
if (xQueueReceive(ota_task_queue, &msg, portMAX_DELAY)) {
ESP_LOGI(TAG, "master ota_task received command: %d", msg.command);
BaseMessage replyMessage = {};
switch (msg.command) {
case OTA_SEND_SLAVES_PREPARE_MESSAGE: {
}
case OTA_SLAVE_WILL_PREPARE: {
int id = get_client_id(client_list, msg.mac_addr);
if (id < 0) {
// error
ESP_LOGE(TAG, "Error set OTA_PREPARE could not get client id");
}
// just wait
// mark client that it will wait
ESP_LOGI(TAG, "MASTER OTA TASK: Marking Client %d as OTA_PREPARING",
id);
client_list->Clients[id].ota_status = OTA_PREPARING;
break;
}
case OTA_SLAVE_IS_PREPARED: {
// client is prepared check if all clients are preapred to send chunks
int id = get_client_id(client_list, msg.mac_addr);
if (id < 0) {
// error
}
if (client_list->Clients[id].ota_status == OTA_PREPARING) {
ESP_LOGI(TAG, "MASTER OTA TASK: Marking Client %d as OTA_UPDATING",
id);
client_list->Clients[id].ota_status = OTA_UPDATING;
} else {
ESP_LOGE(TAG, "MASTER OTA TASK: Client this should not happend");
}
bool start = true;
// check if all clients are prepared
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_list->Clients[i].slotIsUsed) {
if (client_list->Clients[i].ota_status != OTA_UPDATING)
start = false;
}
}
if (start)
ota_send_next_update_chunk(client_list);
break;
}
case OTA_SLAVE_ACKED: {
// mark client as acked check if all clients acked to send next message
int id = get_client_id(client_list, msg.mac_addr);
if (id < 0) {
// error
}
if (client_list->Clients[id].ota_status == OTA_AWAITING_ACK) {
ESP_LOGI(TAG, "OTA_SLAVE_ACKED Client %d Status Update OTA_UPDATING",
id);
client_list->Clients[id].ota_status = OTA_UPDATING;
} else {
ESP_LOGE(TAG, "OTA_SLAVE_ACKED Client %d Status Should not HAPPEND",
id);
}
bool start = true;
// check if all clients are prepared
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_list->Clients[i].slotIsUsed) {
ESP_LOGI(TAG, "SLOT %d is USED", i);
if (client_list->Clients[i].ota_status != OTA_UPDATING)
start = false;
}
}
if (start) {
if (finished)
break; // dont need to send anything else
if (all_chunks_send) {
ota_send_finish(client_list);
break;
}
ESP_LOGE(TAG, "OTA_SLAVE_ACKED all clients have the status "
"OTA_UPDATING SENDING NEXT CHUNK");
int end = ota_send_next_update_chunk(client_list);
if (end) {
all_chunks_send = true;
}
}
break;
}
case OTA_SLAVE_ERROR:
break;
// mark client as error
case OTA_MASTER_SEND_PREAPRE_REQUEST:
break;
case OTA_MASTER_SEND_CHUNK:
break;
case OTA_MASTER_SEND_FINISH:
break;
}
}
}
}
void slave_ota_task(void *pvParameter) {
ESP_LOGI(TAG, "slave_ota_task started");
ota_task_queue_message_t msg;
BaseMessage replyMessage = {};
while (1) {
if (xQueueReceive(ota_task_queue, &msg, portMAX_DELAY)) {
ESP_LOGI(TAG, "slave ota_task received command: %d", msg.command);
switch (msg.command) {
case OTA_SEND_SLAVES_PREPARE_MESSAGE:
break;
case OTA_SLAVE_WILL_PREPARE:
break;
case OTA_SLAVE_IS_PREPARED:
break;
case OTA_SLAVE_ACKED:
break;
case OTA_SLAVE_ERROR:
break;
case OTA_MASTER_SEND_PREAPRE_REQUEST: {
ESP_LOGE(TAG, "START PREAPRE CALL");
int part = prepare_ota_update(); // this part function is blocking
OTA_READY_TO_RECEIVE_Payload payload = {};
if (part < 0) {
payload.status = 1; // ERROR
} else {
payload.status = 0; // READY
}
ESP_LOGE(TAG, "PREPARED %d", part);
replyMessage = MessageBuilder(
OTA_READY_TO_RECEIVE, *(PayloadUnion *)&payload, sizeof(payload));
esp_now_send(msg.mac_addr, (uint8_t *)&replyMessage,
sizeof(BaseMessage));
break;
}
case OTA_MASTER_SEND_CHUNK: {
// TODO: Move Update_buffer_chunk in normal update buffer no need for
// the extra step...
// TODO: at the moment its just so i can use the write_ota_update
// function unmodified
ESP_LOGI(TAG, "Master send Chunk writing it!");
write_ota_update(update_buffer_chunk_len, update_buffer_chunk);
ESP_LOGI(TAG, "AFTER WRITE_OTA_UPDATE!");
OTA_UPDATE_ACK_Payload payload = {
.update_buffer_write_index = update_buffer_write_index,
.current_block_id = current_block_id,
.sequenz_counter = sequenz_counter,
.status = 0,
};
replyMessage = MessageBuilder(
OTA_UPDATE_SLAVE_ACKED, *(PayloadUnion *)&payload, sizeof(payload));
esp_now_send(msg.mac_addr, (uint8_t *)&replyMessage,
sizeof(BaseMessage));
break;
}
case OTA_MASTER_SEND_FINISH: {
esp_err_t err = end_ota_update();
int status = 0;
if (err != ESP_OK) {
status = 1; // TODO: Set real error
}
ESP_LOGI(TAG, "UPDATE FERTIG STATUS %d should be 0", status);
OTA_UPDATE_ACK_Payload payload = {
.update_buffer_write_index = update_buffer_write_index,
.current_block_id = current_block_id,
.sequenz_counter = sequenz_counter,
.status = status,
};
replyMessage = MessageBuilder(
OTA_UPDATE_SLAVE_ACKED, *(PayloadUnion *)&payload, sizeof(payload));
esp_now_send(msg.mac_addr, (uint8_t *)&replyMessage,
sizeof(BaseMessage));
break;
}
}
}
}
}
void start_uart_update(uint8_t msgid, const uint8_t *payload,
size_t payload_len, uint8_t *send_payload_buffer,
size_t send_payload_buffer_size, uint8_t *send_buffer,
size_t send_buffer_size) {
ESP_LOGI(TAG, "OTA Update Start Uart Command");
vTaskPrioritySet(NULL, 2);
update_size = 0;
update_buffer_write_index = 0;
sequenz_counter = 0;
all_chunks_send = false;
finished = false;
int part = prepare_ota_update();
uart_ota_start_t *start = (uart_ota_start_t *)send_payload_buffer;
start->partition = part & 0xff;
// TODO: Refine Errors
// Set error
if (part < 0) {
start->error = 0x01;
}
int send_payload_len = sizeof(uart_ota_start_t);
int len = build_message(UART_OTA_START, send_payload_buffer, send_payload_len,
send_buffer, send_buffer_size);
if (len < 0) {
ESP_LOGE(TAG,
"Error Building UART Message: payload_len, %d, sendbuffer_size: "
"%d, mes_len(error): %d",
payload_len, send_buffer_size, len);
return;
}
uart_write_bytes(MASTER_UART, send_buffer, len);
}
esp_err_t write_ota_update(uint32_t write_len, const uint8_t *payload) {
// ESP_LOGI(TAG, "write_ota_update: write_len: %d", write_len);
// ESP_LOGI(TAG, "write_ota_update: update_buffer_write_index: %d",
// update_buffer_write_index);
if (update_buffer_write_index + write_len > UPDATE_BUFFER_SIZE) {
ESP_LOGI(TAG, "write_ota_update: schreib das update weg!");
esp_err_t err =
esp_ota_write(update_handle, update_buffer, update_buffer_write_index);
if (err != ESP_OK) {
return err;
}
update_buffer_write_index = 0;
sequenz_counter++;
}
memcpy(&update_buffer[update_buffer_write_index], payload, write_len);
update_buffer_write_index += write_len;
return ESP_OK;
}
void payload_uart_update(uint8_t msgid, const uint8_t *payload_data_from_uart,
size_t total_payload_len_from_uart,
uint8_t *send_payload_buffer,
size_t send_payload_buffer_size, uint8_t *send_buffer,
size_t send_buffer_size) {
const uint8_t *actual_firmware_data = payload_data_from_uart;
uint32_t write_len = total_payload_len_from_uart;
if (update_size == 0) {
ESP_LOGI(TAG, "First chunk received. write_len: %d", write_len);
}
update_size += write_len;
esp_err_t err = write_ota_update(write_len, actual_firmware_data);
uart_ota_ack_t *ack = (uart_ota_ack_t *)send_payload_buffer;
ack->sequence_counter = sequenz_counter;
ack->write_index = update_buffer_write_index;
ack->error = (err == ESP_OK) ? 0x00 : 0x01;
size_t send_payload_len = sizeof(uart_ota_ack_t);
if (err != ESP_OK) {
ESP_LOGE(TAG, "GOT ESP ERROR WRITE OTA %d", err);
}
int len = build_message(UART_OTA_PAYLOAD, send_payload_buffer,
send_payload_len, send_buffer, send_buffer_size);
if (len < 0) {
ESP_LOGE(TAG,
"Error Building UART Message: payload_len, %d, sendbuffer_size: "
"%d, mes_len(error): %d",
total_payload_len_from_uart, send_buffer_size, len);
return;
}
uart_write_bytes(MASTER_UART, send_buffer, len);
}
void end_uart_update(uint8_t msgid, const uint8_t *payload, size_t payload_len,
uint8_t *send_payload_buffer,
size_t send_payload_buffer_size, uint8_t *send_buffer,
size_t send_buffer_size) {
ESP_LOGI(TAG, "OTA Update End Uart Command");
esp_err_t err = end_ota_update();
uart_ota_end_t *end = (uart_ota_end_t *)send_payload_buffer;
end->error = err & 0xff;
int send_payload_len = sizeof(uart_ota_end_t);
int len = build_message(UART_OTA_END, send_payload_buffer, send_payload_len,
send_buffer, send_buffer_size);
if (len < 0) {
ESP_LOGE(TAG,
"Error Building UART Message: payload_len, %d, sendbuffer_size: "
"%d, mes_len(error): %d",
payload_len, send_buffer_size, len);
return;
}
uart_write_bytes(MASTER_UART, send_buffer, len);
vTaskPrioritySet(NULL, 1);
}
void init_ota() {
ota_task_queue = xQueueCreate(50, sizeof(ota_task_queue_message_t));
}
void RegisterUART_OTAFunctions() {
RegisterCallback(UART_OTA_START, start_uart_update);
RegisterCallback(UART_OTA_PAYLOAD, payload_uart_update);
RegisterCallback(UART_OTA_END, end_uart_update);
}
int prepare_ota_update() {
const esp_partition_t *running = esp_ota_get_running_partition();
ESP_LOGI(TAG, "Running Partition: %s", running->label);
const esp_partition_t *update_partition =
esp_ota_get_next_update_partition(NULL);
if (update_partition == NULL) {
ESP_LOGE(TAG, "Failed to find OTA partition.");
return -1;
}
ESP_LOGI(TAG, "Writing OTA Update to Partition: %s", update_partition->label);
esp_err_t err =
esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
return -2;
}
if (update_partition->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_0) {
return 0;
}
if (update_partition->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_1) {
return 1;
}
// TODO: Unknow partition
return 2;
}
esp_err_t end_ota_update() {
if (update_buffer_write_index > 0) {
ESP_LOG_BUFFER_HEX(TAG, update_buffer, update_buffer_write_index);
esp_err_t err =
esp_ota_write(update_handle, update_buffer, update_buffer_write_index);
vTaskDelay(1);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error writing remaining data to partition: %s",
esp_err_to_name(err));
return err;
}
}
esp_err_t err = esp_ota_end(update_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(err));
ESP_LOGI(TAG, "Total blocks written: %u, Last partial block size: %u",
sequenz_counter, update_buffer_write_index);
return err;
}
const esp_partition_t *update_partition =
esp_ota_get_next_update_partition(NULL);
err = esp_ota_set_boot_partition(update_partition);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed: %s",
esp_err_to_name(err));
}
return err;
}
// Acknoledge that the slave should prepare for an update
// Queues the Prepare Task beacuse it takes like 30 seconds
void slave_Prep_Upgrade_Callback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len) {
ESP_LOGE(TAG, "SLAVE PREPARE FOR UPDATE Callback");
update_size = 0;
update_buffer_write_index = 0;
sequenz_counter = 0;
const BaseMessage *message = (const BaseMessage *)data;
const OTA_PREPARE_FOR_UPDATE_Payload *payload =
&message->payload.ota_prepare_for_update_payload;
// total_update_size = payload->total_size;
// Queue Command for Task to call the prepare method
ota_task_queue_message_t msg = {.command = OTA_MASTER_SEND_PREAPRE_REQUEST};
memcpy(msg.mac_addr, esp_now_info->src_addr, ESP_NOW_ETH_ALEN);
if (xQueueSend(ota_task_queue, &msg, pdMS_TO_TICKS(10)) != pdTRUE) {
ESP_LOGE(TAG, "Failed to send prepare command to OTA task");
}
ESP_LOGE(TAG, "SLAVE PREPARE CALLBACK AFTER QUEUE SEND");
// Tell the master that the slave will preapre
OTA_PREPARE_ACKNOWLEDGED_Payload *reply_payload;
BaseMessage reply_message =
MessageBuilder(OTA_PREPARE_ACKNOWLEDGED, *(PayloadUnion *)&reply_payload,
sizeof(OTA_PREPARE_ACKNOWLEDGED_Payload));
ESP_ERROR_CHECK(esp_now_send(esp_now_info->src_addr,
(uint8_t *)&reply_message, sizeof(BaseMessage)));
}
void slave_Update_Chunk_Callback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len) {
const BaseMessage *message = (const BaseMessage *)data;
const OTA_CHUNK_Payload *payload = &message->payload.ota_chunk_payload;
// copy data to update_buffer_chunk so that the write method can write it
// back later
memcpy(update_buffer_chunk, payload->data, payload->data_len);
update_buffer_chunk_len = payload->data_len;
ESP_LOGI(TAG, "slave_update_Chunk_Callback got %d bytes from Master",
payload->data_len);
// Queue Command for Task to call the ota_write_message method
ota_task_queue_message_t msg = {.command = OTA_MASTER_SEND_CHUNK};
memcpy(msg.mac_addr, esp_now_info->src_addr, ESP_NOW_ETH_ALEN);
if (xQueueSend(ota_task_queue, &msg, pdMS_TO_TICKS(10)) != pdTRUE) {
ESP_LOGE(TAG, "Failed to send prepare command to OTA task");
}
}
void slave_Update_Finished_Callback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len) {
const BaseMessage *message = (const BaseMessage *)data;
const OTA_FINISH_UPDATE_Payload *payload =
&message->payload.ota_finish_update_payload;
ESP_LOGI(TAG, "slave_Update_Finished_Callback");
// Queue Command for Task to call the ota_write_message method
ota_task_queue_message_t msg = {.command = OTA_MASTER_SEND_FINISH};
memcpy(msg.mac_addr, esp_now_info->src_addr, ESP_NOW_ETH_ALEN);
if (xQueueSend(ota_task_queue, &msg, pdMS_TO_TICKS(10)) != pdTRUE) {
ESP_LOGE(TAG, "Failed to send prepare command to OTA task");
}
}
void start_ota_update_espnow(uint8_t msgid, const uint8_t *payload,
size_t payload_len, uint8_t *send_payload_buffer,
size_t send_payload_buffer_size,
uint8_t *send_buffer, size_t send_buffer_size) {
ESP_LOGI(TAG, "Starting OTA update for all clients");
const esp_partition_t *ota_update_partition;
ota_update_partition = esp_ota_get_next_update_partition(NULL);
if (ota_update_partition == NULL) {
ESP_LOGE(TAG, "Failed to get update partition");
return;
}
update_size = get_app_size(ota_update_partition);
partition_to_read_update_from = *ota_update_partition;
partition_to_read_from_read_index = 0;
BaseMessage replyMessage = {};
OTA_PREPARE_FOR_UPDATE_Payload replyPayload = {};
replyMessage =
MessageBuilder(OTA_PREPARE_FOR_UPDATE, *(PayloadUnion *)&replyPayload,
sizeof(replyPayload));
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_list->Clients[i].slotIsUsed) {
esp_now_send(client_list->Clients[i].macAddr, (uint8_t *)&replyMessage,
sizeof(BaseMessage));
client_list->Clients[i].ota_status = OTA_READY;
}
}
}
void master_ota_prepare_acknowledge_callback(
const esp_now_recv_info_t *esp_now_info, const uint8_t *data,
int data_len) {
ESP_LOGI(TAG, "entering master_ota_prepare_acknowledge_callback");
// Queue Command for Task to call the ota_write_message method
ota_task_queue_message_t msg = {.command = OTA_SLAVE_WILL_PREPARE};
memcpy(msg.mac_addr, esp_now_info->src_addr, ESP_NOW_ETH_ALEN);
if (xQueueSend(ota_task_queue, &msg, pdMS_TO_TICKS(10)) != pdTRUE) {
ESP_LOGE(TAG, "Failed to send prepare command to OTA task");
}
}
void master_ota_ready_to_recieve_callback(
const esp_now_recv_info_t *esp_now_info, const uint8_t *data,
int data_len) {
ESP_LOGI(TAG, "entering master_ota_ready_to_recieve_callback");
// Queue Command for Task to call the ota_write_message method
ota_task_queue_message_t msg = {.command = OTA_SLAVE_IS_PREPARED};
memcpy(msg.mac_addr, esp_now_info->src_addr, ESP_NOW_ETH_ALEN);
if (xQueueSend(ota_task_queue, &msg, pdMS_TO_TICKS(10)) != pdTRUE) {
ESP_LOGE(TAG, "Failed to send prepare command to OTA task");
}
}
void master_ota_update_slave_acknowledge_callback(
const esp_now_recv_info_t *esp_now_info, const uint8_t *data,
int data_len) {
ESP_LOGI(TAG, "entering master_ota_update_slave_acknowledge_callback");
// Queue Command for Task to call the ota_write_message method
ota_task_queue_message_t msg = {.command = OTA_SLAVE_ACKED};
memcpy(msg.mac_addr, esp_now_info->src_addr, ESP_NOW_ETH_ALEN);
if (xQueueSend(ota_task_queue, &msg, pdMS_TO_TICKS(10)) != pdTRUE) {
ESP_LOGE(TAG, "Failed to send prepare command to OTA task");
}
}
+97
View File
@@ -0,0 +1,97 @@
#ifndef OTA_UPDATE_H
#define OTA_UPDATE_H
#include "client_handler.h"
#include "esp_err.h"
#include "esp_now.h"
#include "esp_partition.h"
#include "message_structs.h"
#include <stdint.h>
#include <sys/types.h>
#define UPDATE_BUFFER_SIZE 4096
#define UPDATE_PAYLOAD_SIZE 200
#define UPDATE_MAX_SEQUENZES (UPDATE_BUFFER_SIZE / UPDATE_PAYLOAD_SIZE)
typedef enum {
OTA_SEND_SLAVES_PREPARE_MESSAGE,
OTA_SLAVE_WILL_PREPARE,
OTA_SLAVE_IS_PREPARED,
OTA_SLAVE_ACKED,
OTA_SLAVE_ERROR,
OTA_MASTER_SEND_PREAPRE_REQUEST,
OTA_MASTER_SEND_CHUNK,
OTA_MASTER_SEND_FINISH,
} ota_command_t;
typedef struct {
ota_command_t command;
uint8_t mac_addr[ESP_NOW_ETH_ALEN];
} ota_task_queue_message_t;
typedef struct {
ClientList *client_list;
} MasterOTA_TaskParams_t;
typedef struct __attribute__((packed)) {
uint16_t sequence_counter;
uint16_t write_index;
uint8_t error;
} uart_ota_ack_t;
typedef struct __attribute__((packed)) {
uint8_t partition;
uint8_t error;
} uart_ota_start_t;
typedef struct __attribute__((packed)) {
uint8_t error;
} uart_ota_end_t;
void init_ota();
void RegisterUART_OTAFunctions();
void MasterOTATask(void *pvParameter);
void slave_ota_task(void *pvParameter);
u_int32_t get_app_size(const esp_partition_t *app_size_partition);
int prepare_ota_update();
esp_err_t write_ota_update(uint32_t write_len, const uint8_t *payload);
esp_err_t end_ota_update();
void slave_Prep_Upgrade_Callback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len);
void slave_Update_Chunk_Callback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len);
void slave_Update_Finished_Callback(const esp_now_recv_info_t *esp_now_info,
const uint8_t *data, int data_len);
void master_ota_prepare_acknowledge_callback(
const esp_now_recv_info_t *esp_now_info, const uint8_t *data, int data_len);
void master_ota_ready_to_recieve_callback(
const esp_now_recv_info_t *esp_now_info, const uint8_t *data, int data_len);
void master_ota_update_slave_acknowledge_callback(
const esp_now_recv_info_t *esp_now_info, const uint8_t *data, int data_len);
void start_uart_update(uint8_t msgid, const uint8_t *payload,
size_t payload_len, uint8_t *send_payload_buffer,
size_t send_payload_buffer_size, uint8_t *send_buffer,
size_t send_buffer_size);
void payload_uart_update(uint8_t msgid, const uint8_t *payload_data_from_uart,
size_t total_payload_len_from_uart,
uint8_t *send_payload_buffer,
size_t send_payload_buffer_size, uint8_t *send_buffer,
size_t send_buffer_size);
void end_uart_update(uint8_t msgid, const uint8_t *payload, size_t payload_len,
uint8_t *send_payload_buffer,
size_t send_payload_buffer_size, uint8_t *send_buffer,
size_t send_buffer_size);
void start_ota_update_espnow(uint8_t msgid, const uint8_t *payload,
size_t payload_len, uint8_t *send_payload_buffer,
size_t send_payload_buffer_size,
uint8_t *send_buffer, size_t send_buffer_size);
#endif
+91
View File
@@ -0,0 +1,91 @@
#include "driver/gpio.h"
#include "driver/uart.h"
#include "esp_log.h"
#include "esp_log_buffer.h"
#include "freertos/idf_additions.h"
#include "hal/uart_types.h"
#include "message_handler.h"
#include "message_parser.h"
#include "nvs_flash.h"
#include "portmacro.h"
#include <stdbool.h>
#include <string.h>
#include "message_parser.h"
#include "uart_handler.h"
static const char *TAG = "ALOX - UART";
static QueueHandle_t parsed_message_queue;
void init_uart(QueueHandle_t msg_queue_handle) {
uart_config_t uart_config = {// .baud_rate = 115200, // 921600, 115200
.baud_rate = 921600,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE};
uart_driver_install(MASTER_UART, BUF_SIZE * 2, 0, 0, NULL, 0);
uart_param_config(MASTER_UART, &uart_config);
uart_set_pin(MASTER_UART, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE,
UART_PIN_NO_CHANGE);
parsed_message_queue = msg_queue_handle;
register_message_callback(HandleMessageReceivedCallback);
register_message_fail_callback(HandleMessageFailCallback);
xTaskCreate(uart_read_task, "Read Uart", 4096, NULL, 1, NULL);
}
void uart_read_task(void *param) {
// Send all Input from Uart to the Message Handler for Parsing
struct MessageReceive mr = InitMessageReceive();
uint8_t *data = (uint8_t *)malloc(BUF_SIZE);
int len = 0;
while (1) {
len = 0;
len =
uart_read_bytes(MASTER_UART, data, BUF_SIZE, (20 / portTICK_PERIOD_MS));
if (len > 0) {
for (int i = 0; i < len; ++i) {
parse_byte(&mr, data[i]);
}
}
}
}
// TODO: Remove this? or handle message sending in any other way reduce
// abstraction hell
void send_message_hook(const uint8_t *buffer, size_t length) {
uart_write_bytes(MASTER_UART, buffer, length);
}
void HandleMessageReceivedCallback(uint8_t msgid, const uint8_t *payload,
size_t payload_len) {
/*ESP_LOGI(TAG, "GOT UART MESSAGE MSGID: %02X, Len: %u bytes \nMSG: ", msgid,
payload_len, payload);
ESP_LOG_BUFFER_HEX(TAG, payload, payload_len);*/
ParsedMessage_t msg_to_send;
msg_to_send.msgid = msgid;
msg_to_send.payload_len = payload_len;
memcpy(msg_to_send.data, payload, payload_len);
if (xQueueSend(parsed_message_queue, &msg_to_send, portMAX_DELAY) != pdPASS) {
// Fehlerbehandlung: Queue voll oder Senden fehlgeschlagen
ESP_LOGE(TAG, "Failed to send parsed message to queue.");
}
return;
}
void HandleMessageFailCallback(uint8_t msgid, const uint8_t *payload,
size_t payload_len, enum ParserError error) {
ESP_LOGE(
TAG,
"UART MESSAGE Parsing Failed MSGID: %02X, Len: %u, ERROR: %X, \nMSG: ",
msgid, payload_len, error);
ESP_LOG_BUFFER_HEX(TAG, payload, payload_len);
return;
}
+36
View File
@@ -0,0 +1,36 @@
#ifndef UART_HANDLER_H
#define UART_HANDLER_H
#include "freertos/idf_additions.h"
#include "message_parser.h"
#include <stddef.h>
#include <stdint.h>
#ifdef CONFIG_IDF_TARGET_ESP32S3
#define MASTER_UART UART_NUM_1
#define TXD_PIN (GPIO_NUM_2)
#define RXD_PIN (GPIO_NUM_3)
#elif CONFIG_IDF_TARGET_ESP32C3
#define MASTER_UART UART_NUM_1
#define TXD_PIN (GPIO_NUM_1)
#define RXD_PIN (GPIO_NUM_2)
#endif
#define BUF_SIZE (256)
typedef struct {
uint8_t msgid;
size_t payload_len;
uint8_t data[MAX_MESSAGE_PAYLOAD_LENGTH];
} ParsedMessage_t;
void init_uart(QueueHandle_t msg_queue_handle);
void uart_read_task(void *param);
void uart_send_task(void *param);
void HandleMessageReceivedCallback(uint8_t msgid, const uint8_t *payload,
size_t payload_len);
void HandleMessageFailCallback(uint8_t msgid, const uint8_t *payload,
size_t payload_len, enum ParserError error);
#endif
+19
View File
@@ -0,0 +1,19 @@
#ifndef UART_MSG_IDS_H
#define UART_MSG_IDS_H
enum UART_MSG_IDS {
// 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,
};
#endif
+61
View File
@@ -0,0 +1,61 @@
{
"protocol": {
"start_byte": "0xAA",
"message_length": 0,
"max_payload": 255,
"checksum": "xor"
},
"messages_esp_to_pc": [
{
"name": "Clients",
"id": "0xE1",
"payload": [
{ "name": "clientCount", "type": "uint8_t" },
{ "name": "clientAvaiableBitMask", "type": "uint32_t" }
]
},
{
"name": "Status",
"id": "0xE2",
"payload": [
{ "name": "clientId", "type": "uint8_t" },
{ "name": "mac", "type": "uint8_t", "array": 6 }
]
},
{
"name": "Pong",
"id": "0xD1",
"payload": [
{ "name": "clientId", "type": "uint8_t" },
{ "name": "ping", "type": "uint32_t" }
]
}
],
"messages_pc_to_esp": [
{
"name": "RequestPing",
"id": "0xE1",
"payload": [
{ "name": "clientId", "type": "uint8_t" }
]
},
{
"name": "RequestStatus",
"id": "0xE2",
"payload": [
{ "name": "clientId", "type": "uint8_t" }
]
},
{
"name": "PrepareFirmwareUpdate",
"id": "0xF1"
},
{
"name": "FirmwareUpdateLine",
"id": "0xF2",
"payload": [
{ "name": "data", "type": "uint8_t", "array": 240 }
]
}
]
}
+200
View File
@@ -1,3 +1,151 @@
# Conventions
## Naming
### Filenames: snake_case (ota_master.c, ota_slave.c, com_handler.c)
### Functions: module_submodule_action()
- OTA Master = ota_m_send_chunk(), ota_m_init()
- OTA Slave = ota_s_handle_chunk(), ota_s_prepare_flash()
- com_init(), msg_parse()
### Variables:
- Global: g_ (g_client_list)
- Statics: s_ (s_update_handle)
- Constants: UPPER_SNAKE_CASE (UPDATE_BUFFER_SIZE)
### Types:
- Typedefs: Ends with _t (ota_payload_t)
- Struct-Tags: Ends with _t or spezifik name (struct ota_context)
- Unions: Ends with _u (msg_payload_u)
- Callbacks: End with _cb (com_m_rx_cb)
### FreeRTOS-Objects
- x for Basetypes (x_ota_queue, x_status_semaphor)
- v for void return (v_ota_task)
# UART Protokoll
## Struktur einer Nachricht
- Control Bytes:
- 0xAA = Startbyte
- 0xBB = EscapeByte
- 0xCC = EndByte
checksum = XOR über alle Bytes (ohne Control Bytes und Checksum-Byte)
Command, Payload und Checksum werden Escaped sollten sie einem Control Byte ensteprechend
| Startbyte | Command | Payload (variable) | Checksum | Endbyte |
|-----------|---------|--------------------|----------|---------|
### Felder im Detail:
- **Command** (`uint8_t`):
Gibt an, welcher Nachrichtentyp gesendet wird.
- **Payload** (`variabel`):
Datenfeld mit variabler Länge, abhängig vom `Command`.
- **Checksum** (`uint8_t`):
XOR über aller Bytes von `Command` und `Payload`.
### Messages
Command:
- UART_ECHO = 0x01
- UART_VERSION = 0x02
- UART_CLIENT_INFO = 0x03
Grundlegend sind alle Zahlenwerte im LittleEndian format!
#### UART_ECHO:
- Send Message: AA 01 01 CC
- Message Received: AA 01 01 CC
Sendet zurück was geschickt wird.
#### UART_VERSION:
| Offset | Länge (Bytes) | Bezeichnung | Beschreibung |
|--------|---------------|-------------|------------------|
| 0 | 2 | Version | Software Version |
| 2 | 7 | BuildHash | Git Hash |
- Send Message: AA 02 02 CC
- Message Received: AA 02 01 00 33 62 35 36 30 37 39 6F CC
| Version | Buildhash |
|---------|-----------|
| 1 | 3b56078 |
Sendet die Version und den Buildhash vom Master zurück.
#### UART_CLIENT_INFO:
Das erste Datenbyte nach dem Commando gibt an wie viele Client Infos in dieser Nachricht vorhanden sind.
Danach teilt sich ein Eintrag wie Folgt auf:
| Offset | Länge (Bytes) | Bezeichnung | Beschreibung |
|--------|---------------|----------------------------|---------------------------------------------------------------|
| 0 | 1 | Client ID | Eindeutige ID des Clients. |
| 1 | 1 | Ist verfügbar | Boolean-Wert (0 = nein, 1 = ja), ob der Client verfügbar ist. |
| 2 | 1 | Slot genutzt | Boolean-Wert (0 = nein, 1 = ja), ob der Slot belegt ist. |
| 3 | 6 | MAC-Adresse | Die Hardware-Adresse des Clients. |
| 9 | 4 | Letzter Ping | Zeit in Millisekunden seit dem letzten Ping. |
| 13 | 4 | Letzter erfolgreicher Ping | Zeit in Millisekunden seit dem letzten erfolgreichen Ping. |
| 17 | 2 | Version | Versionsnummer des Clients. |
##### Ein Client
- Send Message: AA 03 03 CC
- Message Received: AA 03 01 00 01 01 50 78 7D 18 89 F8 34 00 00 00 61 1F 00 00 02 00 76 CC
| Client ID | Verfügbar | Genutzt | MAC-Adresse | Last Ping | Last Successful Ping | Version |
|-----------|-----------|---------|-------------------|-----------|----------------------|---------|
| 0 | 1 | 1 | 50:78:7D:18:89:F8 | 52 | 8033 | 2 |
##### Zwei Clients
- Send Message: AA 03 03 CC
- Message Received: AA 03 02 00 01 01 50 78 7D 18 89 F8 22 00 00 00 F4 2A 01 00 02 00 01 01 01 50 78 7D 18 0C B4 10 00 00 00 F1 2A 01 00 02 00 FE CC
| Client ID | Verfügbar | Genutzt | MAC-Adresse | Last Ping | Last Successful Ping | Version |
|-----------|-----------|---------|-------------------|-----------|----------------------|---------|
| 0 | 1 | 1 | 50:78:7D:18:89:F8 | 34 | 76532 | 2 |
| 1 | 1 | 1 | 50:78:7D:18:C:B4 | 16 | 76529 | 2 |
#### UART_CLIENT_INPUT:
Die Identifizierung wird hier anhand der vorher gesendeten ClientID gemacht also muss einmal vorher `UART_CLIENT_INFO` aufgerufen werden.
Das erste Datenbyte nach dem Commando gibt an wie viele Client Infos in dieser Nachricht vorhanden sind.
Danach teilt sich ein Eintrag wie Folgt auf:
| Offset | Länge (Bytes) | Bezeichnung | Beschreibung |
|--------|---------------|-------------|----------------------------------------------------------------------------------|
| 0 | 1 | Client ID | Eindeutige ID des Clients. |
| 1 | 4 | LageX | Float Wert von der X Lage. |
| 5 | 4 | LageY | Float Wert von der Y Lage. |
| 9 | 4 | InputMaske | Int32 Wert der als Bitmaske genutzt wird um bis zu 32 Boolische Werte anzugeben. |
Inputmaske:
Taster1, Taster2, IOError1, IOErro2, AkkuStand1, AkkuStand2 (2 Bit kodiert für 25%,50%,75%,100%), rest unbelegt, default 0
| Bit1 | Bit2 | Akkustand |
|------|------|-----------|
| 0 | 0 | 25% |
| 0 | 1 | 50% |
| 1 | 0 | 75% |
| 1 | 1 | 100% |
<div style="page-break-after: always;"></div>
# Machbarkeits-Studie # Machbarkeits-Studie
## 1.0 Hardware-Features ## 1.0 Hardware-Features
@@ -124,3 +272,55 @@ techn. Anforderungen hinreichend gut umsetzen lassen.
und ob diese den ursprünglichen Anforderungen entsprechen. und ob diese den ursprünglichen Anforderungen entsprechen.
## OTA-Update Technische Umsetzung:
### Vorrausetzung:
- Update File steht bereit und ist unter 2MB groß.
- UART Verbindung steht
- ESP Funktioniert einwandfrei
- ESP Läuft auf Partition A, Partition B soll geupdated werden
### Erste Schritt:
- Update in 200Byte stücke zerhacken und stück für stück per UART an den Master schicken
- Uart Protokol hat schon eine fehlercheck für die Übertragung drinnen
- Firmware wird in Partition B geschrieben
- OTA API Validiert Firmware am ende
#### Hier könnte man schon einen Neustart machen und Validieren ob die Firmware für den Master läuft!
#### Denn angeblich kann man beim ESP die aktuell laufende Partition auslesen
### Zweiter Schritt:
- Master liest in 200Byte stücken die Firmware aus seiner Partition B aus
- Und schickt per Broadcast die ersten 20 Packete an die Clients
- Die clients haben 4KB Buffer vorgesehen wo sie die 20 Packete unterbringen können
- Master Forder Ack Bitmaske an zur Validierung das alle 20 Packete da sind
- Sollten in der Bitmaske zeilen fehlen gibt der Master per unicast die fehlenden Zeilen an die Entsprechend Clients erneut
- ESP NOW kümmert sich hier um die Datenintigrität
- Wenn alle ihre ersten 20 packete haben gibt der master das go und alle schreiben die ersten 20 packete weg.
- Der master aktuallisiert den fortschritt für alle clients -> dann kann man das auch abfragen per uart und hat eine Fortschrittsanzeige
- Alle melden sich zurück wenn sie fertig sind mit dem schreiben per ota und der buffer leer ist.
- Repeat bis alle Daten da sind
Hier hab ich mal grob gerechnet:
2MB in 200Byte Schritten -> 10.000 Packete
10.000 Packete in 20er Schritten -> 500 Sequencen
Retries und Acks mal aussen vor hab ich leider keinen richtigen anhaltspunkt wie lang das dauern kann.
Aber 500* ca 300ms => ist schonmal 150sekunden nur für das acken das die Packete da sind. Annahme hier das die maximal latenz beim Ping mit 16 Clients ca 300ms sind.
Entsprechend mit Daten und retries... ja kp, Gemini schätzt max 10min. Wird sich zeigen. Da addiert sich zu viel auf.
- Alle Clients validieren ihren firmware
- Sollte das bei einem nicht klappen muss man hier nochmal gucken ob man den ganzen process nochmal von vorne anstößt nur mit dem fehlenden client...
### Dritter Schritt:
- Alle Clients rebooten
- Clients geben rückmeldung ob das Update funktioniert
#### Hier müssten war noch entscheiden was passiert wenn das Update bei nur ein paar funktionert hat?
#### Was passiert wenn das Update garnicht funktioniert hat, behält der master dann auch seinen stand?
#### Entsprechend hätte man ihn vorher auch nicht neustarten dürfen
- Sollten alle Clients ihr go geben startet der Master auch neu
#### Wenn das Master Update jetzt fehlschlägt sagt er den clients bescheid und die booten auch wieder um?
Gibt halt noch ein zwar sachen die man sich überlegen muss aber ich denke den rest hab ich soweit ausgearbeitet
+582 -301
View File
File diff suppressed because it is too large Load Diff
+2098
View File
File diff suppressed because it is too large Load Diff
+2155
View File
File diff suppressed because it is too large Load Diff
+2357
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
Unity/*
test_runner
test_builder
+21
View File
@@ -0,0 +1,21 @@
CC=gcc
CFLAGS=-Wall -Wextra -I../main
SRC = Unity/src/unity.c test_parser.c \
../main/message_parser.c
TARGET = test_runner
.PHONY: test_builder
all: $(TARGET)
$(TARGET): $(SRC)
$(CC) $(CFLAGS) -o $@ $^
./$(TARGET)
test_builder: Unity/src/unity.c test_message_builder.c ../main/message_parser.c ../main/message_builder.c
$(CC) $(CFLAGS) -o $@ $^
@echo "--- Running Builder Tests ---"
./$(BUILDER_TEST_TARGET)
clean:
rm -f $(TARGET)
+9
View File
@@ -0,0 +1,9 @@
#include "Unity/src/unity.h"
void setUp(void) {} // optional
void tearDown(void) {} // optional
int main(void) {
UNITY_BEGIN();
return UNITY_END();
}
+433
View File
@@ -0,0 +1,433 @@
#include "Unity/src/unity.h"
#include "message_builder.h" // Stellt sicher, dass deine Header-Datei message_parser.h die Definitionen für build_message, StartByte, EscapeByte, EndByte, MAX_MESSAGE_PAYLOAD_LENGTH und Fehlercodes enthält
#include <stdint.h>
#include <string.h> // Für memcpy, memset
// --- Globale Konstanten (Annahmen aus vorheriger Konversation) ---
// Wenn diese in message_parser.h nicht definiert sind, musst du sie hier
// definieren: #define StartByte 0xAA #define EscapeByte 0xBB #define EndByte
// 0xCC
// #define MAX_MESSAGE_PAYLOAD_LENGTH 250 // Beispiel: Max. Payload-Länge
// Fehlercodes für den Builder (sollten mit deiner build_message Implementierung
// übereinstimmen) #define BUILD_ERROR_BUFFER_TOO_SMALL_INITIAL_CHECK -1 #define
// BUILD_ERROR_BUFFER_OVERFLOW -2 #define PayloadBiggerThenBuffer -3
// --- UNITY SETUP/TEARDOWN ---
void setUp(void) {
// Nichts Besonderes für den Builder-Test zu resetten
}
void tearDown(void) {} // optional
static bool needs_stuffing_byte(uint8_t byte) {
return (byte == StartByte || byte == EscapeByte || byte == EndByte);
}
// --- Hilfsfunktion zur Berechnung des *zu sendenden* Checksummen-Bytes ---
// Dies ist der Wert, der im Frame an der Checksummen-Position steht,
// sodass die finale XOR-Summe der Nutzdaten (MSGID + Payload + dieses Byte)
// 0x00 ergibt.
uint8_t calculate_payload_checksum_byte(uint8_t msgid, const uint8_t *payload,
size_t payload_len) {
uint8_t cs = msgid;
for (size_t i = 0; i < payload_len; ++i) {
cs ^= payload[i];
}
return cs; // Dies ist der Wert, der gesendet werden muss
}
// --- Hilfsfunktion zum Vergleichen von Hex-Arrays und Debug-Ausgabe ---
void TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(const uint8_t *expected,
const uint8_t *actual, size_t len) {
char expected_str[len * 3 + 1];
char actual_str[len * 3 + 1];
int offset_exp = 0;
int offset_act = 0;
for (size_t i = 0; i < len; ++i) {
offset_exp +=
snprintf(expected_str + offset_exp, sizeof(expected_str) - offset_exp,
"%02X ", expected[i]);
offset_act += snprintf(actual_str + offset_act,
sizeof(actual_str) - offset_act, "%02X ", actual[i]);
}
// Stelle sicher, dass die Strings nullterminiert sind, falls der Puffer genau
// gefüllt wurde
if (offset_exp > 0)
expected_str[offset_exp - 1] = '\0';
else
expected_str[0] = '\0'; // Remove last space, null-terminate
if (offset_act > 0)
actual_str[offset_act - 1] = '\0';
else
actual_str[0] = '\0';
printf("\n"); // Neue Zeile für bessere Lesbarkeit
printf(" Expected: %s\n", expected_str);
printf(" Actual: %s\n", actual_str);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, len);
}
// --- TESTFÄLLE FÜR build_message ---
// Test 1: Gültige Nachricht ohne Escaping
void test_builder_1_basic_message_no_escaping(void) {
uint8_t msgid = 0x01;
uint8_t payload[] = {0x10, 0x20, 0x30, 0x40};
size_t payload_len = sizeof(payload);
uint8_t output_buffer[64]; // Ausreichend großer Puffer
size_t output_buffer_size = sizeof(output_buffer);
// Erwarteter Checksummen-Wert
uint8_t expected_checksum =
calculate_payload_checksum_byte(msgid, payload, payload_len);
// Erwartete fertige Nachricht
uint8_t expected_message[] = {
StartByte, msgid, 0x10, 0x20, 0x30, 0x40, // Payload
expected_checksum, EndByte};
size_t expected_len = sizeof(expected_message);
int actual_len = build_message(msgid, payload, payload_len, output_buffer,
output_buffer_size);
TEST_ASSERT_EQUAL_INT(expected_len, actual_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer,
actual_len);
}
// Test 2: Gültige Nachricht mit leerem Payload
void test_builder_2_empty_payload(void) {
uint8_t msgid = 0x02;
uint8_t payload[] = {};
size_t payload_len = sizeof(payload);
uint8_t output_buffer[64];
size_t output_buffer_size = sizeof(output_buffer);
uint8_t expected_checksum =
calculate_payload_checksum_byte(msgid, payload, payload_len);
uint8_t expected_message[] = {StartByte, msgid, expected_checksum, EndByte};
size_t expected_len = sizeof(expected_message);
int actual_len = build_message(msgid, payload, payload_len, output_buffer,
output_buffer_size);
TEST_ASSERT_EQUAL_INT(expected_len, actual_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer,
actual_len);
}
// Test 3: MSGID muss escapet werden (StartByte als MSGID)
void test_builder_3_escaped_msgid(void) {
uint8_t msgid = StartByte; // MSGID = 0xAA
uint8_t payload[] = {0x11, 0x22};
size_t payload_len = sizeof(payload);
uint8_t output_buffer[64];
size_t output_buffer_size = sizeof(output_buffer);
uint8_t expected_checksum =
calculate_payload_checksum_byte(msgid, payload, payload_len);
uint8_t expected_message[] = {StartByte,
EscapeByte,
StartByte, // Escaped MSGID
0x11,
0x22, // Payload
expected_checksum,
EndByte};
size_t expected_len = sizeof(expected_message);
int actual_len = build_message(msgid, payload, payload_len, output_buffer,
output_buffer_size);
TEST_ASSERT_EQUAL_INT(expected_len, actual_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer,
actual_len);
}
// Test 4: Payload-Byte muss escapet werden (EndByte im Payload)
void test_builder_4_escaped_payload_byte(void) {
uint8_t msgid = 0x04;
uint8_t payload[] = {0x01, EndByte, 0x03}; // EndByte = 0xCC im Payload
size_t payload_len = sizeof(payload);
uint8_t output_buffer[64];
size_t output_buffer_size = sizeof(output_buffer);
uint8_t expected_checksum =
calculate_payload_checksum_byte(msgid, payload, payload_len);
uint8_t expected_message[] = {StartByte, msgid,
0x01, EscapeByte,
EndByte, // Escaped payload byte
0x03, expected_checksum,
EndByte};
size_t expected_len = sizeof(expected_message);
int actual_len = build_message(msgid, payload, payload_len, output_buffer,
output_buffer_size);
TEST_ASSERT_EQUAL_INT(expected_len, actual_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer,
actual_len);
}
// Test 5: Checksummen-Byte muss escapet werden (EscapeByte als Checksumme)
void test_builder_5_escaped_checksum_byte(void) {
uint8_t msgid = 0x05;
// Payload so wählen, dass msgid ^ payload[0] = EscapeByte (0xBB)
uint8_t payload[] = {msgid ^ EscapeByte}; // Payload ist 0x05 ^ 0xBB = 0xBE
size_t payload_len = sizeof(payload);
uint8_t output_buffer[64];
size_t output_buffer_size = sizeof(output_buffer);
uint8_t expected_checksum =
calculate_payload_checksum_byte(msgid, payload, payload_len);
TEST_ASSERT_EQUAL_UINT8(EscapeByte, expected_checksum); // Sanity check
uint8_t expected_message[] = {StartByte,
msgid,
payload[0],
EscapeByte,
expected_checksum, // Escaped checksum byte
EndByte};
size_t expected_len = sizeof(expected_message);
int actual_len = build_message(msgid, payload, payload_len, output_buffer,
output_buffer_size);
TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer,
actual_len);
TEST_ASSERT_EQUAL_INT(expected_len, actual_len);
}
// Test 6: Mehrere Escapings in einer Nachricht
void test_builder_6_multiple_escapings(void) {
uint8_t msgid = StartByte; // 0xAA
uint8_t payload[] = {EscapeByte, 0x01, EndByte, StartByte, 0x02};
size_t payload_len = sizeof(payload);
uint8_t output_buffer[64];
size_t output_buffer_size = sizeof(output_buffer);
uint8_t expected_checksum =
calculate_payload_checksum_byte(msgid, payload, payload_len);
// Angenommen, die berechnete Checksumme selbst ist KEIN Steuerzeichen,
// sonst müsste sie auch escapet werden.
// Wenn doch, würde expected_message noch ein EscapeByte vor der Checksumme
// bekommen.
uint8_t expected_message[] = {
StartByte, EscapeByte, StartByte, // Escaped MSGID
EscapeByte, EscapeByte, // Escaped payload[0]
0x01, EscapeByte, EndByte, // Escaped payload[2]
EscapeByte, StartByte, // Escaped payload[3]
0x02, expected_checksum, EndByte};
size_t expected_len = sizeof(expected_message);
int actual_len = build_message(msgid, payload, payload_len, output_buffer,
output_buffer_size);
TEST_ASSERT_EQUAL_INT(expected_len, actual_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer,
actual_len);
}
// Test 7: Puffer zu klein - initiale Prüfung (Payload ist zu lang für den
// Puffer)
void test_builder_7_buffer_too_small_initial(void) {
uint8_t msgid = 0x07;
// Payload, das auch im besten Fall (ohne Stuffing) nicht in einen
// 10-Byte-Puffer passt. Minimale Länge wäre 1+1+5+1+1 = 9 Bytes (Start, ID,
// Payload, CRC, End)
uint8_t payload[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
size_t payload_len = sizeof(payload); // 8 Bytes
uint8_t output_buffer[10]; // Ein Puffer, der zu klein ist für 8 Payload-Bytes
// + Rahmung + CRC
size_t output_buffer_size = sizeof(output_buffer); // 10 Bytes
int actual_len = build_message(msgid, payload, payload_len, output_buffer,
output_buffer_size);
// Die Berechnung für PayloadBiggerThenBuffer sollte hier greifen.
// payload_len * 2 + 5 (worst case) = 8*2+5 = 21. 21 ist > 10.
TEST_ASSERT_EQUAL_INT(PayloadBiggerThenBuffer, actual_len);
}
// Test 8: Puffer zu klein - Überlauf beim Schreiben (knapper Puffer, z.B. bei
// Stuffing)
void test_builder_8_buffer_overflow_during_build(void) {
uint8_t msgid = 0x08;
// Payload so wählen, dass Stuffing stattfindet
uint8_t payload[] = {0x01, StartByte, 0x02}; // StartByte im Payload
size_t payload_len = sizeof(payload); // 3 Bytes
// Minimaler Puffer für diese Nachricht OHNE Stuffing:
// Start (1) + MSGID (1) + Payload (3) + CRC (1) + End (1) = 7 Bytes
// Mit Stuffing für payload[1] (StartByte) wird es:
// 1 (Start) + 1 (MSGID) + 1 (0x01) + 2 (Escape+StartByte) + 1 (0x02) + 1
// (CRC) + 1 (End) = 8 Bytes
uint8_t output_buffer[7]; // Puffer ist 1 Byte zu klein für die gestuffte
// Nachricht
size_t output_buffer_size = sizeof(output_buffer);
int actual_len = build_message(msgid, payload, payload_len, output_buffer,
output_buffer_size);
TEST_ASSERT_EQUAL_INT(BufferOverFlow, actual_len);
}
// Test 9: Puffer genau groß genug (Randfall)
void test_builder_9_buffer_just_enough(void) {
uint8_t msgid = 0x09;
uint8_t payload[] = {0x01, 0x02, 0x03, 0x04}; // 4 Bytes
size_t payload_len = sizeof(payload);
uint8_t expected_checksum =
calculate_payload_checksum_byte(msgid, payload, payload_len);
uint8_t expected_message[] = {
StartByte, msgid, 0x01, 0x02, 0x03, 0x04, expected_checksum, EndByte};
size_t expected_len = sizeof(expected_message); // 1 + 1 + 4 + 1 + 1 = 8 Bytes
uint8_t output_buffer[expected_len]; // Puffer GENAU der erwarteten Größe
size_t output_buffer_size = sizeof(output_buffer);
int actual_len = build_message(msgid, payload, payload_len, output_buffer,
output_buffer_size);
TEST_ASSERT_EQUAL_INT(expected_len, actual_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer,
actual_len);
}
// Test 10: Maximale Payload-Länge ohne Stuffing
void test_builder_10_max_payload_no_stuffing(void) {
uint8_t msgid = 0x10;
uint8_t payload[MAX_MESSAGE_PAYLOAD_LENGTH];
for (size_t i = 0; i < MAX_MESSAGE_PAYLOAD_LENGTH; ++i) {
// Sicherstellen, dass keine Steuerzeichen dabei sind
payload[i] = (uint8_t)(i % 0xF0 + 0x01); // Vermeidet 0xAA, 0xBB, 0xCC
}
size_t payload_len = MAX_MESSAGE_PAYLOAD_LENGTH;
// Geschätzte maximale Puffergröße für worst-case (alle Bytes gestuffed)
// 1 (Start) + 1 (MSGID) + MAX_PAYLOAD_LEN*2 (Payload worst-case) + 1 (CRC) +
// 1 (End)
// + 2 (potential stuffing for MSGID/CRC) = 2*MAX_MESSAGE_PAYLOAD_LENGTH + 5
// Aber da wir hier KEIN Stuffing haben, ist es einfacher: 1 + 1 +
// MAX_PAYLOAD_LEN + 1 + 1
size_t max_buffer_needed = 1 + 1 + MAX_MESSAGE_PAYLOAD_LENGTH + 1 + 1;
uint8_t output_buffer[max_buffer_needed + 10]; // Etwas Puffer extra
size_t output_buffer_size = sizeof(output_buffer);
uint8_t expected_checksum =
calculate_payload_checksum_byte(msgid, payload, payload_len);
// Erstelle erwartete Nachricht manuell oder dynamisch
uint8_t expected_message[max_buffer_needed];
size_t exp_idx = 0;
expected_message[exp_idx++] = StartByte;
expected_message[exp_idx++] = msgid;
memcpy(&expected_message[exp_idx], payload, payload_len);
exp_idx += payload_len;
expected_message[exp_idx++] = expected_checksum;
expected_message[exp_idx++] = EndByte;
size_t expected_len = exp_idx;
int actual_len = build_message(msgid, payload, payload_len, output_buffer,
output_buffer_size);
TEST_ASSERT_EQUAL_INT(expected_len, actual_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer,
actual_len);
}
// Test 11: Maximale Payload-Länge mit Stuffing (Worst-Case Szenario)
void test_builder_11_max_payload_with_stuffing(void) {
uint8_t msgid = StartByte; // MSGID muss gestuffed werden
uint8_t payload[MAX_MESSAGE_PAYLOAD_LENGTH];
for (size_t i = 0; i < MAX_MESSAGE_PAYLOAD_LENGTH; ++i) {
// Alle Bytes sind Steuerzeichen, müssen gestuffed werden
payload[i] =
(i % 3 == 0) ? StartByte : ((i % 3 == 1) ? EscapeByte : EndByte);
}
size_t payload_len = MAX_MESSAGE_PAYLOAD_LENGTH;
// Worst-Case Puffergröße:
// 1 (Start) + 2 (Escaped MSGID) + MAX_PAYLOAD_LEN * 2 (Escaped Payload) + 2
// (Escaped CRC) + 1 (End)
size_t max_buffer_needed_worst_case =
1 + 2 + (MAX_MESSAGE_PAYLOAD_LENGTH * 2) + 2 + 1;
uint8_t
output_buffer[max_buffer_needed_worst_case + 10]; // Etwas Puffer extra
size_t output_buffer_size = sizeof(output_buffer);
// Build the expected message manually to account for all stuffing
uint8_t expected_message[max_buffer_needed_worst_case];
size_t exp_idx = 0;
// StartByte
expected_message[exp_idx++] = StartByte;
// MSGID (escaped)
expected_message[exp_idx++] = EscapeByte;
expected_message[exp_idx++] = msgid;
// Payload (all bytes escaped)
for (size_t i = 0; i < payload_len; ++i) {
expected_message[exp_idx++] = EscapeByte;
expected_message[exp_idx++] = payload[i];
}
// Checksumme (kann auch escapet sein)
uint8_t expected_checksum =
calculate_payload_checksum_byte(msgid, payload, payload_len);
if (needs_stuffing_byte(expected_checksum)) {
expected_message[exp_idx++] = EscapeByte;
}
expected_message[exp_idx++] = expected_checksum;
// EndByte
expected_message[exp_idx++] = EndByte;
size_t expected_len = exp_idx;
int actual_len = build_message(msgid, payload, payload_len, output_buffer,
output_buffer_size);
TEST_ASSERT_EQUAL_INT(expected_len, actual_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer,
actual_len);
}
// --- MAIN TEST RUNNER ---
int main(void) {
UNITY_BEGIN();
// Run Builder Tests
RUN_TEST(test_builder_1_basic_message_no_escaping);
RUN_TEST(test_builder_2_empty_payload);
RUN_TEST(test_builder_3_escaped_msgid);
RUN_TEST(test_builder_4_escaped_payload_byte);
RUN_TEST(test_builder_5_escaped_checksum_byte);
RUN_TEST(test_builder_6_multiple_escapings);
RUN_TEST(test_builder_7_buffer_too_small_initial);
RUN_TEST(test_builder_8_buffer_overflow_during_build);
RUN_TEST(test_builder_9_buffer_just_enough);
RUN_TEST(test_builder_10_max_payload_no_stuffing);
RUN_TEST(test_builder_11_max_payload_with_stuffing);
return UNITY_END();
}
+452
View File
@@ -0,0 +1,452 @@
#include "Unity/src/unity.h"
#include "message_parser.h" // Stellt sicher, dass deine Header-Datei message_parser.h korrekt ist
#include <stdint.h>
#include <string.h> // Für memcpy
// Globale Variablen für Callback-Überprüfung
static uint8_t received_msgid = 0xFF;
static uint8_t
received_payload[MAX_TOTAL_CONTENT_LENGTH]; // Muss groß genug sein
static size_t received_payload_len = 0;
static enum ParserError received_error = NoError;
static bool message_received_flag = false;
static bool message_fail_flag = false;
// Mock-Implementierungen für die Callbacks
void mock_on_message_received(uint8_t msgid, const uint8_t *payload,
size_t payload_len) {
received_msgid = msgid;
received_payload_len = payload_len;
// Sicherstellen, dass der Puffer nicht überläuft
memcpy(received_payload, payload,
(payload_len < MAX_TOTAL_CONTENT_LENGTH) ? payload_len
: MAX_TOTAL_CONTENT_LENGTH);
message_received_flag = true;
}
void mock_on_message_fail(uint8_t msgid, const uint8_t *payload,
size_t payload_len, enum ParserError error) {
received_msgid = msgid; // Auch bei Fehlern kann die ID relevant sein
received_payload_len = payload_len;
// Auch hier, um sicherzustellen, dass wir den Zustand des Puffers beim Fehler
// sehen können
memcpy(received_payload, payload,
(payload_len < MAX_TOTAL_CONTENT_LENGTH) ? payload_len
: MAX_TOTAL_CONTENT_LENGTH);
received_error = error;
message_fail_flag = true;
}
// --- UNITY SETUP/TEARDOWN ---
void setUp(void) {
// Reset der globalen Variablen vor jedem Test
received_msgid = 0xFF;
received_payload_len = 0;
received_error = NoError;
message_received_flag = false;
message_fail_flag = false;
memset(received_payload, 0, MAX_TOTAL_CONTENT_LENGTH);
// Registrierung der Mock-Callbacks (muss vor den Tests erfolgen)
register_message_callback(mock_on_message_received);
register_message_fail_callback(mock_on_message_fail);
}
void tearDown(void) {} // optional
// --- Hilfsfunktion zur Checksummenberechnung (für Tests) ---
// Berechnet die Checksumme für MSGID + Payload + Checksummen-Byte
// Ergibt 0x00, wenn die gesamte Kette XORiert wird
uint8_t calculate_test_checksum_final(uint8_t msgid, const uint8_t *payload,
size_t payload_len,
uint8_t actual_checksum_byte) {
uint8_t cs = msgid;
for (size_t i = 0; i < payload_len; ++i) {
cs ^= payload[i];
}
cs ^=
actual_checksum_byte; // Das gesendete Checksummen-Byte wird auch XORiert
return cs;
}
// Hilfsfunktion zur Berechnung des *zu sendenden* Checksummen-Bytes
// Dies ist der Wert, der im Frame an der Checksummen-Position steht,
// sodass die finale XOR-Summe der Nutzdaten (MSGID + Payload + dieses Byte)
// 0x00 ergibt.
uint8_t calculate_payload_checksum_byte(uint8_t msgid, const uint8_t *payload,
size_t payload_len) {
uint8_t cs = msgid;
for (size_t i = 0; i < payload_len; ++i) {
cs ^= payload[i];
}
return cs; // Dies ist der Wert, der gesendet werden muss, damit die finale
// XOR-Summe 0x00 wird
}
// Test 1: Gültige Nachricht mit Payload
void test_1_valid_message_parses_correctly(void) {
struct MessageReceive mr = InitMessageReceive();
uint8_t msgid = 0x01;
uint8_t payload[] = {0x01, 0x02, 0x03};
size_t payload_len = sizeof(payload);
// Berechne das Checksummen-Byte, das gesendet werden muss
uint8_t checksum_byte_to_send =
calculate_payload_checksum_byte(msgid, payload, payload_len);
uint8_t full_message[] = {
StartByte, // 0xAA
msgid, // 0x01
0x01,
0x02,
0x03, // Payload
checksum_byte_to_send, // Das Checksummen-Byte
EndByte // 0xCC
};
for (uint8_t i = 0; i < sizeof(full_message); ++i) {
parse_byte(&mr, full_message[i]);
}
TEST_ASSERT_TRUE(message_received_flag);
TEST_ASSERT_FALSE(message_fail_flag);
TEST_ASSERT_EQUAL_UINT8(msgid, received_msgid);
TEST_ASSERT_EQUAL_UINT8(payload_len, received_payload_len); // Payload-Länge
TEST_ASSERT_EQUAL_UINT8_ARRAY(payload, received_payload, payload_len);
TEST_ASSERT_EQUAL_UINT8(WaitingForStartByte, mr.state);
TEST_ASSERT_EQUAL_UINT8(NoError, mr.error);
}
// Test 2: Ungültige Checksumme Fehler gemeldet und Zustand zurückgesetzt
void test_2_invalid_checksum_resets_state(void) {
struct MessageReceive mr = InitMessageReceive();
uint8_t msgid = 0x02;
uint8_t payload[] = {0x10, 0x20};
size_t payload_len = sizeof(payload);
uint8_t wrong_checksum_byte = 0x01; // Absichtlich falsche Checksumme
uint8_t full_message[] = {StartByte,
msgid,
0x10,
0x20,
wrong_checksum_byte, // Falsches Checksummen-Byte
EndByte};
for (uint8_t i = 0; i < sizeof(full_message); ++i) {
parse_byte(&mr, full_message[i]);
}
TEST_ASSERT_FALSE(message_received_flag);
TEST_ASSERT_TRUE(message_fail_flag);
TEST_ASSERT_EQUAL_UINT8(msgid, received_msgid);
// received_payload_len sollte die Länge der Daten sein, die bis zum Fehler
// empfangen wurden, d.h., Payload-Länge + das falsche Checksummen-Byte.
TEST_ASSERT_EQUAL_UINT8(payload_len + 1, received_payload_len);
TEST_ASSERT_EQUAL_UINT8(WrongCheckSum, received_error);
TEST_ASSERT_EQUAL_UINT8(WaitingForStartByte, mr.state);
TEST_ASSERT_EQUAL_UINT8(WrongCheckSum, mr.error);
}
// Test 3: Gültige Nachricht ohne Payload (Länge 0)
void test_3_zero_length_message(void) {
struct MessageReceive mr = InitMessageReceive();
uint8_t msgid = 0x03;
uint8_t payload[] = {}; // Leerer Payload
size_t payload_len = sizeof(payload);
uint8_t checksum_byte_to_send =
calculate_payload_checksum_byte(msgid, payload, payload_len);
uint8_t full_message[] = {
StartByte, msgid,
checksum_byte_to_send, // Checksummen-Byte (hier gleich MSGID, da Payload
// leer)
EndByte};
for (uint8_t i = 0; i < sizeof(full_message); ++i) {
parse_byte(&mr, full_message[i]);
}
TEST_ASSERT_TRUE(message_received_flag);
TEST_ASSERT_FALSE(message_fail_flag);
TEST_ASSERT_EQUAL_UINT8(msgid, received_msgid);
TEST_ASSERT_EQUAL_UINT8(0, received_payload_len); // Payload-Länge ist 0
TEST_ASSERT_EQUAL_UINT8(WaitingForStartByte, mr.state);
TEST_ASSERT_EQUAL_UINT8(NoError, mr.error);
}
// Test 4: Escapete MSGID (0xAA im MSGID-Feld)
void test_4_escaped_message_id(void) {
struct MessageReceive mr = InitMessageReceive();
uint8_t msgid = 0xAA; // MSGID ist ein Steuerzeichen, muss escapet werden
uint8_t payload[] = {0x42};
size_t payload_len = sizeof(payload);
uint8_t checksum_byte_to_send =
calculate_payload_checksum_byte(msgid, payload, payload_len);
uint8_t full_message[] = {StartByte,
EscapeByte,
msgid, // Escapete MSGID (0xBB 0xAA)
0x42, // Payload
checksum_byte_to_send,
EndByte};
for (uint8_t i = 0; i < sizeof(full_message); ++i) {
parse_byte(&mr, full_message[i]);
}
TEST_ASSERT_TRUE(message_received_flag);
TEST_ASSERT_FALSE(message_fail_flag);
TEST_ASSERT_EQUAL_UINT8(msgid, received_msgid);
TEST_ASSERT_EQUAL_UINT8(payload_len, received_payload_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY(payload, received_payload, payload_len);
TEST_ASSERT_EQUAL_UINT8(WaitingForStartByte, mr.state);
TEST_ASSERT_EQUAL_UINT8(NoError, mr.error);
}
// Test 5: Escapetes Payload-Byte (z.B. ein StartByte im Payload)
void test_5_escaped_payload_byte(void) {
struct MessageReceive mr = InitMessageReceive();
uint8_t msgid = 0x05;
uint8_t payload[] = {
0x11, StartByte}; // StartByte (0xAA) im Payload, muss escapet werden
size_t payload_len = sizeof(payload);
uint8_t checksum_byte_to_send =
calculate_payload_checksum_byte(msgid, payload, payload_len);
uint8_t full_message[] = {
StartByte,
msgid,
0x11,
EscapeByte,
StartByte, // Escaptes StartByte (0xBB 0xAA) im Payload
checksum_byte_to_send,
EndByte};
for (uint8_t i = 0; i < sizeof(full_message); ++i) {
parse_byte(&mr, full_message[i]);
}
TEST_ASSERT_TRUE(message_received_flag);
TEST_ASSERT_FALSE(message_fail_flag);
TEST_ASSERT_EQUAL_UINT8(msgid, received_msgid);
TEST_ASSERT_EQUAL_UINT8(payload_len, received_payload_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY(payload, received_payload, payload_len);
TEST_ASSERT_EQUAL_UINT8(WaitingForStartByte, mr.state);
TEST_ASSERT_EQUAL_UINT8(NoError, mr.error);
}
// Test 6: Escapetes Checksummen-Byte (z.B. ein EndByte als Checksumme)
void test_6_escaped_checksum_byte(void) {
struct MessageReceive mr = InitMessageReceive();
uint8_t msgid = 0x01;
uint8_t payload[] = {
0xCD}; // payload[0] ^ msgid = 0xCD ^ 0x01 = 0xCC (EndByte)
size_t payload_len = sizeof(payload);
uint8_t checksum_byte_to_send = calculate_payload_checksum_byte(
msgid, payload, payload_len); // Dies ist 0xCC
uint8_t full_message[] = {
StartByte,
msgid,
payload[0], // Payload
EscapeByte,
checksum_byte_to_send, // Escaptes 0xCC als Checksummen-Byte (0xBB 0xCC)
EndByte};
for (uint8_t i = 0; i < sizeof(full_message); ++i) {
parse_byte(&mr, full_message[i]);
}
TEST_ASSERT_TRUE(message_received_flag);
TEST_ASSERT_FALSE(message_fail_flag);
TEST_ASSERT_EQUAL_UINT8(msgid, received_msgid);
TEST_ASSERT_EQUAL_UINT8(payload_len, received_payload_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY(payload, received_payload, payload_len);
TEST_ASSERT_EQUAL_UINT8(WaitingForStartByte, mr.state);
TEST_ASSERT_EQUAL_UINT8(NoError, mr.error);
}
// Test 7: Nachricht zu lang (Pufferüberlauf)
void test_7_message_too_long(void) {
struct MessageReceive mr =
InitMessageReceive(); // mr.max_total_content_length wird auf
// MAX_TOTAL_CONTENT_LENGTH gesetzt
uint8_t msgid = 0x07;
// Dieser Payload ist absichtlich 1 Byte zu lang für
// MAX_MESSAGE_PAYLOAD_LENGTH. D.h., der gesamte Inhalt für mr.message[]
// (Payload + Checksumme) ist MAX_TOTAL_CONTENT_LENGTH + 1 Bytes lang. Dadurch
// wird der Puffer definitiv überlaufen.
uint8_t oversized_data_for_buffer[MAX_TOTAL_CONTENT_LENGTH +
1]; // Ein Byte zu viel für mr.message[]
for (size_t i = 0; i < sizeof(oversized_data_for_buffer); ++i) {
oversized_data_for_buffer[i] = (uint8_t)(i + 1); // Beliebige Daten
}
// Wir brauchen eine korrekte Checksumme, auch wenn die Nachricht zu lang ist,
// da der Sender diese theoretisch senden würde.
// Die Berechnung erfolgt über die tatsächlich gesendeten Payload-Daten (die
// zu lang sind).
uint8_t checksum_byte_for_oversized_msg = calculate_payload_checksum_byte(
msgid, oversized_data_for_buffer, MAX_MESSAGE_PAYLOAD_LENGTH + 1);
// Simuliere den Versand der Nachricht Byte für Byte
parse_byte(&mr, StartByte); // mr.state = GetMessageType
parse_byte(&mr, msgid); // mr.state = InPayload, mr.messageid = 0x07
// Sende MAX_TOTAL_CONTENT_LENGTH Bytes, die den Puffer `mr.message`
// vollständig füllen. Index läuft von 0 bis MAX_TOTAL_CONTENT_LENGTH-1.
for (size_t i = 0; i < MAX_TOTAL_CONTENT_LENGTH; ++i) {
// Hier schicken wir die ersten MAX_TOTAL_CONTENT_LENGTH Bytes des
// übergroßen Payloads (inklusive dem eigentlichen Checksummen-Byte an
// Position MAX_MESSAGE_PAYLOAD_LENGTH). Das wird den Puffer anfüllen, aber
// noch keinen Overflow melden.
parse_byte(&mr, oversized_data_for_buffer[i]);
}
// An diesem Punkt sollte mr.index = MAX_TOTAL_CONTENT_LENGTH sein.
// Der Puffer `mr.message` ist jetzt voll.
TEST_ASSERT_EQUAL_UINT8(MAX_TOTAL_CONTENT_LENGTH,
mr.index); // Der Puffer ist genau gefüllt.
TEST_ASSERT_EQUAL_UINT8(InPayload, mr.state); // Noch im Payload-Zustand.
// Das nächste Byte (das letzte Byte von oversized_data_for_buffer)
// wird den Overflow auslösen, da mr->index dann mr->max_total_content_length
// überschreitet.
parse_byte(&mr, oversized_data_for_buffer[MAX_TOTAL_CONTENT_LENGTH]);
TEST_ASSERT_FALSE(message_received_flag);
TEST_ASSERT_TRUE(message_fail_flag);
TEST_ASSERT_EQUAL_UINT8(msgid, received_msgid); // MSGID ist korrekt gesetzt
// received_payload_len sollte die max. Puffergröße sein, bis der Fehler
// auftrat
TEST_ASSERT_EQUAL_UINT8(MAX_TOTAL_CONTENT_LENGTH, received_payload_len);
TEST_ASSERT_EQUAL_UINT8(MessageToLong, received_error);
TEST_ASSERT_EQUAL_UINT8(WaitingForStartByte, mr.state);
TEST_ASSERT_EQUAL_UINT8(MessageToLong, mr.error);
}
// Test 8: Unerwartetes StartByte mitten im Frame
void test_8_unexpected_start_byte_in_payload(void) {
struct MessageReceive mr = InitMessageReceive();
uint8_t msgid = 0x08;
uint8_t full_message[] = {
StartByte, msgid,
0x10, // Teil des Payloads
StartByte, // Unerwartetes StartByte mitten im Payload
0x20, // Dies würde danach kommen
0x00, // Dummy-Checksumme
EndByte // Dummy-EndByte
};
parse_byte(&mr, full_message[0]); // StartByte
parse_byte(&mr, full_message[1]); // MSGID
parse_byte(&mr, full_message[2]); // Payload 0x10
// Hier kommt das unerwartete StartByte, sollte den Fehler auslösen und
// resetten
parse_byte(&mr, full_message[3]);
TEST_ASSERT_FALSE(message_received_flag);
TEST_ASSERT_TRUE(message_fail_flag);
TEST_ASSERT_EQUAL_UINT8(msgid, received_msgid);
// received_payload_len sollte die Länge der Daten sein, die vor dem Fehler
// empfangen wurden.
TEST_ASSERT_EQUAL_UINT8(1, received_payload_len); // Nur 0x10 empfangen
TEST_ASSERT_EQUAL_UINT8_ARRAY(((uint8_t[]){0x10}), received_payload, 1);
TEST_ASSERT_EQUAL_UINT8(UnexpectedCommandByte, received_error);
TEST_ASSERT_EQUAL_UINT8(WaitingForStartByte, mr.state);
TEST_ASSERT_EQUAL_UINT8(UnexpectedCommandByte, mr.error);
}
// Test 9: Unerwartetes EndByte an der Position der MSGID
void test_9_unexpected_end_byte_at_msgid(void) {
struct MessageReceive mr = InitMessageReceive();
uint8_t full_message[] = {StartByte, EndByte, // EndByte anstelle von MSGID
0x01, 0x02, 0x03, 0x04, EndByte};
parse_byte(&mr, full_message[0]); // StartByte
parse_byte(&mr, full_message[1]); // EndByte anstelle MSGID
TEST_ASSERT_FALSE(message_received_flag);
TEST_ASSERT_TRUE(message_fail_flag);
TEST_ASSERT_EQUAL_UINT8(
0x00, received_msgid); // MSGID ist noch 0, da keine empfangen
TEST_ASSERT_EQUAL_UINT8(0, received_payload_len); // Noch kein Payload
TEST_ASSERT_EQUAL_UINT8(UnexpectedCommandByte, received_error);
TEST_ASSERT_EQUAL_UINT8(WaitingForStartByte, mr.state);
TEST_ASSERT_EQUAL_UINT8(UnexpectedCommandByte, mr.error);
}
// Test 10: Kein StartByte zu Beginn der Sequenz (Parser sollte ignorieren)
void test_10_no_startbyte_at_beginning_ignored(void) {
struct MessageReceive mr = InitMessageReceive();
uint8_t msg[] = {0x01, 0x02, 0x03, 0x04}; // Beginnt nicht mit StartByte
for (uint8_t i = 0; i < sizeof(msg); ++i) {
parse_byte(&mr, msg[i]);
}
TEST_ASSERT_FALSE(message_received_flag);
TEST_ASSERT_FALSE(
message_fail_flag); // Sollte keinen Fehler melden, nur ignorieren
TEST_ASSERT_EQUAL_UINT8(WaitingForStartByte,
mr.state); // Sollte im Wartezustand bleiben
TEST_ASSERT_EQUAL_UINT8(0, mr.index); // Index sollte 0 bleiben
TEST_ASSERT_EQUAL_UINT8(NoError, mr.error); // Kein Fehler gemeldet
}
// Test 11: Ungültige Länge (z.B. EndByte kommt zu früh, ohne Checksumme)
// Angenommen, das Protokoll erwartet immer mindestens MSGID + Checksumme.
// Ein leeres Payload ist OK (MSGID + Checksumme + EndByte), aber nur MSGID +
// EndByte ist Fehler.
void test_11_frame_too_short_no_checksum(void) {
struct MessageReceive mr = InitMessageReceive();
uint8_t msgid = 0x09;
uint8_t full_message[] = {
StartByte, msgid, EndByte // Kein Payload, keine Checksumme
};
for (uint8_t i = 0; i < sizeof(full_message); ++i) {
parse_byte(&mr, full_message[i]);
}
TEST_ASSERT_FALSE(message_received_flag);
TEST_ASSERT_TRUE(message_fail_flag);
TEST_ASSERT_EQUAL_UINT8(msgid, received_msgid); // MSGID ist bekannt
TEST_ASSERT_EQUAL_UINT8(0, received_payload_len); // Payload-Puffer ist leer
TEST_ASSERT_EQUAL_UINT8(WrongCheckSum,
received_error); // Checksumme kann nicht 0x00 sein
TEST_ASSERT_EQUAL_UINT8(WaitingForStartByte, mr.state);
TEST_ASSERT_EQUAL_UINT8(WrongCheckSum, mr.error);
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_1_valid_message_parses_correctly);
RUN_TEST(test_2_invalid_checksum_resets_state);
RUN_TEST(test_3_zero_length_message);
RUN_TEST(test_4_escaped_message_id);
RUN_TEST(test_5_escaped_payload_byte);
RUN_TEST(test_6_escaped_checksum_byte);
RUN_TEST(test_7_message_too_long);
RUN_TEST(test_8_unexpected_start_byte_in_payload);
RUN_TEST(test_9_unexpected_end_byte_at_msgid);
RUN_TEST(test_10_no_startbyte_at_beginning_ignored);
RUN_TEST(test_11_frame_too_short_no_checksum);
return UNITY_END();
}