No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 

249 líneas
6.3 KiB

  1. package main
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "math"
  7. "strconv"
  8. "time"
  9. "github.com/AFASystems/presence/internal/pkg/config"
  10. "github.com/AFASystems/presence/internal/pkg/kafkaclient"
  11. "github.com/AFASystems/presence/internal/pkg/model"
  12. "github.com/segmentio/kafka-go"
  13. )
  14. func main() {
  15. // Load global context to init beacons and latest list
  16. appCtx := model.AppContext{
  17. Settings: model.Settings{
  18. Settings: model.SettingsVal{
  19. LocationConfidence: 4,
  20. LastSeenThreshold: 15,
  21. BeaconMetricSize: 30,
  22. HASendInterval: 5,
  23. HASendChangesOnly: false,
  24. RSSIEnforceThreshold: true,
  25. RSSIMinThreshold: 100,
  26. },
  27. },
  28. BeaconsLookup: make(map[string]struct{}),
  29. LatestList: model.LatestBeaconsList{
  30. LatestList: make(map[string]model.Beacon),
  31. },
  32. }
  33. cfg := config.Load()
  34. // Kafka reader for Raw MQTT beacons
  35. rawReader := kafkaclient.KafkaReader(cfg.KafkaURL, "rawbeacons", "gid-raw-loc")
  36. defer rawReader.Close()
  37. // Kafka reader for API server updates
  38. apiReader := kafkaclient.KafkaReader(cfg.KafkaURL, "apibeacons", "gid-api-loc")
  39. defer apiReader.Close()
  40. writer := kafkaclient.KafkaWriter(cfg.KafkaURL, "locevents")
  41. defer writer.Close()
  42. fmt.Println("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. go kafkaclient.Consume(rawReader, chRaw)
  48. go kafkaclient.Consume(apiReader, chApi)
  49. for {
  50. select {
  51. case <-locTicker.C:
  52. getLikelyLocations(&appCtx, writer)
  53. case msg := <-chRaw:
  54. assignBeaconToList(msg, &appCtx)
  55. case msg := <-chApi:
  56. switch msg.Method {
  57. case "POST":
  58. id := msg.Beacon.ID
  59. appCtx.BeaconsLookup[id] = struct{}{}
  60. case "DELETE":
  61. fmt.Println("Incoming delete message")
  62. }
  63. }
  64. }
  65. }
  66. func getLikelyLocations(ctx *model.AppContext, writer *kafka.Writer) {
  67. ctx.Beacons.Lock.Lock()
  68. beacons := ctx.Beacons.Beacons
  69. for _, beacon := range beacons {
  70. // Shrinking the model because other properties have nothing to do with the location
  71. r := model.HTTPLocation{
  72. Method: "Standard",
  73. Distance: 999,
  74. Name: beacon.Name,
  75. ID: beacon.ID,
  76. Location: "",
  77. LastSeen: 999,
  78. }
  79. mSize := len(beacon.BeaconMetrics)
  80. if (int64(time.Now().Unix()) - (beacon.BeaconMetrics[mSize-1].Timestamp)) > ctx.Settings.Settings.LastSeenThreshold {
  81. continue
  82. }
  83. locList := make(map[string]float64)
  84. seenW := 1.5
  85. rssiW := 0.75
  86. for _, metric := range beacon.BeaconMetrics {
  87. res := seenW + (rssiW * (1.0 - (float64(metric.RSSI) / -100.0)))
  88. locList[metric.Location] += res
  89. }
  90. bestLocName := ""
  91. maxScore := 0.0
  92. for locName, score := range locList {
  93. if score > maxScore {
  94. maxScore = score
  95. bestLocName = locName
  96. }
  97. }
  98. bestLocation := model.BestLocation{
  99. Name: bestLocName,
  100. Distance: beacon.BeaconMetrics[mSize-1].Distance,
  101. LastSeen: beacon.BeaconMetrics[mSize-1].Timestamp,
  102. }
  103. if bestLocName == beacon.PreviousLocation {
  104. beacon.LocationConfidence++
  105. } else {
  106. beacon.LocationConfidence = 0
  107. }
  108. r.Distance = bestLocation.Distance
  109. r.Location = bestLocName
  110. r.LastSeen = bestLocation.LastSeen
  111. if beacon.LocationConfidence == ctx.Settings.Settings.LocationConfidence && beacon.PreviousConfidentLocation != bestLocName {
  112. beacon.LocationConfidence = 0
  113. // Who do I need this if I am sending entire structure anyways? who knows
  114. js, err := json.Marshal(model.LocationChange{
  115. Method: "LocationChange",
  116. BeaconRef: beacon,
  117. Name: beacon.Name,
  118. PreviousLocation: beacon.PreviousConfidentLocation,
  119. NewLocation: bestLocName,
  120. Timestamp: time.Now().Unix(),
  121. })
  122. if err != nil {
  123. beacon.PreviousConfidentLocation = bestLocName
  124. beacon.PreviousLocation = bestLocName
  125. ctx.Beacons.Beacons[beacon.ID] = beacon
  126. continue
  127. }
  128. msg := kafka.Message{
  129. Value: js,
  130. }
  131. err = writer.WriteMessages(context.Background(), msg)
  132. if err != nil {
  133. fmt.Println("Error in sending Kafka message")
  134. }
  135. }
  136. beacon.PreviousLocation = bestLocName
  137. ctx.Beacons.Beacons[beacon.ID] = beacon
  138. js, err := json.Marshal(r)
  139. if err != nil {
  140. continue
  141. }
  142. msg := kafka.Message{
  143. Value: js,
  144. }
  145. err = writer.WriteMessages(context.Background(), msg)
  146. if err != nil {
  147. fmt.Println("Error in sending Kafka message")
  148. }
  149. }
  150. }
  151. func assignBeaconToList(adv model.BeaconAdvertisement, ctx *model.AppContext) {
  152. id := adv.MAC
  153. _, ok := ctx.BeaconsLookup[id]
  154. now := time.Now().Unix()
  155. if !ok {
  156. // handle removing from the list somewhere else, probably at the point where this is being used which is nowhere in the original code
  157. ctx.LatestList.Lock.Lock()
  158. ctx.LatestList.LatestList[id] = model.Beacon{ID: id, BeaconType: adv.BeaconType, LastSeen: now, IncomingJSON: adv, BeaconLocation: adv.Hostname, Distance: getBeaconDistance(adv)}
  159. ctx.LatestList.Lock.Unlock()
  160. return
  161. }
  162. if ctx.Settings.Settings.RSSIEnforceThreshold && (int64(adv.RSSI) < ctx.Settings.Settings.RSSIMinThreshold) {
  163. return
  164. }
  165. ctx.Beacons.Lock.Lock()
  166. beacon := ctx.Beacons.Beacons[id]
  167. beacon.IncomingJSON = adv
  168. beacon.LastSeen = now
  169. beacon.BeaconType = adv.BeaconType
  170. beacon.HSButtonCounter = adv.HSButtonCounter
  171. beacon.HSBattery = adv.HSBatteryLevel
  172. beacon.HSRandomNonce = adv.HSRandomNonce
  173. beacon.HSButtonMode = adv.HSButtonMode
  174. if beacon.BeaconMetrics == nil {
  175. beacon.BeaconMetrics = make([]model.BeaconMetric, 0, ctx.Settings.Settings.BeaconMetricSize)
  176. }
  177. metric := model.BeaconMetric{
  178. Distance: getBeaconDistance(adv),
  179. Timestamp: now,
  180. RSSI: int64(adv.RSSI),
  181. Location: adv.Hostname,
  182. }
  183. if len(beacon.BeaconMetrics) >= ctx.Settings.Settings.BeaconMetricSize {
  184. copy(beacon.BeaconMetrics, beacon.BeaconMetrics[1:])
  185. beacon.BeaconMetrics[ctx.Settings.Settings.BeaconMetricSize-1] = metric
  186. } else {
  187. beacon.BeaconMetrics = append(beacon.BeaconMetrics, metric)
  188. }
  189. ctx.Beacons.Beacons[id] = beacon
  190. ctx.Beacons.Lock.Unlock()
  191. }
  192. func getBeaconDistance(adv model.BeaconAdvertisement) float64 {
  193. ratio := float64(adv.RSSI) * (1.0 / float64(twosComp(adv.TXPower)))
  194. distance := 100.0
  195. if ratio < 1.0 {
  196. distance = math.Pow(ratio, 10)
  197. } else {
  198. distance = (0.89976)*math.Pow(ratio, 7.7095) + 0.111
  199. }
  200. return distance
  201. }
  202. func twosComp(inp string) int64 {
  203. i, _ := strconv.ParseInt("0x"+inp, 0, 64)
  204. return i - 256
  205. }