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.
 
 
 
 

77 line
2.2 KiB

  1. package bridge
  2. import (
  3. "context"
  4. "encoding/json"
  5. "log/slog"
  6. "strings"
  7. "time"
  8. "github.com/AFASystems/presence/internal/pkg/model"
  9. "github.com/segmentio/kafka-go"
  10. )
  11. // RawBeaconWriter writes beacon advertisements to the rawbeacons topic.
  12. type RawBeaconWriter interface {
  13. WriteMessages(ctx context.Context, msgs ...kafka.Message) error
  14. }
  15. // BeaconLookup provides MAC->ID lookup (e.g. AppState).
  16. type BeaconLookup interface {
  17. BeaconExists(mac string) (id string, ok bool)
  18. }
  19. // HandleMQTTMessage processes an MQTT message: parses JSON array of RawReading or CSV.
  20. // For JSON, converts each reading to BeaconAdvertisement and writes to the writer if MAC is in lookup.
  21. // Hostname is derived from topic (e.g. "publish_out/gateway1" -> "gateway1"). Safe if topic has no "/".
  22. func HandleMQTTMessage(topic string, payload []byte, lookup BeaconLookup, writer RawBeaconWriter) {
  23. parts := strings.SplitN(topic, "/", 2)
  24. hostname := ""
  25. if len(parts) >= 2 {
  26. hostname = parts[1]
  27. }
  28. msgStr := string(payload)
  29. if strings.HasPrefix(msgStr, "[") {
  30. var readings []model.RawReading
  31. if err := json.Unmarshal(payload, &readings); err != nil {
  32. slog.Error("parsing MQTT JSON", "err", err, "topic", topic)
  33. return
  34. }
  35. for _, reading := range readings {
  36. if reading.Type == "Gateway" {
  37. continue
  38. }
  39. id, ok := lookup.BeaconExists(reading.MAC)
  40. if !ok {
  41. continue
  42. }
  43. adv := model.BeaconAdvertisement{
  44. ID: id,
  45. Hostname: hostname,
  46. MAC: reading.MAC,
  47. RSSI: int64(reading.RSSI),
  48. Data: reading.RawData,
  49. }
  50. encoded, err := json.Marshal(adv)
  51. if err != nil {
  52. slog.Error("marshaling beacon advertisement", "err", err)
  53. break
  54. }
  55. if err := writer.WriteMessages(context.Background(), kafka.Message{Value: encoded}); err != nil {
  56. slog.Error("writing to Kafka", "err", err)
  57. time.Sleep(1 * time.Second)
  58. break
  59. }
  60. }
  61. return
  62. }
  63. // CSV format: validate minimum fields (e.g. 6 columns); full parsing can be added later
  64. s := strings.Split(msgStr, ",")
  65. if len(s) < 6 {
  66. slog.Error("invalid CSV MQTT message", "topic", topic, "message", msgStr)
  67. return
  68. }
  69. slog.Debug("CSV MQTT message received", "topic", topic, "fields", len(s))
  70. }