| @@ -19,7 +19,7 @@ type DecoderApp struct { | |||||
| KafkaManager *kafkaclient.KafkaManager | KafkaManager *kafkaclient.KafkaManager | ||||
| AppState *appcontext.AppState | AppState *appcontext.AppState | ||||
| ParserRegistry *model.ParserRegistry | ParserRegistry *model.ParserRegistry | ||||
| ChRaw chan model.BeaconAdvertisement | |||||
| ChRaw chan appcontext.BeaconAdvertisement | |||||
| ChParser chan model.KafkaParser | ChParser chan model.KafkaParser | ||||
| Cleanup func() | Cleanup func() | ||||
| wg sync.WaitGroup | wg sync.WaitGroup | ||||
| @@ -48,7 +48,7 @@ func New(cfg *config.Config) (*DecoderApp, error) { | |||||
| KafkaManager: kafkaManager, | KafkaManager: kafkaManager, | ||||
| AppState: appState, | AppState: appState, | ||||
| ParserRegistry: registry, | ParserRegistry: registry, | ||||
| ChRaw: make(chan model.BeaconAdvertisement, config.LARGE_CHANNEL_SIZE), | |||||
| ChRaw: make(chan appcontext.BeaconAdvertisement, config.LARGE_CHANNEL_SIZE), | |||||
| ChParser: make(chan model.KafkaParser, config.SMALL_CHANNEL_SIZE), | ChParser: make(chan model.KafkaParser, config.SMALL_CHANNEL_SIZE), | ||||
| Cleanup: cleanup, | Cleanup: cleanup, | ||||
| }, nil | }, nil | ||||
| @@ -22,7 +22,7 @@ type LocationApp struct { | |||||
| KafkaManager *kafkaclient.KafkaManager | KafkaManager *kafkaclient.KafkaManager | ||||
| AppState *appcontext.AppState | AppState *appcontext.AppState | ||||
| Inferencer pkglocation.Inferencer | Inferencer pkglocation.Inferencer | ||||
| ChRaw chan model.BeaconAdvertisement | |||||
| ChRaw chan appcontext.BeaconAdvertisement | |||||
| ChSettings chan map[string]any | ChSettings chan map[string]any | ||||
| Cleanup func() | Cleanup func() | ||||
| wg sync.WaitGroup | wg sync.WaitGroup | ||||
| @@ -47,7 +47,7 @@ func New(cfg *config.Config) (*LocationApp, error) { | |||||
| KafkaManager: kafkaManager, | KafkaManager: kafkaManager, | ||||
| AppState: appState, | AppState: appState, | ||||
| Inferencer: pkglocation.NewDefaultInferencer(cfg.TLSInsecureSkipVerify), | Inferencer: pkglocation.NewDefaultInferencer(cfg.TLSInsecureSkipVerify), | ||||
| ChRaw: make(chan model.BeaconAdvertisement, config.LARGE_CHANNEL_SIZE), | |||||
| ChRaw: make(chan appcontext.BeaconAdvertisement, config.LARGE_CHANNEL_SIZE), | |||||
| ChSettings: make(chan map[string]any, config.SMALL_CHANNEL_SIZE), | ChSettings: make(chan map[string]any, config.SMALL_CHANNEL_SIZE), | ||||
| Cleanup: cleanup, | Cleanup: cleanup, | ||||
| }, nil | }, nil | ||||
| @@ -28,7 +28,7 @@ type ServerApp struct { | |||||
| KafkaManager *kafkaclient.KafkaManager | KafkaManager *kafkaclient.KafkaManager | ||||
| AppState *appcontext.AppState | AppState *appcontext.AppState | ||||
| ChLoc chan model.HTTPLocation | ChLoc chan model.HTTPLocation | ||||
| ChEvents chan model.BeaconEvent | |||||
| ChEvents chan appcontext.BeaconEvent | |||||
| ctx context.Context | ctx context.Context | ||||
| Server *http.Server | Server *http.Server | ||||
| Cleanup func() | Cleanup func() | ||||
| @@ -98,12 +98,12 @@ func (a *ServerApp) Init(ctx context.Context) error { | |||||
| slog.Error("UpdateDB", "err", err) | slog.Error("UpdateDB", "err", err) | ||||
| } | } | ||||
| readerTopics := []string{"locevents", "alertbeacons"} | |||||
| readerTopics := []string{"locevents", "alertbeacons", "health"} | |||||
| a.KafkaManager.PopulateKafkaManager(a.Cfg.KafkaURL, "server", readerTopics) | a.KafkaManager.PopulateKafkaManager(a.Cfg.KafkaURL, "server", readerTopics) | ||||
| slog.Info("Kafka readers initialized", "topics", readerTopics) | slog.Info("Kafka readers initialized", "topics", readerTopics) | ||||
| a.ChLoc = make(chan model.HTTPLocation, config.SMALL_CHANNEL_SIZE) | a.ChLoc = make(chan model.HTTPLocation, config.SMALL_CHANNEL_SIZE) | ||||
| a.ChEvents = make(chan model.BeaconEvent, config.MEDIUM_CHANNEL_SIZE) | |||||
| a.ChEvents = make(chan appcontext.BeaconEvent, config.MEDIUM_CHANNEL_SIZE) | |||||
| a.wg.Add(2) | a.wg.Add(2) | ||||
| go kafkaclient.Consume(a.KafkaManager.GetReader("locevents"), a.ChLoc, ctx, &a.wg) | go kafkaclient.Consume(a.KafkaManager.GetReader("locevents"), a.ChLoc, ctx, &a.wg) | ||||
| @@ -70,7 +70,7 @@ func UpdateDB(db *gorm.DB, ctx context.Context, cfg *config.Config, writer *kafk | |||||
| } | } | ||||
| } | } | ||||
| var settings model.Settings | |||||
| var settings appcontext.Settings | |||||
| db.First(&settings) | db.First(&settings) | ||||
| if settings.ID == 0 { | if settings.ID == 0 { | ||||
| msg := "settings are empty" | msg := "settings are empty" | ||||
| @@ -7,6 +7,7 @@ import ( | |||||
| "strings" | "strings" | ||||
| "time" | "time" | ||||
| "github.com/AFASystems/presence/internal/pkg/common/appcontext" | |||||
| "github.com/AFASystems/presence/internal/pkg/kafkaclient" | "github.com/AFASystems/presence/internal/pkg/kafkaclient" | ||||
| "github.com/AFASystems/presence/internal/pkg/model" | "github.com/AFASystems/presence/internal/pkg/model" | ||||
| "github.com/segmentio/kafka-go" | "github.com/segmentio/kafka-go" | ||||
| @@ -42,7 +43,7 @@ func HandleMQTTMessage(topic string, payload []byte, lookup BeaconLookup, writer | |||||
| if !ok { | if !ok { | ||||
| continue | continue | ||||
| } | } | ||||
| adv := model.BeaconAdvertisement{ | |||||
| adv := appcontext.BeaconAdvertisement{ | |||||
| ID: id, | ID: id, | ||||
| Hostname: hostname, | Hostname: hostname, | ||||
| MAC: reading.MAC, | MAC: reading.MAC, | ||||
| @@ -5,16 +5,15 @@ import ( | |||||
| "log/slog" | "log/slog" | ||||
| "os" | "os" | ||||
| "github.com/AFASystems/presence/internal/pkg/model" | |||||
| "github.com/mitchellh/mapstructure" | "github.com/mitchellh/mapstructure" | ||||
| ) | ) | ||||
| // AppState provides centralized access to application state | // AppState provides centralized access to application state | ||||
| type AppState struct { | type AppState struct { | ||||
| beacons model.BeaconsList | |||||
| settings model.Settings | |||||
| beaconEvents model.BeaconEventList | |||||
| beaconsLookup model.BeaconsLookup | |||||
| beacons BeaconsList | |||||
| settings Settings | |||||
| beaconEvents BeaconEventList | |||||
| beaconsLookup BeaconsLookup | |||||
| } | } | ||||
| func getEnv(key, def string) string { | func getEnv(key, def string) string { | ||||
| @@ -27,10 +26,10 @@ func getEnv(key, def string) string { | |||||
| // NewAppState creates a new application context AppState with default values | // NewAppState creates a new application context AppState with default values | ||||
| func NewAppState() *AppState { | func NewAppState() *AppState { | ||||
| return &AppState{ | return &AppState{ | ||||
| beacons: model.BeaconsList{ | |||||
| Beacons: make(map[string]model.Beacon), | |||||
| beacons: BeaconsList{ | |||||
| Beacons: make(map[string]Beacon), | |||||
| }, | }, | ||||
| settings: model.Settings{ | |||||
| settings: Settings{ | |||||
| ID: 1, | ID: 1, | ||||
| CurrentAlgorithm: getEnv("ALGORITHM", "filter"), | CurrentAlgorithm: getEnv("ALGORITHM", "filter"), | ||||
| LocationConfidence: 4, | LocationConfidence: 4, | ||||
| @@ -41,29 +40,29 @@ func NewAppState() *AppState { | |||||
| RSSIEnforceThreshold: false, | RSSIEnforceThreshold: false, | ||||
| RSSIMinThreshold: 100, | RSSIMinThreshold: 100, | ||||
| }, | }, | ||||
| beaconEvents: model.BeaconEventList{ | |||||
| Beacons: make(map[string]model.BeaconEvent), | |||||
| beaconEvents: BeaconEventList{ | |||||
| Beacons: make(map[string]BeaconEvent), | |||||
| }, | }, | ||||
| beaconsLookup: model.BeaconsLookup{ | |||||
| beaconsLookup: BeaconsLookup{ | |||||
| Lookup: make(map[string]string), | Lookup: make(map[string]string), | ||||
| }, | }, | ||||
| } | } | ||||
| } | } | ||||
| // GetBeacons returns thread-safe access to beacons list | // GetBeacons returns thread-safe access to beacons list | ||||
| func (m *AppState) GetBeacons() *model.BeaconsList { | |||||
| func (m *AppState) GetBeacons() *BeaconsList { | |||||
| m.beacons.Lock.RLock() | m.beacons.Lock.RLock() | ||||
| defer m.beacons.Lock.RUnlock() | defer m.beacons.Lock.RUnlock() | ||||
| return &m.beacons | return &m.beacons | ||||
| } | } | ||||
| // GetSettings returns thread-safe access to settings | // GetSettings returns thread-safe access to settings | ||||
| func (m *AppState) GetSettings() *model.Settings { | |||||
| func (m *AppState) GetSettings() *Settings { | |||||
| return &m.settings | return &m.settings | ||||
| } | } | ||||
| // GetBeaconEvents returns thread-safe access to beacon events | // GetBeaconEvents returns thread-safe access to beacon events | ||||
| func (m *AppState) GetBeaconEvents() *model.BeaconEventList { | |||||
| func (m *AppState) GetBeaconEvents() *BeaconEventList { | |||||
| m.beaconEvents.Lock.RLock() | m.beaconEvents.Lock.RLock() | ||||
| defer m.beaconEvents.Lock.RUnlock() | defer m.beaconEvents.Lock.RUnlock() | ||||
| return &m.beaconEvents | return &m.beaconEvents | ||||
| @@ -98,7 +97,7 @@ func (m *AppState) BeaconExists(id string) (string, bool) { | |||||
| } | } | ||||
| // GetBeacon returns a beacon by ID (thread-safe) | // GetBeacon returns a beacon by ID (thread-safe) | ||||
| func (m *AppState) GetBeacon(id string) (model.Beacon, bool) { | |||||
| func (m *AppState) GetBeacon(id string) (Beacon, bool) { | |||||
| m.beacons.Lock.RLock() | m.beacons.Lock.RLock() | ||||
| defer m.beacons.Lock.RUnlock() | defer m.beacons.Lock.RUnlock() | ||||
| @@ -107,7 +106,7 @@ func (m *AppState) GetBeacon(id string) (model.Beacon, bool) { | |||||
| } | } | ||||
| // UpdateBeacon updates a beacon in the list (thread-safe) | // UpdateBeacon updates a beacon in the list (thread-safe) | ||||
| func (m *AppState) UpdateBeacon(id string, beacon model.Beacon) { | |||||
| func (m *AppState) UpdateBeacon(id string, beacon Beacon) { | |||||
| m.beacons.Lock.Lock() | m.beacons.Lock.Lock() | ||||
| defer m.beacons.Lock.Unlock() | defer m.beacons.Lock.Unlock() | ||||
| @@ -115,7 +114,7 @@ func (m *AppState) UpdateBeacon(id string, beacon model.Beacon) { | |||||
| } | } | ||||
| // GetBeaconEvent returns a beacon event by ID (thread-safe) | // GetBeaconEvent returns a beacon event by ID (thread-safe) | ||||
| func (m *AppState) GetBeaconEvent(id string) (model.BeaconEvent, bool) { | |||||
| func (m *AppState) GetBeaconEvent(id string) (BeaconEvent, bool) { | |||||
| m.beaconEvents.Lock.RLock() | m.beaconEvents.Lock.RLock() | ||||
| defer m.beaconEvents.Lock.RUnlock() | defer m.beaconEvents.Lock.RUnlock() | ||||
| @@ -124,7 +123,7 @@ func (m *AppState) GetBeaconEvent(id string) (model.BeaconEvent, bool) { | |||||
| } | } | ||||
| // UpdateBeaconEvent updates a beacon event in the list (thread-safe) | // UpdateBeaconEvent updates a beacon event in the list (thread-safe) | ||||
| func (m *AppState) UpdateBeaconEvent(id string, event model.BeaconEvent) { | |||||
| func (m *AppState) UpdateBeaconEvent(id string, event BeaconEvent) { | |||||
| m.beaconEvents.Lock.Lock() | m.beaconEvents.Lock.Lock() | ||||
| defer m.beaconEvents.Lock.Unlock() | defer m.beaconEvents.Lock.Unlock() | ||||
| @@ -132,11 +131,11 @@ func (m *AppState) UpdateBeaconEvent(id string, event model.BeaconEvent) { | |||||
| } | } | ||||
| // GetAllBeacons returns a copy of all beacons | // GetAllBeacons returns a copy of all beacons | ||||
| func (m *AppState) GetAllBeacons() map[string]model.Beacon { | |||||
| func (m *AppState) GetAllBeacons() map[string]Beacon { | |||||
| m.beacons.Lock.RLock() | m.beacons.Lock.RLock() | ||||
| defer m.beacons.Lock.RUnlock() | defer m.beacons.Lock.RUnlock() | ||||
| beacons := make(map[string]model.Beacon) | |||||
| beacons := make(map[string]Beacon) | |||||
| for id, beacon := range m.beacons.Beacons { | for id, beacon := range m.beacons.Beacons { | ||||
| beacons[id] = beacon | beacons[id] = beacon | ||||
| } | } | ||||
| @@ -152,7 +151,7 @@ func (m *AppState) GetBeaconCount() int { | |||||
| } | } | ||||
| // GetSettingsValue returns current settings as a value | // GetSettingsValue returns current settings as a value | ||||
| func (m *AppState) GetSettingsValue() model.Settings { | |||||
| func (m *AppState) GetSettingsValue() Settings { | |||||
| return m.settings | return m.settings | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| package model | |||||
| package appcontext | |||||
| import ( | import ( | ||||
| "crypto/sha256" | "crypto/sha256" | ||||
| @@ -0,0 +1,100 @@ | |||||
| package appcontext | |||||
| import "sync" | |||||
| // BeaconAdvertisement represents the JSON payload received from beacon advertisements. | |||||
| type BeaconAdvertisement struct { | |||||
| ID string | |||||
| Hostname string `json:"hostname"` | |||||
| MAC string `json:"mac"` | |||||
| RSSI int64 `json:"rssi"` | |||||
| ScanResponse string `json:"is_scan_response"` | |||||
| Type string `json:"type"` | |||||
| Data string `json:"data"` | |||||
| BeaconType string `json:"beacon_type"` | |||||
| UUID string `json:"uuid"` | |||||
| Major string `json:"major"` | |||||
| Minor string `json:"minor"` | |||||
| TXPower string `json:"tx_power"` | |||||
| NamespaceID string `json:"namespace"` | |||||
| InstanceID string `json:"instance_id"` | |||||
| HSButtonCounter int64 `json:"hb_button_counter"` | |||||
| HSButtonPrev int64 `json:"hb_button_counter_prev"` | |||||
| HSBatteryLevel int64 `json:"hb_button_battery"` | |||||
| HSRandomNonce string `json:"hb_button_random"` | |||||
| HSButtonMode string `json:"hb_button_mode"` | |||||
| } | |||||
| // BeaconMetric stores signal and distance data for a beacon. | |||||
| type BeaconMetric struct { | |||||
| Location string | |||||
| Distance float64 | |||||
| RSSI int64 | |||||
| Timestamp int64 | |||||
| } | |||||
| // Beacon holds all relevant information about a tracked beacon device. | |||||
| type Beacon struct { | |||||
| Name string `json:"name"` | |||||
| ID string `json:"beacon_id"` | |||||
| BeaconType string `json:"beacon_type"` | |||||
| BeaconLocation string `json:"beacon_location"` | |||||
| LastSeen int64 `json:"last_seen"` | |||||
| IncomingJSON BeaconAdvertisement `json:"incoming_json"` | |||||
| Distance float64 `json:"distance"` | |||||
| PreviousLocation string | |||||
| PreviousConfidentLocation string | |||||
| ExpiredLocation string | |||||
| LocationConfidence int64 | |||||
| LocationHistory []string | |||||
| BeaconMetrics []BeaconMetric | |||||
| Location string `json:"location"` | |||||
| HSButtonCounter int64 `json:"hs_button_counter"` | |||||
| HSButtonPrev int64 `json:"hs_button_counter_prev"` | |||||
| HSBattery int64 `json:"hs_button_battery"` | |||||
| HSRandomNonce string `json:"hs_button_random"` | |||||
| HSButtonMode string `json:"hs_button_mode"` | |||||
| Event int `json:"beacon_event"` | |||||
| } | |||||
| type Settings struct { | |||||
| ID int `gorm:"primaryKey"` // this is always 1 | |||||
| CurrentAlgorithm string `json:"current_algorithm" mapstructure:"current_algorithm"` | |||||
| LocationConfidence int64 `json:"location_confidence" mapstructure:"location_confidence"` | |||||
| LastSeenThreshold int64 `json:"last_seen_threshold" mapstructure:"last_seen_threshold"` | |||||
| BeaconMetricSize int `json:"beacon_metric_size" mapstructure:"beacon_metric_size"` | |||||
| HASendInterval int `json:"HA_send_interval" mapstructure:"HA_send_interval"` | |||||
| HASendChangesOnly bool `json:"HA_send_changes_only" mapstructure:"HA_send_changes_only"` | |||||
| RSSIEnforceThreshold bool `json:"RSSI_enforce_threshold" mapstructure:"RSSI_enforce_threshold"` | |||||
| RSSIMinThreshold int64 `json:"RSSI_min_threshold" mapstructure:"RSSI_min_threshold"` | |||||
| } | |||||
| type BeaconEvent struct { | |||||
| Name string | |||||
| ID string | |||||
| Type string | |||||
| Battery uint32 | |||||
| Event int | |||||
| AccX int16 | |||||
| AccY int16 | |||||
| AccZ int16 | |||||
| Temperature uint16 | |||||
| Heart int16 | |||||
| BtnPressed bool | |||||
| } | |||||
| // BeaconsList holds all known beacons and their synchronization lock. | |||||
| type BeaconsList struct { | |||||
| Beacons map[string]Beacon `json:"beacons"` | |||||
| Lock sync.RWMutex | |||||
| } | |||||
| type BeaconEventList struct { | |||||
| Beacons map[string]BeaconEvent | |||||
| Lock sync.RWMutex | |||||
| } | |||||
| type BeaconsLookup struct { | |||||
| Lookup map[string]string | |||||
| Lock sync.RWMutex | |||||
| } | |||||
| @@ -1,6 +1,7 @@ | |||||
| package utils | package utils | ||||
| import ( | import ( | ||||
| "github.com/AFASystems/presence/internal/pkg/common/appcontext" | |||||
| "github.com/AFASystems/presence/internal/pkg/model" | "github.com/AFASystems/presence/internal/pkg/model" | ||||
| ) | ) | ||||
| @@ -37,8 +38,8 @@ func RemoveFlagBytes(b []byte) []byte { | |||||
| } | } | ||||
| // Generate event based on the Beacon type | // Generate event based on the Beacon type | ||||
| func LoopADStructures(b []byte, i [][2]int, id string, parserRegistry *model.ParserRegistry) model.BeaconEvent { | |||||
| be := model.BeaconEvent{} | |||||
| func LoopADStructures(b []byte, i [][2]int, id string, parserRegistry *model.ParserRegistry) appcontext.BeaconEvent { | |||||
| be := appcontext.BeaconEvent{} | |||||
| for _, r := range i { | for _, r := range i { | ||||
| ad := b[r[0]:r[1]] | ad := b[r[0]:r[1]] | ||||
| if !isValidADStructure(ad) { | if !isValidADStructure(ad) { | ||||
| @@ -4,10 +4,10 @@ import ( | |||||
| "math" | "math" | ||||
| "strconv" | "strconv" | ||||
| "github.com/AFASystems/presence/internal/pkg/model" | |||||
| "github.com/AFASystems/presence/internal/pkg/common/appcontext" | |||||
| ) | ) | ||||
| func CalculateDistance(adv model.BeaconAdvertisement) float64 { | |||||
| func CalculateDistance(adv appcontext.BeaconAdvertisement) float64 { | |||||
| rssi := adv.RSSI | rssi := adv.RSSI | ||||
| power := adv.TXPower | power := adv.TXPower | ||||
| ratio := float64(rssi) * (1.0 / float64(twosComp(power))) | ratio := float64(rssi) * (1.0 / float64(twosComp(power))) | ||||
| @@ -7,15 +7,15 @@ import ( | |||||
| "net/http" | "net/http" | ||||
| "github.com/AFASystems/presence/internal/pkg/api/response" | "github.com/AFASystems/presence/internal/pkg/api/response" | ||||
| "github.com/AFASystems/presence/internal/pkg/common/appcontext" | |||||
| "github.com/AFASystems/presence/internal/pkg/kafkaclient" | "github.com/AFASystems/presence/internal/pkg/kafkaclient" | ||||
| "github.com/AFASystems/presence/internal/pkg/model" | |||||
| "github.com/segmentio/kafka-go" | "github.com/segmentio/kafka-go" | ||||
| "gorm.io/gorm" | "gorm.io/gorm" | ||||
| ) | ) | ||||
| func SettingsListController(db *gorm.DB, context context.Context) http.HandlerFunc { | func SettingsListController(db *gorm.DB, context context.Context) http.HandlerFunc { | ||||
| return func(w http.ResponseWriter, r *http.Request) { | return func(w http.ResponseWriter, r *http.Request) { | ||||
| var settings []model.Settings | |||||
| var settings []appcontext.Settings | |||||
| if err := db.WithContext(context).Find(&settings).Error; err != nil { | if err := db.WithContext(context).Find(&settings).Error; err != nil { | ||||
| response.InternalError(w, "failed to list settings", err) | response.InternalError(w, "failed to list settings", err) | ||||
| return | return | ||||
| @@ -34,7 +34,7 @@ func SettingsUpdateController(db *gorm.DB, writer *kafka.Writer, context context | |||||
| slog.Info("updating settings", "updates", updates) | slog.Info("updating settings", "updates", updates) | ||||
| if err := db.WithContext(context).Model(&model.Settings{}).Where("id = ?", 1).Updates(updates).Error; err != nil { | |||||
| if err := db.WithContext(context).Model(&appcontext.Settings{}).Where("id = ?", 1).Updates(updates).Error; err != nil { | |||||
| response.InternalError(w, "failed to update settings", err) | response.InternalError(w, "failed to update settings", err) | ||||
| return | return | ||||
| } | } | ||||
| @@ -4,6 +4,7 @@ import ( | |||||
| "fmt" | "fmt" | ||||
| "log/slog" | "log/slog" | ||||
| "github.com/AFASystems/presence/internal/pkg/common/appcontext" | |||||
| "github.com/AFASystems/presence/internal/pkg/config" | "github.com/AFASystems/presence/internal/pkg/config" | ||||
| "github.com/AFASystems/presence/internal/pkg/model" | "github.com/AFASystems/presence/internal/pkg/model" | ||||
| "gorm.io/driver/postgres" | "gorm.io/driver/postgres" | ||||
| @@ -25,7 +26,7 @@ func Connect(cfg *config.Config) (*gorm.DB, error) { | |||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| if err := db.AutoMigrate(&model.Gateway{}, model.Zone{}, model.TrackerZones{}, model.Tracker{}, model.Config{}, model.Settings{}, model.Tracks{}, &model.Alert{}); err != nil { | |||||
| if err := db.AutoMigrate(&model.Gateway{}, model.Zone{}, model.TrackerZones{}, model.Tracker{}, model.Config{}, appcontext.Settings{}, model.Tracks{}, &model.Alert{}); err != nil { | |||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| @@ -16,14 +16,14 @@ import ( | |||||
| ) | ) | ||||
| // ProcessIncoming decodes a beacon advertisement and writes the event to the writer if it changed. | // ProcessIncoming decodes a beacon advertisement and writes the event to the writer if it changed. | ||||
| func ProcessIncoming(adv model.BeaconAdvertisement, appState *appcontext.AppState, writer *kafka.Writer, registry *model.ParserRegistry) { | |||||
| func ProcessIncoming(adv appcontext.BeaconAdvertisement, appState *appcontext.AppState, writer *kafka.Writer, registry *model.ParserRegistry) { | |||||
| if err := DecodeBeacon(adv, appState, writer, registry); err != nil { | if err := DecodeBeacon(adv, appState, writer, registry); err != nil { | ||||
| slog.Error("decoding beacon", "err", err, "id", adv.ID) | slog.Error("decoding beacon", "err", err, "id", adv.ID) | ||||
| } | } | ||||
| } | } | ||||
| // DecodeBeacon hex-decodes the payload, runs the parser registry, dedupes by event hash, and writes to writer. | // DecodeBeacon hex-decodes the payload, runs the parser registry, dedupes by event hash, and writes to writer. | ||||
| func DecodeBeacon(adv model.BeaconAdvertisement, appState *appcontext.AppState, writer *kafka.Writer, registry *model.ParserRegistry) error { | |||||
| func DecodeBeacon(adv appcontext.BeaconAdvertisement, appState *appcontext.AppState, writer *kafka.Writer, registry *model.ParserRegistry) error { | |||||
| beacon := strings.TrimSpace(adv.Data) | beacon := strings.TrimSpace(adv.Data) | ||||
| id := adv.ID | id := adv.ID | ||||
| if beacon == "" { | if beacon == "" { | ||||
| @@ -121,3 +121,23 @@ func (m *KafkaManager) GetWriter(topic string) *kafka.Writer { | |||||
| defer m.kafkaWritersMap.KafkaWritersLock.RUnlock() | defer m.kafkaWritersMap.KafkaWritersLock.RUnlock() | ||||
| return m.kafkaWritersMap.KafkaWriters[topic] | return m.kafkaWritersMap.KafkaWriters[topic] | ||||
| } | } | ||||
| func (m *KafkaManager) GetReaders() []string { | |||||
| m.kafkaReadersMap.KafkaReadersLock.RLock() | |||||
| var readers []string | |||||
| for key := range m.kafkaReadersMap.KafkaReaders { | |||||
| readers = append(readers, key) | |||||
| } | |||||
| m.kafkaReadersMap.KafkaReadersLock.RUnlock() | |||||
| return readers | |||||
| } | |||||
| func (m *KafkaManager) GetWriters() []string { | |||||
| m.kafkaWritersMap.KafkaWritersLock.RLock() | |||||
| var writers []string | |||||
| for key := range m.kafkaWritersMap.KafkaWriters { | |||||
| writers = append(writers, key) | |||||
| } | |||||
| m.kafkaWritersMap.KafkaWritersLock.RUnlock() | |||||
| return writers | |||||
| } | |||||
| @@ -6,12 +6,11 @@ import ( | |||||
| "github.com/AFASystems/presence/internal/pkg/common/appcontext" | "github.com/AFASystems/presence/internal/pkg/common/appcontext" | ||||
| "github.com/AFASystems/presence/internal/pkg/common/utils" | "github.com/AFASystems/presence/internal/pkg/common/utils" | ||||
| "github.com/AFASystems/presence/internal/pkg/model" | |||||
| ) | ) | ||||
| // AssignBeaconToList updates app state with a new beacon advertisement: appends a metric | // AssignBeaconToList updates app state with a new beacon advertisement: appends a metric | ||||
| // to the beacon's sliding window and updates last seen. | // to the beacon's sliding window and updates last seen. | ||||
| func AssignBeaconToList(adv model.BeaconAdvertisement, appState *appcontext.AppState) { | |||||
| func AssignBeaconToList(adv appcontext.BeaconAdvertisement, appState *appcontext.AppState) { | |||||
| id := adv.ID | id := adv.ID | ||||
| now := time.Now().Unix() | now := time.Now().Unix() | ||||
| settings := appState.GetSettingsValue() | settings := appState.GetSettingsValue() | ||||
| @@ -23,17 +22,17 @@ func AssignBeaconToList(adv model.BeaconAdvertisement, appState *appcontext.AppS | |||||
| beacon, ok := appState.GetBeacon(id) | beacon, ok := appState.GetBeacon(id) | ||||
| if !ok { | if !ok { | ||||
| beacon = model.Beacon{ID: id} | |||||
| beacon = appcontext.Beacon{ID: id} | |||||
| } | } | ||||
| beacon.IncomingJSON = adv | beacon.IncomingJSON = adv | ||||
| beacon.LastSeen = now | beacon.LastSeen = now | ||||
| if beacon.BeaconMetrics == nil { | if beacon.BeaconMetrics == nil { | ||||
| beacon.BeaconMetrics = make([]model.BeaconMetric, 0, settings.BeaconMetricSize) | |||||
| beacon.BeaconMetrics = make([]appcontext.BeaconMetric, 0, settings.BeaconMetricSize) | |||||
| } | } | ||||
| metric := model.BeaconMetric{ | |||||
| metric := appcontext.BeaconMetric{ | |||||
| Distance: utils.CalculateDistance(adv), | Distance: utils.CalculateDistance(adv), | ||||
| Timestamp: now, | Timestamp: now, | ||||
| RSSI: int64(adv.RSSI), | RSSI: int64(adv.RSSI), | ||||
| @@ -0,0 +1,71 @@ | |||||
| package model | |||||
| import ( | |||||
| "encoding/json" | |||||
| "sync" | |||||
| "time" | |||||
| "github.com/AFASystems/presence/internal/pkg/common/appcontext" | |||||
| "github.com/AFASystems/presence/internal/pkg/kafkaclient" | |||||
| ) | |||||
| type BaseHealth struct { | |||||
| Lock sync.RWMutex | |||||
| Uptime time.Duration `json:"uptime"` | |||||
| ActiveReaders []string `json:"activeReaders"` | |||||
| ActiveWriters []string `json:"activeWriters"` | |||||
| ActiveBeacons []string `json:"activeBeacons"` | |||||
| } | |||||
| type DecoderHealth struct { | |||||
| BaseHealth | |||||
| // current active configs ? dont know yet how | |||||
| } | |||||
| type LocationHealth struct { | |||||
| BaseHealth | |||||
| ActiveSettings []appcontext.Settings `json:"activeSettings"` | |||||
| } | |||||
| type BridgeHealth struct { | |||||
| BaseHealth | |||||
| } | |||||
| func (b *BaseHealth) GetUptime(startTime time.Time) { | |||||
| b.Lock.Lock() | |||||
| b.Uptime = time.Since(startTime) | |||||
| b.Lock.Unlock() | |||||
| } | |||||
| func (b *BaseHealth) GetActiveReaders(m *kafkaclient.KafkaManager) { | |||||
| b.Lock.Lock() | |||||
| b.ActiveReaders = m.GetReaders() | |||||
| b.Lock.Unlock() | |||||
| } | |||||
| func (b *BaseHealth) GetActiveWriters(m *kafkaclient.KafkaManager) { | |||||
| b.Lock.Lock() | |||||
| b.ActiveWriters = m.GetWriters() | |||||
| b.Lock.Unlock() | |||||
| } | |||||
| func (b *BaseHealth) GetActiveBeacons(m *appcontext.AppState) { | |||||
| b.Lock.Lock() | |||||
| beacons := m.GetAllBeacons() | |||||
| for beacon := range beacons { | |||||
| b.ActiveBeacons = append(b.ActiveBeacons, beacon) | |||||
| } | |||||
| b.Lock.Unlock() | |||||
| } | |||||
| func (d *DecoderHealth) Marshal() ([]byte, error) { | |||||
| return json.Marshal(d) | |||||
| } | |||||
| func (l *LocationHealth) Marshal() ([]byte, error) { | |||||
| return json.Marshal(l) | |||||
| } | |||||
| func (b *BridgeHealth) Marshal() ([]byte, error) { | |||||
| return json.Marshal(b) | |||||
| } | |||||
| @@ -6,6 +6,8 @@ import ( | |||||
| "fmt" | "fmt" | ||||
| "log/slog" | "log/slog" | ||||
| "sync" | "sync" | ||||
| "github.com/AFASystems/presence/internal/pkg/common/appcontext" | |||||
| ) | ) | ||||
| type ParserConfig struct { | type ParserConfig struct { | ||||
| @@ -77,9 +79,9 @@ func (p *ParserRegistry) Unregister(name string) { | |||||
| // TODO: change this to be dynamic, maybe event is interface with no predefined properties | // TODO: change this to be dynamic, maybe event is interface with no predefined properties | ||||
| // or types | // or types | ||||
| func (b *BeaconParser) Parse(name string, ad []byte) (BeaconEvent, bool) { | |||||
| func (b *BeaconParser) Parse(name string, ad []byte) (appcontext.BeaconEvent, bool) { | |||||
| flag := false | flag := false | ||||
| event := BeaconEvent{Type: name} | |||||
| event := appcontext.BeaconEvent{Type: name} | |||||
| if cfg, ok := b.configs["battery"]; ok { | if cfg, ok := b.configs["battery"]; ok { | ||||
| event.Battery = uint32(b.extract(ad, cfg).(uint16)) | event.Battery = uint32(b.extract(ad, cfg).(uint16)) | ||||
| flag = true | flag = true | ||||
| @@ -1,13 +0,0 @@ | |||||
| package model | |||||
| type Settings struct { | |||||
| ID int `gorm:"primaryKey"` // this is always 1 | |||||
| CurrentAlgorithm string `json:"current_algorithm" mapstructure:"current_algorithm"` | |||||
| LocationConfidence int64 `json:"location_confidence" mapstructure:"location_confidence"` | |||||
| LastSeenThreshold int64 `json:"last_seen_threshold" mapstructure:"last_seen_threshold"` | |||||
| BeaconMetricSize int `json:"beacon_metric_size" mapstructure:"beacon_metric_size"` | |||||
| HASendInterval int `json:"HA_send_interval" mapstructure:"HA_send_interval"` | |||||
| HASendChangesOnly bool `json:"HA_send_changes_only" mapstructure:"HA_send_changes_only"` | |||||
| RSSIEnforceThreshold bool `json:"RSSI_enforce_threshold" mapstructure:"RSSI_enforce_threshold"` | |||||
| RSSIMinThreshold int64 `json:"RSSI_min_threshold" mapstructure:"RSSI_min_threshold"` | |||||
| } | |||||
| @@ -1,40 +1,5 @@ | |||||
| package model | package model | ||||
| import ( | |||||
| "sync" | |||||
| ) | |||||
| // BeaconAdvertisement represents the JSON payload received from beacon advertisements. | |||||
| type BeaconAdvertisement struct { | |||||
| ID string | |||||
| Hostname string `json:"hostname"` | |||||
| MAC string `json:"mac"` | |||||
| RSSI int64 `json:"rssi"` | |||||
| ScanResponse string `json:"is_scan_response"` | |||||
| Type string `json:"type"` | |||||
| Data string `json:"data"` | |||||
| BeaconType string `json:"beacon_type"` | |||||
| UUID string `json:"uuid"` | |||||
| Major string `json:"major"` | |||||
| Minor string `json:"minor"` | |||||
| TXPower string `json:"tx_power"` | |||||
| NamespaceID string `json:"namespace"` | |||||
| InstanceID string `json:"instance_id"` | |||||
| HSButtonCounter int64 `json:"hb_button_counter"` | |||||
| HSButtonPrev int64 `json:"hb_button_counter_prev"` | |||||
| HSBatteryLevel int64 `json:"hb_button_battery"` | |||||
| HSRandomNonce string `json:"hb_button_random"` | |||||
| HSButtonMode string `json:"hb_button_mode"` | |||||
| } | |||||
| // BeaconMetric stores signal and distance data for a beacon. | |||||
| type BeaconMetric struct { | |||||
| Location string | |||||
| Distance float64 | |||||
| RSSI int64 | |||||
| Timestamp int64 | |||||
| } | |||||
| // HTTPLocation describes a beacon's state as served over HTTP. | // HTTPLocation describes a beacon's state as served over HTTP. | ||||
| type HTTPLocation struct { | type HTTPLocation struct { | ||||
| Method string `json:"method"` | Method string `json:"method"` | ||||
| @@ -50,80 +15,6 @@ type HTTPLocation struct { | |||||
| MAC string `json:"mac"` | MAC string `json:"mac"` | ||||
| } | } | ||||
| // Beacon holds all relevant information about a tracked beacon device. | |||||
| type Beacon struct { | |||||
| Name string `json:"name"` | |||||
| ID string `json:"beacon_id"` | |||||
| BeaconType string `json:"beacon_type"` | |||||
| BeaconLocation string `json:"beacon_location"` | |||||
| LastSeen int64 `json:"last_seen"` | |||||
| IncomingJSON BeaconAdvertisement `json:"incoming_json"` | |||||
| Distance float64 `json:"distance"` | |||||
| PreviousLocation string | |||||
| PreviousConfidentLocation string | |||||
| ExpiredLocation string | |||||
| LocationConfidence int64 | |||||
| LocationHistory []string | |||||
| BeaconMetrics []BeaconMetric | |||||
| Location string `json:"location"` | |||||
| HSButtonCounter int64 `json:"hs_button_counter"` | |||||
| HSButtonPrev int64 `json:"hs_button_counter_prev"` | |||||
| HSBattery int64 `json:"hs_button_battery"` | |||||
| HSRandomNonce string `json:"hs_button_random"` | |||||
| HSButtonMode string `json:"hs_button_mode"` | |||||
| Event int `json:"beacon_event"` | |||||
| } | |||||
| type BeaconEvent struct { | |||||
| Name string | |||||
| ID string | |||||
| Type string | |||||
| Battery uint32 | |||||
| Event int | |||||
| AccX int16 | |||||
| AccY int16 | |||||
| AccZ int16 | |||||
| Temperature uint16 | |||||
| Heart int16 | |||||
| BtnPressed bool | |||||
| } | |||||
| type HTTPResult struct { | |||||
| BeaconId string `json:"id"` | |||||
| Name string `json:"name"` | |||||
| ID string `json:"MAC"` | |||||
| Status string `json:"status"` | |||||
| Model string `json:"model"` | |||||
| Position string `json:"position"` | |||||
| Notes string `json:"notes"` | |||||
| X float32 `json:"x"` | |||||
| Y float32 `json:"y"` | |||||
| Zone string `json:"zone"` | |||||
| Building string `json:"building"` | |||||
| BeaconType string `json:"type"` | |||||
| Battery int64 `json:"battery"` | |||||
| Event int `json:"event"` | |||||
| Distance float64 `json:"distance"` | |||||
| LastSeen int64 `json:"timestamp"` | |||||
| PreviousConfidentLocation string `json:"previous_confident_location"` | |||||
| } | |||||
| // BeaconsList holds all known beacons and their synchronization lock. | |||||
| type BeaconsList struct { | |||||
| Beacons map[string]Beacon `json:"beacons"` | |||||
| Lock sync.RWMutex | |||||
| } | |||||
| type BeaconEventList struct { | |||||
| Beacons map[string]BeaconEvent | |||||
| Lock sync.RWMutex | |||||
| } | |||||
| type BeaconsLookup struct { | |||||
| Lookup map[string]string | |||||
| Lock sync.RWMutex | |||||
| } | |||||
| // RawReading represents an incoming raw sensor reading. | // RawReading represents an incoming raw sensor reading. | ||||
| type RawReading struct { | type RawReading struct { | ||||
| Timestamp string `json:"timestamp"` | Timestamp string `json:"timestamp"` | ||||