| @@ -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" | "bytes" | ||||
| "context" | "context" | ||||
| "encoding/hex" | "encoding/hex" | ||||
| "encoding/json" | |||||
| "fmt" | "fmt" | ||||
| "io" | "io" | ||||
| "log" | "log" | ||||
| @@ -29,6 +30,24 @@ func main() { | |||||
| appState := appcontext.NewAppState() | appState := appcontext.NewAppState() | ||||
| cfg := config.Load() | 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 | // Create log file | ||||
| logFile, err := os.OpenFile("server.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) | logFile, err := os.OpenFile("server.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -60,7 +79,7 @@ eventloop: | |||||
| case <-ctx.Done(): | case <-ctx.Done(): | ||||
| break eventloop | break eventloop | ||||
| case msg := <-chRaw: | case msg := <-chRaw: | ||||
| processIncoming(msg, appState, alertWriter) | |||||
| processIncoming(msg, appState, alertWriter, &parserRegistry) | |||||
| } | } | ||||
| } | } | ||||
| @@ -72,8 +91,8 @@ eventloop: | |||||
| appState.CleanKafkaWriters() | 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 { | if err != nil { | ||||
| eMsg := fmt.Sprintf("Error in decoding: %v", err) | eMsg := fmt.Sprintf("Error in decoding: %v", err) | ||||
| fmt.Println(eMsg) | 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) | beacon := strings.TrimSpace(adv.Data) | ||||
| id := adv.ID | id := adv.ID | ||||
| if beacon == "" { | if beacon == "" { | ||||
| @@ -96,7 +115,7 @@ func decodeBeacon(adv model.BeaconAdvertisement, appState *appcontext.AppState, | |||||
| b = utils.RemoveFlagBytes(b) | b = utils.RemoveFlagBytes(b) | ||||
| indeces := utils.ParseADFast(b) | indeces := utils.ParseADFast(b) | ||||
| event := utils.LoopADStructures(b, indeces, id) | |||||
| event := utils.LoopADStructures(b, indeces, id, parserRegistry) | |||||
| if event.ID == "" { | if event.ID == "" { | ||||
| return nil | return nil | ||||
| @@ -1,9 +1,6 @@ | |||||
| package utils | package utils | ||||
| import ( | import ( | ||||
| "encoding/binary" | |||||
| "fmt" | |||||
| "github.com/AFASystems/presence/internal/pkg/model" | "github.com/AFASystems/presence/internal/pkg/model" | ||||
| ) | ) | ||||
| @@ -40,30 +37,22 @@ func RemoveFlagBytes(b []byte) []byte { | |||||
| } | } | ||||
| // Generate event based on the Beacon type | // 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{} | be := model.BeaconEvent{} | ||||
| for _, r := range i { | for _, r := range i { | ||||
| ad := b[r[0]:r[1]] | ad := b[r[0]:r[1]] | ||||
| if !isValidADStructure(ad) { | if !isValidADStructure(ad) { | ||||
| break | 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]) | length := int(data[0]) | ||||
| return length > 0 && int(length)+1 <= len(data) | 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 | |||||
| } | |||||