package main import ( "bytes" "context" "encoding/binary" "encoding/hex" "encoding/json" "fmt" "strings" "github.com/AFASystems/presence/internal/pkg/config" "github.com/AFASystems/presence/internal/pkg/kafkaclient" "github.com/AFASystems/presence/internal/pkg/model" "github.com/segmentio/kafka-go" ) func main() { // Load global context to init beacons and latest list appCtx := model.AppContext{ Beacons: model.BeaconsList{ Beacons: make(map[string]model.Beacon), }, Settings: model.Settings{ Settings: model.SettingsVal{ LocationConfidence: 4, LastSeenThreshold: 15, BeaconMetricSize: 30, HASendInterval: 5, HASendChangesOnly: false, }, }, BeaconEvents: model.BeaconEventList{ Beacons: make(map[string]model.BeaconEvent), }, BeaconsLookup: make(map[string]struct{}), } cfg := config.Load() // Kafka reader for Raw MQTT beacons rawReader := kafkaclient.KafkaReader(cfg.KafkaURL, "rawbeacons", "gid-raw") defer rawReader.Close() // Kafka reader for API server updates apiReader := kafkaclient.KafkaReader(cfg.KafkaURL, "apibeacons", "gid-api") defer apiReader.Close() alertWriter := kafkaclient.KafkaWriter(cfg.KafkaURL, "alertbeacons") defer alertWriter.Close() fmt.Println("Decoder initialized, subscribed to Kafka topics") chRaw := make(chan model.BeaconAdvertisement, 2000) chApi := make(chan model.ApiUpdate, 2000) go kafkaclient.Consume(rawReader, chRaw) go kafkaclient.Consume(apiReader, chApi) for { select { case msg := <-chRaw: processIncoming(msg, &appCtx, alertWriter) case msg := <-chApi: switch msg.Method { case "POST": id := msg.Beacon.ID appCtx.BeaconsLookup[id] = struct{}{} case "DELETE": fmt.Println("Incoming delete message") } } } } func processIncoming(adv model.BeaconAdvertisement, ctx *model.AppContext, writer *kafka.Writer) { id := adv.MAC _, ok := ctx.BeaconsLookup[id] if !ok { return } err := decodeBeacon(adv, ctx, writer) if err != nil { fmt.Println("error in decoding") return } } func decodeBeacon(adv model.BeaconAdvertisement, ctx *model.AppContext, writer *kafka.Writer) error { beacon := strings.TrimSpace(adv.Data) id := adv.MAC if beacon == "" { return nil // How to return error?, do I even need to return error } b, err := hex.DecodeString(beacon) if err != nil { return err } // check for flag byte, if first AD structure is flag bytes, remove it if len(b) > 1 && b[1] == 0x01 { l := int(b[0]) // length of AD structure if 1+l <= len(b) { b = b[1+l:] } } adStructureIndeces := ParseADFast(b) event := model.BeaconEvent{} for _, r := range adStructureIndeces { ad := b[r[0]:r[1]] if checkIngics(ad) { event = parseIngicsState(ad) event.ID = id event.Name = id break } else if checkEddystoneTLM(ad) { event = parseEddystoneState(ad) event.ID = id event.Name = id break } else if checkMinewB7(ad) { fmt.Println("Minew B7 vendor format") break } } if event.ID != "" { prevEvent, ok := ctx.BeaconEvents.Beacons[id] ctx.BeaconEvents.Beacons[id] = event if ok && bytes.Equal(prevEvent.Hash(), event.Hash()) { return nil } eMsg, err := json.Marshal(event) if err != nil { return err } err = writer.WriteMessages(context.Background(), kafka.Message{ Value: eMsg, }) if err != nil { return err } fmt.Println("Message sent") } return nil } func checkIngics(ad []byte) bool { if len(ad) >= 6 && ad[1] == 0xFF && ad[2] == 0x59 && ad[3] == 0x00 && ad[4] == 0x80 && ad[5] == 0xBC { return true } return false } func parseIngicsState(ad []byte) model.BeaconEvent { return model.BeaconEvent{ Battery: uint32(binary.LittleEndian.Uint16(ad[6:8])), Event: int(ad[8]), Type: "Ingics", } } func checkEddystoneTLM(ad []byte) bool { if len(ad) >= 4 && ad[1] == 0x16 && ad[2] == 0xAA && ad[3] == 0xFE && ad[4] == 0x20 { return true } return false } func parseEddystoneState(ad []byte) model.BeaconEvent { return model.BeaconEvent{ Battery: uint32(binary.BigEndian.Uint16(ad[6:8])), Type: "Eddystone", } } // I dont think this is always true, but for testing is ok func checkMinewB7(ad []byte) bool { if len(ad) >= 4 && ad[1] == 0x16 && ad[2] == 0xE1 && ad[3] == 0xFF { return true } return false } func ParseADFast(b []byte) [][2]int { var res [][2]int i := 0 for i < len(b) { l := int(b[i]) if l == 0 || i+1+l > len(b) { break } res = append(res, [2]int{i, i + 1 + l}) i += 1 + l } return res }