You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

140 lines
4.3 KiB

  1. package location
  2. import (
  3. "context"
  4. "encoding/json"
  5. "log/slog"
  6. "sync"
  7. "time"
  8. "github.com/AFASystems/presence/internal/pkg/common/appcontext"
  9. "github.com/AFASystems/presence/internal/pkg/config"
  10. "github.com/AFASystems/presence/internal/pkg/kafkaclient"
  11. pkglocation "github.com/AFASystems/presence/internal/pkg/location"
  12. "github.com/AFASystems/presence/internal/pkg/logger"
  13. "github.com/AFASystems/presence/internal/pkg/model"
  14. "github.com/segmentio/kafka-go"
  15. )
  16. // LocationApp holds dependencies for the location service.
  17. type LocationApp struct {
  18. Cfg *config.Config
  19. KafkaManager *kafkaclient.KafkaManager
  20. AppState *appcontext.AppState
  21. Inferencer pkglocation.Inferencer
  22. ChRaw chan appcontext.BeaconAdvertisement
  23. ChSettings chan map[string]any
  24. Cleanup func()
  25. wg sync.WaitGroup
  26. }
  27. // New creates a LocationApp with Kafka readers (rawbeacons, settings) and writer (locevents).
  28. func New(cfg *config.Config) (*LocationApp, error) {
  29. appState := appcontext.NewAppState()
  30. kafkaManager := kafkaclient.InitKafkaManager()
  31. srvLogger, cleanup := logger.CreateLogger("location.log")
  32. slog.SetDefault(srvLogger)
  33. readerTopics := []string{"rawbeacons", "settings"}
  34. writerTopics := []string{"locevents", "healthlocation"}
  35. kafkaManager.PopulateKafkaManager(cfg.KafkaURL, "location", readerTopics)
  36. kafkaManager.PopulateKafkaManager(cfg.KafkaURL, "", writerTopics)
  37. slog.Info("location service initialized", "readers", readerTopics, "writers", writerTopics)
  38. return &LocationApp{
  39. Cfg: cfg,
  40. KafkaManager: kafkaManager,
  41. AppState: appState,
  42. Inferencer: pkglocation.NewDefaultInferencer(cfg.TLSInsecureSkipVerify),
  43. ChRaw: make(chan appcontext.BeaconAdvertisement, config.LARGE_CHANNEL_SIZE),
  44. ChSettings: make(chan map[string]any, config.SMALL_CHANNEL_SIZE),
  45. Cleanup: cleanup,
  46. }, nil
  47. }
  48. // Run starts consumers and the event loop until ctx is cancelled.
  49. func (a *LocationApp) Run(ctx context.Context) {
  50. a.wg.Add(2)
  51. go kafkaclient.Consume(a.KafkaManager.GetReader("rawbeacons"), a.ChRaw, ctx, &a.wg)
  52. go kafkaclient.Consume(a.KafkaManager.GetReader("settings"), a.ChSettings, ctx, &a.wg)
  53. locTicker := time.NewTicker(config.LARGE_TICKER_INTERVAL)
  54. defer locTicker.Stop()
  55. healthTicker := time.NewTicker(config.LARGE_TICKER_INTERVAL)
  56. defer healthTicker.Stop()
  57. for {
  58. select {
  59. case <-ctx.Done():
  60. return
  61. case <-healthTicker.C:
  62. health, err := a.AppState.GetLocationHealth(a.KafkaManager)
  63. if err != nil {
  64. slog.Error("getting location health", "err", err)
  65. continue
  66. }
  67. m := kafka.Message{
  68. Value: health,
  69. }
  70. if err := kafkaclient.Write(ctx, a.KafkaManager.GetWriter("healthlocation"), m); err != nil {
  71. slog.Error("writing location health", "err", err)
  72. continue
  73. }
  74. case <-locTicker.C:
  75. settings := a.AppState.GetSettings()
  76. slog.Info("current algorithm", "algorithm", settings.CurrentAlgorithm)
  77. switch settings.CurrentAlgorithm {
  78. case "filter":
  79. pkglocation.GetLikelyLocations(a.AppState, a.KafkaManager.GetWriter("locevents"))
  80. case "ai":
  81. inferred, err := a.Inferencer.Infer(ctx, a.Cfg)
  82. if err != nil {
  83. slog.Error("AI inference", "err", err)
  84. continue
  85. }
  86. for _, item := range inferred.Items {
  87. r := model.HTTPLocation{
  88. Method: "AI",
  89. Y: item.Y,
  90. X: item.X,
  91. Z: item.Z,
  92. MAC: item.Mac,
  93. LastSeen: time.Now().Unix(),
  94. }
  95. js, err := json.Marshal(r)
  96. if err != nil {
  97. slog.Error("marshaling location", "err", err, "beacon_id", item.Mac)
  98. continue
  99. }
  100. if err := a.KafkaManager.GetWriter("locevents").WriteMessages(ctx, kafka.Message{Value: js}); err != nil {
  101. slog.Error("sending kafka location message", "err", err, "beacon_id", item.Mac)
  102. }
  103. }
  104. slog.Info("AI algorithm", "count", inferred.Count, "items", len(inferred.Items))
  105. }
  106. case msg := <-a.ChRaw:
  107. pkglocation.AssignBeaconToList(msg, a.AppState)
  108. case msg := <-a.ChSettings:
  109. slog.Info("settings update", "msg", msg)
  110. a.AppState.UpdateSettings(msg)
  111. }
  112. }
  113. }
  114. // Shutdown waits for consumers and cleans up Kafka and logger.
  115. func (a *LocationApp) Shutdown() {
  116. a.wg.Wait()
  117. a.KafkaManager.CleanKafkaReaders()
  118. a.KafkaManager.CleanKafkaWriters()
  119. if a.Cleanup != nil {
  120. a.Cleanup()
  121. }
  122. slog.Info("location service shutdown complete")
  123. }