package mqttclient import ( "encoding/json" "log" "time" "github.com/AFASystems/presence/internal/pkg/model" "github.com/AFASystems/presence/internal/pkg/persistence" "github.com/yosssi/gmq/mqtt" "github.com/yosssi/gmq/mqtt/client" ) func getLikelyLocations(settings *model.Settings, ctx *model.AppContext, cl *client.Client) { ctx.HTTPResults.HTTPResultsLock.Lock() defer ctx.HTTPResults.HTTPResultsLock.Unlock() ctx.HTTPResults.HTTPResults = model.HTTPLocationsList{Beacons: []model.HTTPLocation{}} shouldPersist := false for id, beacon := range ctx.Beacons.Beacons { if len(beacon.Beacon_metrics) == 0 { continue } if isExpired(&beacon, settings) { handleExpiredBeacon(&beacon, cl, ctx) continue } best := calculateBestLocation(&beacon) updateBeaconState(&beacon, best, settings, ctx, cl) appendHTTPResult(ctx, beacon, best) ctx.Beacons.Beacons[id] = beacon shouldPersist = true } if shouldPersist { persistence.PersistBeacons(&ctx.Beacons) } } func isExpired(b *model.Beacon, s *model.Settings) bool { return time.Now().Unix()-b.Beacon_metrics[len(b.Beacon_metrics)-1].Timestamp > s.Last_seen_threshold } func handleExpiredBeacon(b *model.Beacon, cl *client.Client, ctx *model.AppContext) { if b.Expired_location == "expired" { return } b.Expired_location = "expired" msg := model.Message{ Email: b.Previous_confident_location, Username: b.Name, Message: "expired", } data, _ := json.Marshal(msg) log.Println(string(data)) ctx.Broadcast <- msg } func calculateBestLocation(b *model.Beacon) model.BestLocation { locScores := map[string]float64{} for _, m := range b.Beacon_metrics { score := 1.5 + 0.75*(1.0-(float64(m.Rssi)/-100.0)) locScores[m.Location] += score } bestName, bestScore := "", 0.0 for name, score := range locScores { if score > bestScore { bestName, bestScore = name, score } } last := b.Beacon_metrics[len(b.Beacon_metrics)-1] return model.BestLocation{Name: bestName, Distance: last.Distance, Last_seen: last.Timestamp} } func updateBeaconState(b *model.Beacon, best model.BestLocation, s *model.Settings, ctx *model.AppContext, cl *client.Client) { updateLocationHistory(b, best.Name) updateConfidence(b, best.Name, s) if locationChanged(b, best, s) { publishLocationChange(b, best, cl) b.Location_confidence = 0 b.Previous_confident_location = best.Name } } func updateLocationHistory(b *model.Beacon, loc string) { b.Location_history = append(b.Location_history, loc) if len(b.Location_history) > 10 { b.Location_history = b.Location_history[1:] } } func updateConfidence(b *model.Beacon, loc string, s *model.Settings) { counts := map[string]int{} for _, l := range b.Location_history { counts[l]++ } maxCount, mostCommon := 0, "" for l, c := range counts { if c > maxCount { maxCount, mostCommon = c, l } } if maxCount >= 7 { if mostCommon == b.Previous_confident_location { b.Location_confidence++ } else { b.Location_confidence = 1 b.Previous_confident_location = mostCommon } } } func locationChanged(b *model.Beacon, best model.BestLocation, s *model.Settings) bool { return (b.Location_confidence == s.Location_confidence && b.Previous_confident_location != best.Name) || b.Expired_location == "expired" } func publishLocationChange(b *model.Beacon, best model.BestLocation, cl *client.Client) { location := best.Name if b.Expired_location == "expired" { location = "expired" } js, err := json.Marshal(model.LocationChange{ Beacon_ref: *b, Name: b.Name, Previous_location: b.Previous_confident_location, New_location: location, Timestamp: time.Now().Unix(), }) if err != nil { return } err = cl.Publish(&client.PublishOptions{ QoS: mqtt.QoS1, TopicName: []byte("afa-systems/presence/changes"), Message: js, }) if err != nil { log.Printf("mqtt publish error: %v", err) } } func appendHTTPResult(ctx *model.AppContext, b model.Beacon, best model.BestLocation) { ctx.HTTPResults.HTTPResultsLock.Lock() defer ctx.HTTPResults.HTTPResultsLock.Unlock() r := model.HTTPLocation{ Name: b.Name, Beacon_id: b.Beacon_id, Location: best.Name, Distance: best.Distance, Last_seen: best.Last_seen, } ctx.HTTPResults.HTTPResults.Beacons = append(ctx.HTTPResults.HTTPResults.Beacons, r) }