| @@ -0,0 +1,37 @@ | |||
| [ | |||
| { | |||
| "name": "Ingics", | |||
| "min": 4, | |||
| "max": 255, | |||
| "pattern": ["0xFF", "0x59", "0x00", "0x80", "0xBC"], | |||
| "configs": { | |||
| "battery": {"offset": 6, "length": 2, "order": "littleendian"}, | |||
| "event": {"offset": 8, "length": 1} | |||
| } | |||
| }, | |||
| { | |||
| "name": "Eddystone", | |||
| "min": 4, | |||
| "max": 255, | |||
| "pattern": ["0x16", "0xAA", "0xFE", "0x20"], | |||
| "configs": { | |||
| "battery": {"offset": 6, "length": 2, "order": "bigendian"} | |||
| } | |||
| }, | |||
| { | |||
| "name": "Minew B7", | |||
| "min": 4, | |||
| "max": 18, | |||
| "pattern": ["0x16", "0xE1", "0xFF"], | |||
| "configs": { | |||
| "battery": {"offset": 6, "length": 1} | |||
| } | |||
| }, | |||
| { | |||
| "name": "Minew Acc", | |||
| "min": 19, | |||
| "max": 19, | |||
| "pattern": ["0x16", "0xE1", "0xFF"], | |||
| "configs": {} | |||
| } | |||
| ] | |||
| @@ -4,6 +4,7 @@ import ( | |||
| "bytes" | |||
| "context" | |||
| "encoding/hex" | |||
| "encoding/json" | |||
| "fmt" | |||
| "io" | |||
| "log" | |||
| @@ -29,6 +30,24 @@ func main() { | |||
| appState := appcontext.NewAppState() | |||
| cfg := config.Load() | |||
| parserRegistry := model.ParserRegistry{ | |||
| ParserList: make([]model.BeaconParser, 0), | |||
| } | |||
| configFile, err := os.Open("/app/cmd/decoder/config.json") | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| b, _ := io.ReadAll(configFile) | |||
| var configs []model.Config | |||
| json.Unmarshal(b, &configs) | |||
| for _, config := range configs { | |||
| parserRegistry.Register(config.Name, config) | |||
| } | |||
| // Create log file | |||
| logFile, err := os.OpenFile("server.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) | |||
| if err != nil { | |||
| @@ -60,7 +79,7 @@ eventloop: | |||
| case <-ctx.Done(): | |||
| break eventloop | |||
| case msg := <-chRaw: | |||
| processIncoming(msg, appState, alertWriter) | |||
| processIncoming(msg, appState, alertWriter, &parserRegistry) | |||
| } | |||
| } | |||
| @@ -72,8 +91,8 @@ eventloop: | |||
| appState.CleanKafkaWriters() | |||
| } | |||
| func processIncoming(adv model.BeaconAdvertisement, appState *appcontext.AppState, writer *kafka.Writer) { | |||
| err := decodeBeacon(adv, appState, writer) | |||
| func processIncoming(adv model.BeaconAdvertisement, appState *appcontext.AppState, writer *kafka.Writer, parserRegistry *model.ParserRegistry) { | |||
| err := decodeBeacon(adv, appState, writer, parserRegistry) | |||
| if err != nil { | |||
| eMsg := fmt.Sprintf("Error in decoding: %v", err) | |||
| fmt.Println(eMsg) | |||
| @@ -81,7 +100,7 @@ func processIncoming(adv model.BeaconAdvertisement, appState *appcontext.AppStat | |||
| } | |||
| } | |||
| func decodeBeacon(adv model.BeaconAdvertisement, appState *appcontext.AppState, writer *kafka.Writer) error { | |||
| func decodeBeacon(adv model.BeaconAdvertisement, appState *appcontext.AppState, writer *kafka.Writer, parserRegistry *model.ParserRegistry) error { | |||
| beacon := strings.TrimSpace(adv.Data) | |||
| id := adv.ID | |||
| if beacon == "" { | |||
| @@ -96,7 +115,7 @@ func decodeBeacon(adv model.BeaconAdvertisement, appState *appcontext.AppState, | |||
| b = utils.RemoveFlagBytes(b) | |||
| indeces := utils.ParseADFast(b) | |||
| event := utils.LoopADStructures(b, indeces, id) | |||
| event := utils.LoopADStructures(b, indeces, id, parserRegistry) | |||
| if event.ID == "" { | |||
| return nil | |||
| @@ -1,9 +1,6 @@ | |||
| package utils | |||
| import ( | |||
| "encoding/binary" | |||
| "fmt" | |||
| "github.com/AFASystems/presence/internal/pkg/model" | |||
| ) | |||
| @@ -40,30 +37,22 @@ func RemoveFlagBytes(b []byte) []byte { | |||
| } | |||
| // Generate event based on the Beacon type | |||
| func LoopADStructures(b []byte, i [][2]int, id string) model.BeaconEvent { | |||
| func LoopADStructures(b []byte, i [][2]int, id string, parserRegistry *model.ParserRegistry) model.BeaconEvent { | |||
| be := model.BeaconEvent{} | |||
| for _, r := range i { | |||
| ad := b[r[0]:r[1]] | |||
| if !isValidADStructure(ad) { | |||
| break | |||
| } | |||
| if checkIngics(ad) { | |||
| be = parseIngicsState(ad) | |||
| be.ID = id | |||
| be.Name = id | |||
| break | |||
| } else if checkEddystoneTLM(ad) { | |||
| be = parseEddystoneState(ad) | |||
| be.ID = id | |||
| be.Name = id | |||
| break | |||
| } else if checkMinewDeviceInfo(ad) { | |||
| be = parseMinewDeviceInfo(ad) | |||
| be.ID = id | |||
| be.Name = id | |||
| break | |||
| } else if checkMinewAccData(ad) { | |||
| break | |||
| for _, parser := range parserRegistry.ParserList { | |||
| if parser.CanParse(ad) { | |||
| event, ok := parser.Parse(ad) | |||
| if ok { | |||
| event.ID = id | |||
| event.Name = id | |||
| return event | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -79,81 +68,3 @@ func isValidADStructure(data []byte) bool { | |||
| length := int(data[0]) | |||
| return length > 0 && int(length)+1 <= len(data) | |||
| } | |||
| 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", | |||
| } | |||
| } | |||
| // Minew Battery level | |||
| func checkMinewDeviceInfo(ad []byte) bool { | |||
| if len(ad) >= 4 && | |||
| len(ad) != 19 && | |||
| ad[1] == 0x16 && | |||
| ad[2] == 0xE1 && | |||
| ad[3] == 0xFF { | |||
| fmt.Println("Minew device info") | |||
| return true | |||
| } | |||
| return false | |||
| } | |||
| func parseMinewDeviceInfo(ad []byte) model.BeaconEvent { | |||
| fmt.Printf("ad: %x\n", ad) | |||
| return model.BeaconEvent{ | |||
| Battery: uint32(ad[6]), | |||
| Type: "Minew B7", | |||
| } | |||
| } | |||
| func checkMinewAccData(ad []byte) bool { | |||
| if len(ad) == 19 && | |||
| ad[1] == 0x16 && | |||
| ad[2] == 0xE1 && | |||
| ad[3] == 0xFF { | |||
| fmt.Println("Minew Acc data") | |||
| return true | |||
| } | |||
| return false | |||
| } | |||
| // func parseMinewAccData(ad []byte) model.BeaconEvent { | |||
| // } | |||
| @@ -0,0 +1,104 @@ | |||
| package model | |||
| import ( | |||
| "bytes" | |||
| "encoding/binary" | |||
| "fmt" | |||
| "sync" | |||
| ) | |||
| type ParserConfig struct { | |||
| Length int `json:"length"` | |||
| Offset int `json:"offset"` | |||
| Order string `json:"order"` | |||
| } | |||
| type BeaconParser struct { | |||
| Name string | |||
| CanParse func([]byte) bool | |||
| configs map[string]ParserConfig | |||
| } | |||
| type ParserRegistry struct { | |||
| ParserList []BeaconParser | |||
| rw sync.RWMutex | |||
| } | |||
| type Config struct { | |||
| Name string `json:"name"` | |||
| Min int `json:"min"` | |||
| Max int `json:"max"` | |||
| Pattern []string `json:"pattern"` | |||
| Configs map[string]ParserConfig `json:"configs"` | |||
| } | |||
| func (pc ParserConfig) GetOrder() binary.ByteOrder { | |||
| if pc.Order == "bigendian" { | |||
| return binary.BigEndian | |||
| } | |||
| return binary.LittleEndian | |||
| } | |||
| func (p *ParserRegistry) Register(name string, c Config) { | |||
| p.rw.Lock() | |||
| defer p.rw.Unlock() | |||
| b := BeaconParser{ | |||
| Name: name, | |||
| CanParse: func(ad []byte) bool { | |||
| if len(ad) < 2 { | |||
| return false | |||
| } | |||
| return len(ad) >= c.Min && len(ad) <= c.Max && bytes.HasPrefix(ad[1:], c.GetPatternBytes()) | |||
| }, | |||
| configs: c.Configs, | |||
| } | |||
| fmt.Printf("registered beacon parser: %+v\n", b) | |||
| p.ParserList = append(p.ParserList, b) | |||
| } | |||
| func (b *BeaconParser) Parse(ad []byte) (BeaconEvent, bool) { | |||
| flag := false | |||
| event := BeaconEvent{Type: b.Name} | |||
| if cfg, ok := b.configs["battery"]; ok { | |||
| event.Battery = uint32(b.extract(ad, cfg)) | |||
| flag = true | |||
| } | |||
| if cfg, ok := b.configs["accX"]; ok { | |||
| event.AccX = int16(b.extract(ad, cfg)) | |||
| flag = true | |||
| } | |||
| if cfg, ok := b.configs["accY"]; ok { | |||
| event.AccY = int16(b.extract(ad, cfg)) | |||
| flag = true | |||
| } | |||
| if cfg, ok := b.configs["accZ"]; ok { | |||
| event.AccZ = int16(b.extract(ad, cfg)) | |||
| flag = true | |||
| } | |||
| return event, flag | |||
| } | |||
| func (b *BeaconParser) extract(ad []byte, pc ParserConfig) uint16 { | |||
| if len(ad) < pc.Offset+pc.Length { | |||
| return 0 | |||
| } | |||
| data := ad[pc.Offset : pc.Offset+pc.Length] | |||
| if pc.Length == 1 { | |||
| return uint16(data[0]) | |||
| } | |||
| return pc.GetOrder().Uint16(data) | |||
| } | |||
| func (c Config) GetPatternBytes() []byte { | |||
| res := make([]byte, len(c.Pattern)) | |||
| for i, s := range c.Pattern { | |||
| fmt.Sscanf(s, "0x%02x", &res[i]) | |||
| } | |||
| return res | |||
| } | |||