From 0b641d5471274d7b848248a45972700b1bca9f19 Mon Sep 17 00:00:00 2001 From: blazSmehov Date: Tue, 6 Jan 2026 14:39:40 +0100 Subject: [PATCH] feat: read decoding configuration from a file --- cmd/decoder/config.json | 37 +++++++++ cmd/decoder/main.go | 29 +++++-- internal/pkg/common/utils/beacons.go | 109 +++------------------------ internal/pkg/model/parser.go | 104 +++++++++++++++++++++++++ 4 files changed, 175 insertions(+), 104 deletions(-) create mode 100644 cmd/decoder/config.json create mode 100644 internal/pkg/model/parser.go diff --git a/cmd/decoder/config.json b/cmd/decoder/config.json new file mode 100644 index 0000000..0b2385a --- /dev/null +++ b/cmd/decoder/config.json @@ -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": {} + } +] \ No newline at end of file diff --git a/cmd/decoder/main.go b/cmd/decoder/main.go index 2a8626b..6aad8fb 100644 --- a/cmd/decoder/main.go +++ b/cmd/decoder/main.go @@ -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 diff --git a/internal/pkg/common/utils/beacons.go b/internal/pkg/common/utils/beacons.go index 7e51cd0..9fe5682 100644 --- a/internal/pkg/common/utils/beacons.go +++ b/internal/pkg/common/utils/beacons.go @@ -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 { - -// } diff --git a/internal/pkg/model/parser.go b/internal/pkg/model/parser.go new file mode 100644 index 0000000..b5d78b9 --- /dev/null +++ b/internal/pkg/model/parser.go @@ -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 +}