25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 

254 satır
6.5 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. fmt.Println("get likely locations called")
  68. ctx.Beacons.Lock.Lock()
  69. beacons := ctx.Beacons.Beacons
  70. ctx.Beacons.Lock.Unlock()
  71. for _, beacon := range beacons {
  72. fmt.Printf("beacon: %+v", beacon)
  73. // Shrinking the model because other properties have nothing to do with the location
  74. r := model.HTTPLocation{
  75. Method: "Standard",
  76. Distance: 999,
  77. Name: beacon.Name,
  78. ID: beacon.ID,
  79. Location: "",
  80. LastSeen: 999,
  81. }
  82. mSize := len(beacon.BeaconMetrics)
  83. if (int64(time.Now().Unix()) - (beacon.BeaconMetrics[mSize-1].Timestamp)) > ctx.Settings.Settings.LastSeenThreshold {
  84. continue
  85. }
  86. locList := make(map[string]float64)
  87. seenW := 1.5
  88. rssiW := 0.75
  89. for _, metric := range beacon.BeaconMetrics {
  90. res := seenW + (rssiW * (1.0 - (float64(metric.RSSI) / -100.0)))
  91. locList[metric.Location] += res
  92. }
  93. bestLocName := ""
  94. maxScore := 0.0
  95. for locName, score := range locList {
  96. if score > maxScore {
  97. maxScore = score
  98. bestLocName = locName
  99. }
  100. }
  101. if bestLocName == beacon.PreviousLocation {
  102. beacon.LocationConfidence++
  103. } else {
  104. beacon.LocationConfidence = 0
  105. }
  106. r.Distance = beacon.BeaconMetrics[mSize-1].Distance
  107. r.Location = bestLocName
  108. r.LastSeen = beacon.BeaconMetrics[mSize-1].Timestamp
  109. if beacon.LocationConfidence == ctx.Settings.Settings.LocationConfidence && beacon.PreviousConfidentLocation != bestLocName {
  110. beacon.LocationConfidence = 0
  111. // Who do I need this if I am sending entire structure anyways? who knows
  112. js, err := json.Marshal(model.LocationChange{
  113. Method: "LocationChange",
  114. BeaconRef: beacon,
  115. Name: beacon.Name,
  116. PreviousLocation: beacon.PreviousConfidentLocation,
  117. NewLocation: bestLocName,
  118. Timestamp: time.Now().Unix(),
  119. })
  120. if err != nil {
  121. beacon.PreviousConfidentLocation = bestLocName
  122. beacon.PreviousLocation = bestLocName
  123. ctx.Beacons.Lock.Lock()
  124. ctx.Beacons.Beacons[beacon.ID] = beacon
  125. ctx.Beacons.Lock.Unlock()
  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.Lock.Lock()
  138. ctx.Beacons.Beacons[beacon.ID] = beacon
  139. ctx.Beacons.Lock.Unlock()
  140. js, err := json.Marshal(r)
  141. if err != nil {
  142. continue
  143. }
  144. msg := kafka.Message{
  145. Value: js,
  146. }
  147. err = writer.WriteMessages(context.Background(), msg)
  148. if err != nil {
  149. fmt.Println("Error in sending Kafka message")
  150. }
  151. }
  152. }
  153. func assignBeaconToList(adv model.BeaconAdvertisement, ctx *model.AppContext) {
  154. id := adv.MAC
  155. _, ok := ctx.BeaconsLookup[id]
  156. now := time.Now().Unix()
  157. if !ok {
  158. // handle removing from the list somewhere else, probably at the point where this is being used which is nowhere in the original code
  159. ctx.LatestList.Lock.Lock()
  160. ctx.LatestList.LatestList[id] = model.Beacon{ID: id, BeaconType: adv.BeaconType, LastSeen: now, IncomingJSON: adv, BeaconLocation: adv.Hostname, Distance: getBeaconDistance(adv)}
  161. ctx.LatestList.Lock.Unlock()
  162. return
  163. }
  164. fmt.Println("Beacon exists: ", id)
  165. if ctx.Settings.Settings.RSSIEnforceThreshold && (int64(adv.RSSI) < ctx.Settings.Settings.RSSIMinThreshold) {
  166. return
  167. }
  168. ctx.Beacons.Lock.Lock()
  169. beacon := ctx.Beacons.Beacons[id]
  170. ctx.Beacons.Lock.Unlock()
  171. beacon.IncomingJSON = adv
  172. beacon.LastSeen = now
  173. beacon.BeaconType = adv.BeaconType
  174. beacon.HSButtonCounter = adv.HSButtonCounter
  175. beacon.HSBattery = adv.HSBatteryLevel
  176. beacon.HSRandomNonce = adv.HSRandomNonce
  177. beacon.HSButtonMode = adv.HSButtonMode
  178. if beacon.BeaconMetrics == nil {
  179. beacon.BeaconMetrics = make([]model.BeaconMetric, 0, ctx.Settings.Settings.BeaconMetricSize)
  180. }
  181. metric := model.BeaconMetric{
  182. Distance: getBeaconDistance(adv),
  183. Timestamp: now,
  184. RSSI: int64(adv.RSSI),
  185. Location: adv.Hostname,
  186. }
  187. if len(beacon.BeaconMetrics) >= ctx.Settings.Settings.BeaconMetricSize {
  188. copy(beacon.BeaconMetrics, beacon.BeaconMetrics[1:])
  189. beacon.BeaconMetrics[ctx.Settings.Settings.BeaconMetricSize-1] = metric
  190. } else {
  191. beacon.BeaconMetrics = append(beacon.BeaconMetrics, metric)
  192. }
  193. ctx.Beacons.Lock.Lock()
  194. ctx.Beacons.Beacons[id] = beacon
  195. ctx.Beacons.Lock.Unlock()
  196. }
  197. func getBeaconDistance(adv model.BeaconAdvertisement) float64 {
  198. ratio := float64(adv.RSSI) * (1.0 / float64(twosComp(adv.TXPower)))
  199. distance := 100.0
  200. if ratio < 1.0 {
  201. distance = math.Pow(ratio, 10)
  202. } else {
  203. distance = (0.89976)*math.Pow(ratio, 7.7095) + 0.111
  204. }
  205. return distance
  206. }
  207. func twosComp(inp string) int64 {
  208. i, _ := strconv.ParseInt("0x"+inp, 0, 64)
  209. return i - 256
  210. }