parser.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. package ble
  2. import (
  3. "encoding/binary"
  4. "fmt"
  5. "time"
  6. "mybeacon/internal/protocol"
  7. )
  8. // Advertisement data types (EIR/AD)
  9. const (
  10. ADTypeFlags = 0x01
  11. ADTypeIncompleteUUID16 = 0x02
  12. ADTypeCompleteUUID16 = 0x03
  13. ADTypeShortenedName = 0x08
  14. ADTypeCompleteName = 0x09
  15. ADTypeManufacturerData = 0xFF
  16. )
  17. // Manufacturer IDs
  18. const (
  19. MfgApple = 0x004C
  20. MfgNordic = 0x0059
  21. )
  22. // AdvReport represents a parsed LE Advertising Report
  23. type AdvReport struct {
  24. EventType byte
  25. AddrType byte
  26. Addr [6]byte
  27. Data []byte
  28. RSSI int8
  29. }
  30. // ParseLEAdvertisingReport parses LE Meta Event sub-event 0x02
  31. // Format: subevent(1) + num_reports(1) + reports...
  32. func ParseLEAdvertisingReport(data []byte) ([]AdvReport, error) {
  33. if len(data) < 2 {
  34. return nil, fmt.Errorf("data too short")
  35. }
  36. subevent := data[0]
  37. if subevent != EVT_LE_ADVERTISING_REPORT {
  38. return nil, nil // not an advertising report
  39. }
  40. numReports := int(data[1])
  41. if numReports == 0 {
  42. return nil, nil
  43. }
  44. reports := make([]AdvReport, 0, numReports)
  45. offset := 2
  46. // Parse each report
  47. for i := 0; i < numReports && offset < len(data); i++ {
  48. if offset+9 > len(data) {
  49. break
  50. }
  51. report := AdvReport{
  52. EventType: data[offset],
  53. AddrType: data[offset+1],
  54. }
  55. copy(report.Addr[:], data[offset+2:offset+8])
  56. dataLen := int(data[offset+8])
  57. offset += 9
  58. if offset+dataLen > len(data) {
  59. break
  60. }
  61. report.Data = make([]byte, dataLen)
  62. copy(report.Data, data[offset:offset+dataLen])
  63. offset += dataLen
  64. if offset < len(data) {
  65. report.RSSI = int8(data[offset])
  66. offset++
  67. }
  68. reports = append(reports, report)
  69. }
  70. return reports, nil
  71. }
  72. // MACString converts a Bluetooth address to string (reversed byte order)
  73. func MACString(addr [6]byte) string {
  74. return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
  75. addr[5], addr[4], addr[3], addr[2], addr[1], addr[0])
  76. }
  77. // ParseManufacturerData extracts manufacturer data from advertisement
  78. // Returns: manufacturer ID, payload (without ID), found
  79. func ParseManufacturerData(advData []byte) (uint16, []byte, bool) {
  80. offset := 0
  81. for offset < len(advData) {
  82. if offset+1 >= len(advData) {
  83. break
  84. }
  85. length := int(advData[offset])
  86. if length == 0 {
  87. break
  88. }
  89. if offset+1+length > len(advData) {
  90. break
  91. }
  92. adType := advData[offset+1]
  93. adData := advData[offset+2 : offset+1+length]
  94. if adType == ADTypeManufacturerData && len(adData) >= 2 {
  95. mfgID := binary.LittleEndian.Uint16(adData[0:2])
  96. return mfgID, adData[2:], true
  97. }
  98. offset += 1 + length
  99. }
  100. return 0, nil, false
  101. }
  102. // ParseIBeacon parses Apple iBeacon manufacturer data
  103. // Format: 0x02 0x15 + UUID(16) + Major(2) + Minor(2) + TxPower(1)
  104. func ParseIBeacon(payload []byte) (*protocol.IBeaconEvent, bool) {
  105. if len(payload) < 23 {
  106. return nil, false
  107. }
  108. if payload[0] != 0x02 || payload[1] != 0x15 {
  109. return nil, false
  110. }
  111. uuid := fmt.Sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
  112. payload[2], payload[3], payload[4], payload[5],
  113. payload[6], payload[7],
  114. payload[8], payload[9],
  115. payload[10], payload[11],
  116. payload[12], payload[13], payload[14], payload[15], payload[16], payload[17])
  117. major := binary.BigEndian.Uint16(payload[18:20])
  118. minor := binary.BigEndian.Uint16(payload[20:22])
  119. return &protocol.IBeaconEvent{
  120. BLEEvent: protocol.BLEEvent{
  121. Type: protocol.EventIBeacon,
  122. TsMs: time.Now().UnixMilli(),
  123. },
  124. UUID: uuid,
  125. Major: major,
  126. Minor: minor,
  127. }, true
  128. }
  129. // ParseMyBeaconAcc parses my-beacon accelerometer data (Nordic 0x0059)
  130. // Format: 0x01 0x15 + X(s8) + Y(s8) + Z(s8) + Bat(u8) + Temp(s8) + FF(u8)
  131. func ParseMyBeaconAcc(payload []byte) (*protocol.AccelEvent, bool) {
  132. if len(payload) < 8 {
  133. return nil, false
  134. }
  135. if payload[0] != 0x01 || payload[1] != 0x15 {
  136. return nil, false
  137. }
  138. ff := payload[7] != 0
  139. evType := protocol.EventAccel
  140. if ff {
  141. evType = protocol.EventAccelFF
  142. }
  143. return &protocol.AccelEvent{
  144. BLEEvent: protocol.BLEEvent{
  145. Type: evType,
  146. TsMs: time.Now().UnixMilli(),
  147. },
  148. X: int8(payload[2]),
  149. Y: int8(payload[3]),
  150. Z: int8(payload[4]),
  151. Bat: payload[5],
  152. Temp: int8(payload[6]),
  153. FF: ff,
  154. }, true
  155. }
  156. // ParseMyBeaconRelay parses rt_mybeacon relay data (Nordic 0x0059)
  157. // Format: 0x02 0x15 + DEADBEEF(4) + RelayMAC(6) + RelayMaj(2) + RelayMin(2) + RelayRSSI(1) + RelayBat(1) + IBMaj(2) + IBMin(2)
  158. func ParseMyBeaconRelay(payload []byte) (*protocol.RelayEvent, bool) {
  159. if len(payload) < 22 {
  160. return nil, false
  161. }
  162. if payload[0] != 0x02 || payload[1] != 0x15 {
  163. return nil, false
  164. }
  165. // Check magic DEADBEEF
  166. if payload[2] != 0xDE || payload[3] != 0xAD || payload[4] != 0xBE || payload[5] != 0xEF {
  167. return nil, false
  168. }
  169. relayMAC := fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
  170. payload[6], payload[7], payload[8], payload[9], payload[10], payload[11])
  171. return &protocol.RelayEvent{
  172. BLEEvent: protocol.BLEEvent{
  173. Type: protocol.EventRelay,
  174. TsMs: time.Now().UnixMilli(),
  175. },
  176. RelayMAC: relayMAC,
  177. RelayMaj: binary.BigEndian.Uint16(payload[12:14]),
  178. RelayMin: binary.BigEndian.Uint16(payload[14:16]),
  179. RelayRSSI: int8(payload[16]),
  180. RelayBat: payload[17],
  181. IBMajor: binary.BigEndian.Uint16(payload[18:20]),
  182. IBMinor: binary.BigEndian.Uint16(payload[20:22]),
  183. }, true
  184. }
  185. // ParseAdvertisement parses a BLE advertisement and returns any recognized events
  186. func ParseAdvertisement(report AdvReport) interface{} {
  187. mac := MACString(report.Addr)
  188. mfgID, payload, found := ParseManufacturerData(report.Data)
  189. if !found {
  190. return nil
  191. }
  192. switch mfgID {
  193. case MfgApple:
  194. if ev, ok := ParseIBeacon(payload); ok {
  195. ev.MAC = mac
  196. ev.RSSI = report.RSSI
  197. return ev
  198. }
  199. case MfgNordic:
  200. // Try accelerometer first
  201. if ev, ok := ParseMyBeaconAcc(payload); ok {
  202. ev.MAC = mac
  203. ev.RSSI = report.RSSI
  204. return ev
  205. }
  206. // Try relay
  207. if ev, ok := ParseMyBeaconRelay(payload); ok {
  208. ev.MAC = mac
  209. ev.RSSI = report.RSSI
  210. return ev
  211. }
  212. }
  213. return nil
  214. }