generated from CubeCraft-Creations/Tracehound
registration: self-assigned camera IDs (Option B) + tolerate clockless status
Auto-registration never completed: the firmware announced on the wrong topic, the hub never replied, and an unregistered node couldn't receive a reply anyway. Switch to self-assigned IDs: firmware (esp32-mqtt-bridge.cpp): - camera_id defaults to the device id (clientID, e.g. rig-86d978) - always subscribe to <id>/command; announce on the contract topic remoterig/cameras/<id>/announce (was the unmatched announce-<id> form) - drop the bogus numeric timestamp from status (node has no clock) hub (subscriber.go): - handleAnnounce registers new cameras under the node's self-assigned id (no cam-NNN, no registered reply) - handleStatus tolerates an empty/invalid timestamp and stamps server-side (previously rejected the status outright) docs/MQTT_CONTRACT.md updated to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+11
-23
@@ -151,22 +151,20 @@ func (s *Subscriber) handleStatus(cameraID string, payload []byte) {
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if sp.CameraID == "" || sp.Timestamp == "" {
|
||||
log.Printf("MQTT status missing required fields (camera_id, timestamp) from %s", cameraID)
|
||||
if sp.CameraID == "" {
|
||||
log.Printf("MQTT status missing camera_id from %s", cameraID)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate timestamp sanity (reject >5min future, >24h past)
|
||||
// Nodes have no real clock, so tolerate an empty/invalid timestamp by
|
||||
// stamping server-side. Still clamp obviously-bad supplied times below.
|
||||
now := time.Now()
|
||||
ts, err := time.Parse(time.RFC3339, sp.Timestamp)
|
||||
if err != nil {
|
||||
// Try ISO8601 without timezone
|
||||
ts, err = time.Parse("2006-01-02T15:04:05", sp.Timestamp)
|
||||
if err != nil {
|
||||
log.Printf("MQTT status invalid timestamp %q from %s", sp.Timestamp, cameraID)
|
||||
return
|
||||
if ts, err = time.Parse("2006-01-02T15:04:05", sp.Timestamp); err != nil {
|
||||
ts = now
|
||||
}
|
||||
}
|
||||
now := time.Now()
|
||||
if ts.After(now.Add(5 * time.Minute)) {
|
||||
log.Printf("MQTT status timestamp too far in future (%s) from %s — using now", ts, cameraID)
|
||||
ts = now
|
||||
@@ -374,30 +372,20 @@ func (s *Subscriber) handleAnnounce(cameraID string, payload []byte) {
|
||||
}
|
||||
log.Printf("MQTT announce: camera %s (%s) re-connected", existingID, ap.FriendlyName)
|
||||
} else {
|
||||
// New camera — generate sequential cam-NNN ID
|
||||
var maxID string
|
||||
s.db.QueryRow("SELECT MAX(camera_id) FROM cameras").Scan(&maxID)
|
||||
|
||||
seq := 1
|
||||
if maxID != "" {
|
||||
fmt.Sscanf(maxID, "cam-%d", &seq)
|
||||
seq++
|
||||
}
|
||||
|
||||
newID := fmt.Sprintf("cam-%03d", seq)
|
||||
// Option B: the node self-assigns its camera_id (the announce topic id).
|
||||
_, err = s.db.Exec(`
|
||||
INSERT INTO cameras (camera_id, friendly_name, mac_address, created_at, updated_at)
|
||||
VALUES (?, ?, ?, datetime('now'), datetime('now'))
|
||||
`, newID, ap.FriendlyName, ap.MacAddress)
|
||||
`, cameraID, ap.FriendlyName, ap.MacAddress)
|
||||
if err != nil {
|
||||
log.Printf("MQTT announce insert error for %s: %v", ap.MacAddress, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("MQTT announce: new camera registered as %s (%s)", newID, ap.FriendlyName)
|
||||
log.Printf("MQTT announce: new camera registered as %s (%s)", cameraID, ap.FriendlyName)
|
||||
|
||||
// Broadcast new camera via SSE
|
||||
cam, err := getCamera(s.db, newID)
|
||||
cam, err := getCamera(s.db, cameraID)
|
||||
if err == nil {
|
||||
s.hub.Broadcast("camera_registered", cam)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user