mirror of
https://github.com/skinnyrad/Lora-Scanner.git
synced 2026-11-01 19:45:59 +01:00
136 lines
3.4 KiB
Go
136 lines
3.4 KiB
Go
// Package frames holds received frames and the devices aggregated from them.
|
|
package frames
|
|
|
|
import (
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/skinnyrad/Lora-Scanner/internal/hub"
|
|
)
|
|
|
|
// DefaultMax is how many frames the ring keeps.
|
|
const DefaultMax = 10000
|
|
|
|
const (
|
|
KindLoRa = "lora" // packet decoded by a Feather
|
|
KindLoRaWAN = "lorawan" // row scraped from a gateway
|
|
KindRaw = "raw" // any serial line
|
|
)
|
|
|
|
type Frame struct {
|
|
ID uint64 `json:"id"`
|
|
Time time.Time `json:"time"`
|
|
Kind string `json:"kind"`
|
|
Source string `json:"source"`
|
|
// Transceiver is the ID of the attached Feather that produced the frame (0 for gateway frames).
|
|
Transceiver int `json:"transceiver"`
|
|
Identifier string `json:"identifier"`
|
|
Freq float64 `json:"freq"`
|
|
RSSI *int `json:"rssi"`
|
|
Info string `json:"info"`
|
|
Raw string `json:"raw"`
|
|
}
|
|
|
|
type Device struct {
|
|
Identifier string `json:"identifier"`
|
|
Freq float64 `json:"freq"`
|
|
RSSI *int `json:"rssi"`
|
|
FirstSeen time.Time `json:"firstSeen"`
|
|
LastSeen time.Time `json:"lastSeen"`
|
|
Frames int `json:"frames"`
|
|
}
|
|
|
|
// Messages broadcast on the hub.
|
|
type FrameMsg struct {
|
|
Type string `json:"type"` // "frame"
|
|
Frame Frame `json:"frame"`
|
|
}
|
|
|
|
type DeviceMsg struct {
|
|
Type string `json:"type"` // "device"
|
|
Device Device `json:"device"`
|
|
}
|
|
|
|
type Store struct {
|
|
mu sync.RWMutex
|
|
hub *hub.Hub
|
|
max int
|
|
nextID uint64
|
|
frames []Frame
|
|
devices map[string]*Device
|
|
}
|
|
|
|
// NewStore keeps at most max frames (DefaultMax if <= 0). hub may be nil.
|
|
func NewStore(h *hub.Hub, max int) *Store {
|
|
if max <= 0 {
|
|
max = DefaultMax
|
|
}
|
|
return &Store{hub: h, max: max, nextID: 1, devices: map[string]*Device{}}
|
|
}
|
|
|
|
// Add assigns the ID, stores the frame, updates its device (non-raw only)
|
|
// and publishes both to the hub. A zero Time is set to now.
|
|
func (s *Store) Add(f Frame) Frame {
|
|
if f.Time.IsZero() {
|
|
f.Time = time.Now()
|
|
}
|
|
s.mu.Lock()
|
|
f.ID = s.nextID
|
|
s.nextID++
|
|
s.frames = append(s.frames, f)
|
|
if len(s.frames) > s.max {
|
|
s.frames = append([]Frame(nil), s.frames[len(s.frames)-s.max:]...)
|
|
}
|
|
var dev *Device
|
|
if f.Kind != KindRaw && f.Identifier != "" {
|
|
d, ok := s.devices[f.Identifier]
|
|
if !ok {
|
|
d = &Device{Identifier: f.Identifier, FirstSeen: f.Time}
|
|
s.devices[f.Identifier] = d
|
|
}
|
|
d.Freq, d.RSSI, d.LastSeen = f.Freq, f.RSSI, f.Time
|
|
d.Frames++
|
|
cp := *d
|
|
dev = &cp
|
|
}
|
|
s.mu.Unlock()
|
|
|
|
if s.hub != nil {
|
|
s.hub.Broadcast(FrameMsg{Type: "frame", Frame: f})
|
|
if dev != nil {
|
|
s.hub.Broadcast(DeviceMsg{Type: "device", Device: *dev})
|
|
}
|
|
}
|
|
return f
|
|
}
|
|
|
|
// Since returns frames with ID > after, oldest first.
|
|
func (s *Store) Since(after uint64) []Frame {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
i := sort.Search(len(s.frames), func(i int) bool { return s.frames[i].ID > after })
|
|
return append([]Frame{}, s.frames[i:]...)
|
|
}
|
|
|
|
// Devices returns a copy sorted by identifier.
|
|
func (s *Store) Devices() []Device {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
out := make([]Device, 0, len(s.devices))
|
|
for _, d := range s.devices {
|
|
out = append(out, *d)
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].Identifier < out[j].Identifier })
|
|
return out
|
|
}
|
|
|
|
// Clear drops all frames and devices. IDs keep increasing so clients that
|
|
// resume with ?after= never see a repeated ID.
|
|
func (s *Store) Clear() {
|
|
s.mu.Lock()
|
|
s.frames = nil
|
|
s.devices = map[string]*Device{}
|
|
s.mu.Unlock()
|
|
}
|