diff --git a/scripts/daq/heartbeat.go b/scripts/daq/heartbeat.go new file mode 100644 index 000000000..3e9629d71 --- /dev/null +++ b/scripts/daq/heartbeat.go @@ -0,0 +1,94 @@ +package main + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "net" + "net/http" + "os" + "time" + + "go.uber.org/zap" +) + +type HeartbeatHandler struct { + serverURL string + vehicleID string + sessionID string + can0 net.Conn + can1 net.Conn + logger *zap.Logger +} + +func NewHeartbeatHandler(can0, can1 net.Conn, logger *zap.Logger) *HeartbeatHandler { + + // Configuring Vehicle ID + vehicleID := os.Getenv("VEHICLE_ID") + if vehicleID == "" { + logger.Error("VEHICLE_ID not found, using default.") + vehicleID = "default" + } + + // Generating Session ID + sessionBytes := make([]byte, 16) + _, err := rand.Read(sessionBytes) + var sessionID string + if err != nil { + logger.Warn("Failed to generate session ID, defaulting to time based session ID.") + sessionID = fmt.Sprintf("fallback-%d", time.Now().UnixNano()) + } else { + sessionID = hex.EncodeToString(sessionBytes) + } + + serverURL := os.Getenv("SERVER_URL") + if serverURL == "" { + logger.Error("SERVER_URL for heartbeatnot found") + } + + return &HeartbeatHandler{ + serverURL: serverURL, + vehicleID: vehicleID, + sessionID: sessionID, + can0: can0, + can1: can1, + logger: logger, + } +} + +func (h *HeartbeatHandler) SendHeartbeat() error { + can0Active, can1Active := h.checkCAN() + + payload := map[string]interface{}{ + "timestamp": time.Now().UnixMilli(), + "vehicle_id": h.vehicleID, + "session_id": h.sessionID, + "can_status": map[string]bool{ + "can0": can0Active, + "can1": can1Active, + }, + } + + jsonPayload, err := json.Marshal(payload) + if err != nil { + h.logger.Error("Failed to convert heartbeat payload to JSON", zap.Error(err)) + return err + } + + response, err := http.Post(h.serverURL, "application/json", bytes.NewBuffer(jsonPayload)) + if err != nil { + h.logger.Error("Failed to send heartbeat", zap.Error(err)) + return err + } + defer response.Body.Close() + + return nil +} + +func (h *HeartbeatHandler) checkCAN() (bool, bool) { + can0Active := h.can0 != nil + can1Active := h.can1 != nil + return can0Active, can1Active +} diff --git a/scripts/daq/main.go b/scripts/daq/main.go index 08b55e115..49fc945d1 100644 --- a/scripts/daq/main.go +++ b/scripts/daq/main.go @@ -24,6 +24,8 @@ func main() { logger, _ := zap.NewDevelopment() + heartbeat := NewHeartbeatHandler(can0, can1, logger) + telemetry, err := NewTelemetryHandler("./can_cache.sqlite") if err != nil { panic(err) @@ -41,6 +43,10 @@ func main() { manager1.Start(context.Background()) uploadTimer := time.NewTimer(time.Second) + + heartbeatInterval := time.NewTicker(3 * time.Second) + defer heartbeatInterval.Stop() + for { select { case <-uploadTimer.C: @@ -48,6 +54,11 @@ func main() { if err != nil { fmt.Printf("failed to upload telemetry data: %v\n", err) } + case <-heartbeatInterval.C: + err = heartbeat.SendHeartbeat() + if err != nil { + logger.Error("Failed to send heartbeat", zap.Error(err)) + } } } }