You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

140 line
3.0 KiB

  1. package model
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "fmt"
  6. "log/slog"
  7. "sync"
  8. "github.com/AFASystems/presence/internal/pkg/common/appcontext"
  9. )
  10. type ParserConfig struct {
  11. Length int `json:"length"`
  12. Offset int `json:"offset"`
  13. Order string `json:"order"`
  14. }
  15. type BeaconParser struct {
  16. CanParse func([]byte) bool
  17. configs map[string]ParserConfig
  18. }
  19. type ParserRegistry struct {
  20. ParserList map[string]BeaconParser
  21. rw sync.RWMutex
  22. }
  23. type Config struct {
  24. Name string `json:"name" gorm:"primaryKey" validate:"required"`
  25. Min int `json:"min" validate:"gte=0,ltefield=Max"`
  26. Max int `json:"max" validate:"gte=0"`
  27. Pattern []string `json:"pattern" gorm:"serializer:json" validate:"required,min=1,dive,required"`
  28. Configs map[string]ParserConfig `json:"configs" gorm:"serializer:json"`
  29. }
  30. type KafkaParser struct {
  31. ID string
  32. Name string
  33. Config Config
  34. }
  35. func (pc ParserConfig) GetOrder() any {
  36. if pc.Order == "fixedpoint" {
  37. return "fixedpoint"
  38. }
  39. if pc.Order == "bigendian" {
  40. return binary.BigEndian
  41. }
  42. return binary.LittleEndian
  43. }
  44. func (p *ParserRegistry) Register(name string, c Config) {
  45. p.rw.Lock()
  46. defer p.rw.Unlock()
  47. b := BeaconParser{
  48. CanParse: func(ad []byte) bool {
  49. if len(ad) < 2 {
  50. msg := "Beacon advertisement is too short"
  51. slog.Error(msg)
  52. return false
  53. }
  54. return len(ad) >= c.Min && len(ad) <= c.Max && bytes.HasPrefix(ad[1:], c.GetPatternBytes())
  55. },
  56. configs: c.Configs,
  57. }
  58. p.ParserList[name] = b
  59. }
  60. func (p *ParserRegistry) Unregister(name string) {
  61. p.rw.Lock()
  62. delete(p.ParserList, name)
  63. p.rw.Unlock()
  64. }
  65. // TODO: change this to be dynamic, maybe event is interface with no predefined properties
  66. // or types
  67. func (b *BeaconParser) Parse(name string, ad []byte) (appcontext.BeaconEvent, bool) {
  68. flag := false
  69. event := appcontext.BeaconEvent{Type: name}
  70. if cfg, ok := b.configs["battery"]; ok {
  71. event.Battery = uint32(b.extract(ad, cfg).(uint16))
  72. flag = true
  73. }
  74. if cfg, ok := b.configs["accX"]; ok {
  75. val := b.extract(ad, cfg).(float64)
  76. event.AccX = int16(val)
  77. flag = true
  78. }
  79. if cfg, ok := b.configs["accY"]; ok {
  80. val := b.extract(ad, cfg).(float64)
  81. event.AccY = int16(val)
  82. flag = true
  83. }
  84. if cfg, ok := b.configs["accZ"]; ok {
  85. val := b.extract(ad, cfg).(float64)
  86. event.AccZ = int16(val)
  87. flag = true
  88. }
  89. if cfg, ok := b.configs["temperature"]; ok {
  90. val := b.extract(ad, cfg).(float64)
  91. event.Temperature = uint16(val)
  92. flag = true
  93. }
  94. return event, flag
  95. }
  96. func (b *BeaconParser) extract(ad []byte, pc ParserConfig) any {
  97. if len(ad) < pc.Offset+pc.Length {
  98. return 0
  99. }
  100. data := ad[pc.Offset : pc.Offset+pc.Length]
  101. if pc.Length == 1 {
  102. return uint16(data[0])
  103. }
  104. order := pc.GetOrder()
  105. if order == "fixedpoint" {
  106. val := int16(data[0])<<8 | int16(data[1])
  107. return float64(val) / 256.0
  108. }
  109. o := order.(binary.ByteOrder)
  110. return o.Uint16(data)
  111. }
  112. func (c Config) GetPatternBytes() []byte {
  113. res := make([]byte, len(c.Pattern))
  114. for i, s := range c.Pattern {
  115. fmt.Sscanf(s, "0x%02x", &res[i])
  116. }
  117. return res
  118. }