From edade53b065236553fab13393e3d699f11af56f0 Mon Sep 17 00:00:00 2001 From: Darell Tan Date: Sun, 21 May 2023 02:20:37 +0800 Subject: [PATCH] Record & persist device last_seen time This will allow us to mark devices as non-responsive if they haven't been seen in some time. --- bridge.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/bridge.go b/bridge.go index b57625a..768faf3 100644 --- a/bridge.go +++ b/bridge.go @@ -32,6 +32,9 @@ const ( // timeout for UpdateZ2MState Z2M_UPDATE_TIMEOUT = 3 * time.Second + + // timeout for marking devices as non-responsive + Z2M_LAST_SEEN_TIMEOUT = 24 * time.Hour ) const BRIDGE_DEBUG = false @@ -64,6 +67,7 @@ type BridgeDevice struct { Device *Device Accessory *accessory.A Mappings map[string]*ExposeMapping + LastSeen time.Time } // Creates and initializes a Bridge. @@ -179,6 +183,25 @@ func (br *Bridge) loadZ2MState(m *sync.Map) error { } for k, v := range stateMap { + // add a last_seen timestamp for saved states without one + var devState map[string]any + err = json.Unmarshal(v, &devState) + if err != nil { + log.Printf("cannot unmarshal device %s JSON: %v", k, err) + continue + } + if _, hasLastSeen := devState["last_seen"]; !hasLastSeen { + devState["last_seen"] = time.Now().Add(-Z2M_LAST_SEEN_TIMEOUT) + + // re-marshal the JSON + newJson, err := json.Marshal(devState) + if err == nil { + v = newJson + } else { + log.Printf("cannot re-marshal JSON state after adding last_seen for %s: %v", k, err) + } + } + // LoadOrStore retains existing data, only storing if empty if _, exists := m.LoadOrStore(k, v); exists { log.Printf("skipping %s, newer data is available", k) @@ -211,7 +234,10 @@ func (br *Bridge) saveZ2MState() error { } // serialize into JSON - if len(devState) > 0 { + lastSeenSince := time.Since(dev.LastSeen) + if len(devState) > 0 || lastSeenSince < Z2M_LAST_SEEN_TIMEOUT { + devState["last_seen"] = dev.LastSeen.Unix() * 1000 // timestamp in millis + jsonState, err := json.Marshal(devState) if err != nil { return err @@ -366,6 +392,8 @@ func (br *Bridge) AddDevice(dev *Device, acc *accessory.A, mappings []*ExposeMap em[prop] = m } + brdev := &BridgeDevice{dev, acc, em, time.Date(2000, 01, 01, 23, 59, 00, 0, time.Local)} + // wire up accessory's remote value update functions for _, m := range mappings { m := m @@ -384,7 +412,7 @@ func (br *Bridge) AddDevice(dev *Device, acc *accessory.A, mappings []*ExposeMap } } - br.devices[name] = &BridgeDevice{dev, acc, em} + br.devices[name] = brdev return nil } @@ -482,6 +510,28 @@ func (br *Bridge) UpdateAccessoryState(devName string, payload []byte) { return } + lastSeen := time.Now() + if lastSeenProp, found := newState["last_seen"]; found { + switch v := lastSeenProp.(type) { + case float64: + lastSeen = time.Unix(int64(v/1000), 0) + case string: + if lastSeenDate, err := time.Parse(time.RFC3339, v); err == nil { + lastSeen = lastSeenDate + } else { + log.Printf("invalid last_seen timestamp %v", v) + } + default: + log.Printf("invalid last_seen %T %[1]v", v) + } + } + + // update LastSeen only if it was valid + if lastSeen.After(dev.LastSeen) { + //log.Printf("updating last seen for %s to %s", devName, lastSeen) + dev.LastSeen = lastSeen + } + for prop, mapping := range dev.Mappings { newVal, exists := newState[prop] if !exists {