Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

249 linhas
6.4 KiB

  1. package main
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "log"
  8. "log/slog"
  9. "os"
  10. "os/signal"
  11. "sync"
  12. "syscall"
  13. "time"
  14. "github.com/AFASystems/presence/internal/pkg/common/appcontext"
  15. "github.com/AFASystems/presence/internal/pkg/common/utils"
  16. "github.com/AFASystems/presence/internal/pkg/config"
  17. "github.com/AFASystems/presence/internal/pkg/kafkaclient"
  18. "github.com/AFASystems/presence/internal/pkg/model"
  19. "github.com/segmentio/kafka-go"
  20. )
  21. var wg sync.WaitGroup
  22. func main() {
  23. // Load global context to init beacons and latest list
  24. appState := appcontext.NewAppState()
  25. cfg := config.Load()
  26. // Create log file
  27. logFile, err := os.OpenFile("server.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
  28. if err != nil {
  29. log.Fatalf("Failed to open log file: %v\n", err)
  30. }
  31. // shell and log file multiwriter
  32. w := io.MultiWriter(os.Stderr, logFile)
  33. logger := slog.New(slog.NewJSONHandler(w, nil))
  34. slog.SetDefault(logger)
  35. // Define context
  36. ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
  37. defer stop()
  38. rawReader := appState.AddKafkaReader(cfg.KafkaURL, "rawbeacons", "gid-raw-loc")
  39. apiReader := appState.AddKafkaReader(cfg.KafkaURL, "apibeacons", "gid-api-loc")
  40. settingsReader := appState.AddKafkaReader(cfg.KafkaURL, "settings", "gid-settings-loc")
  41. writer := appState.AddKafkaWriter(cfg.KafkaURL, "locevents")
  42. slog.Info("Locations algorithm initialized, subscribed to Kafka topics")
  43. locTicker := time.NewTicker(1 * time.Second)
  44. defer locTicker.Stop()
  45. chRaw := make(chan model.BeaconAdvertisement, 2000)
  46. chApi := make(chan model.ApiUpdate, 2000)
  47. chSettings := make(chan model.SettingsVal, 5)
  48. wg.Add(3)
  49. go kafkaclient.Consume(rawReader, chRaw, ctx, &wg)
  50. go kafkaclient.Consume(apiReader, chApi, ctx, &wg)
  51. go kafkaclient.Consume(settingsReader, chSettings, ctx, &wg)
  52. eventLoop:
  53. for {
  54. select {
  55. case <-ctx.Done():
  56. break eventLoop
  57. case <-locTicker.C:
  58. getLikelyLocations(appState, writer)
  59. case msg := <-chRaw:
  60. assignBeaconToList(msg, appState)
  61. case msg := <-chApi:
  62. switch msg.Method {
  63. case "POST":
  64. id := msg.Beacon.ID
  65. lMsg := fmt.Sprintf("Beacon added to lookup: %s", id)
  66. slog.Info(lMsg)
  67. appState.AddBeaconToLookup(id)
  68. case "DELETE":
  69. id := msg.Beacon.ID
  70. appState.RemoveBeaconFromLookup(id)
  71. lMsg := fmt.Sprintf("Beacon removed from lookup: %s", id)
  72. slog.Info(lMsg)
  73. }
  74. case msg := <-chSettings:
  75. appState.UpdateSettings(msg)
  76. }
  77. }
  78. slog.Info("broken out of the main event loop")
  79. wg.Wait()
  80. slog.Info("All go routines have stopped, Beggining to close Kafka connections")
  81. appState.CleanKafkaReaders()
  82. appState.CleanKafkaWriters()
  83. }
  84. func getLikelyLocations(appState *appcontext.AppState, writer *kafka.Writer) {
  85. beacons := appState.GetAllBeacons()
  86. settings := appState.GetSettingsValue()
  87. for _, beacon := range beacons {
  88. // Shrinking the model because other properties have nothing to do with the location
  89. r := model.HTTPLocation{
  90. Method: "Standard",
  91. Distance: 999,
  92. ID: beacon.ID,
  93. Location: "",
  94. LastSeen: 999,
  95. }
  96. mSize := len(beacon.BeaconMetrics)
  97. if (int64(time.Now().Unix()) - (beacon.BeaconMetrics[mSize-1].Timestamp)) > settings.LastSeenThreshold {
  98. slog.Warn("beacon is too old")
  99. continue
  100. }
  101. locList := make(map[string]float64)
  102. seenW := 1.5
  103. rssiW := 0.75
  104. for _, metric := range beacon.BeaconMetrics {
  105. res := seenW + (rssiW * (1.0 - (float64(metric.RSSI) / -100.0)))
  106. locList[metric.Location] += res
  107. }
  108. bestLocName := ""
  109. maxScore := 0.0
  110. for locName, score := range locList {
  111. if score > maxScore {
  112. maxScore = score
  113. bestLocName = locName
  114. }
  115. }
  116. if bestLocName == beacon.PreviousLocation {
  117. beacon.LocationConfidence++
  118. } else {
  119. beacon.LocationConfidence = 0
  120. }
  121. r.Distance = beacon.BeaconMetrics[mSize-1].Distance
  122. r.Location = bestLocName
  123. r.LastSeen = beacon.BeaconMetrics[mSize-1].Timestamp
  124. if beacon.LocationConfidence == settings.LocationConfidence && beacon.PreviousConfidentLocation != bestLocName {
  125. beacon.LocationConfidence = 0
  126. // Why do I need this if I am sending entire structure anyways? who knows
  127. js, err := json.Marshal(model.LocationChange{
  128. Method: "LocationChange",
  129. BeaconRef: beacon,
  130. Name: beacon.Name,
  131. PreviousLocation: beacon.PreviousConfidentLocation,
  132. NewLocation: bestLocName,
  133. Timestamp: time.Now().Unix(),
  134. })
  135. if err != nil {
  136. eMsg := fmt.Sprintf("Error in marshaling: %v", err)
  137. slog.Error(eMsg)
  138. beacon.PreviousConfidentLocation = bestLocName
  139. beacon.PreviousLocation = bestLocName
  140. appState.UpdateBeacon(beacon.ID, beacon)
  141. continue
  142. }
  143. msg := kafka.Message{
  144. Value: js,
  145. }
  146. err = writer.WriteMessages(context.Background(), msg)
  147. if err != nil {
  148. fmt.Println("Error in sending Kafka message")
  149. }
  150. }
  151. beacon.PreviousLocation = bestLocName
  152. appState.UpdateBeacon(beacon.ID, beacon)
  153. js, err := json.Marshal(r)
  154. if err != nil {
  155. eMsg := fmt.Sprintf("Error in marshaling location: %v", err)
  156. slog.Error(eMsg)
  157. continue
  158. }
  159. msg := kafka.Message{
  160. Value: js,
  161. }
  162. err = writer.WriteMessages(context.Background(), msg)
  163. if err != nil {
  164. eMsg := fmt.Sprintf("Error in sending Kafka message: %v", err)
  165. slog.Error(eMsg)
  166. }
  167. }
  168. }
  169. func assignBeaconToList(adv model.BeaconAdvertisement, appState *appcontext.AppState) {
  170. id := adv.MAC
  171. ok := appState.BeaconExists(id)
  172. now := time.Now().Unix()
  173. if !ok {
  174. appState.UpdateLatestBeacon(id, model.Beacon{ID: id, BeaconType: adv.BeaconType, LastSeen: now, IncomingJSON: adv, BeaconLocation: adv.Hostname, Distance: utils.CalculateDistance(adv)})
  175. return
  176. }
  177. settings := appState.GetSettingsValue()
  178. if settings.RSSIEnforceThreshold && (int64(adv.RSSI) < settings.RSSIMinThreshold) {
  179. slog.Info("Settings returns")
  180. return
  181. }
  182. beacon, ok := appState.GetBeacon(id)
  183. if !ok {
  184. beacon = model.Beacon{
  185. ID: id,
  186. }
  187. }
  188. beacon.IncomingJSON = adv
  189. beacon.LastSeen = now
  190. if beacon.BeaconMetrics == nil {
  191. beacon.BeaconMetrics = make([]model.BeaconMetric, 0, settings.BeaconMetricSize)
  192. }
  193. metric := model.BeaconMetric{
  194. Distance: utils.CalculateDistance(adv),
  195. Timestamp: now,
  196. RSSI: int64(adv.RSSI),
  197. Location: adv.Hostname,
  198. }
  199. if len(beacon.BeaconMetrics) >= settings.BeaconMetricSize {
  200. copy(beacon.BeaconMetrics, beacon.BeaconMetrics[1:])
  201. beacon.BeaconMetrics[settings.BeaconMetricSize-1] = metric
  202. } else {
  203. beacon.BeaconMetrics = append(beacon.BeaconMetrics, metric)
  204. }
  205. appState.UpdateBeacon(id, beacon)
  206. }