package main import ( "bytes" "encoding/gob" "encoding/json" "flag" "fmt" "io/ioutil" "log" "math" "net/http" "os" "os/signal" "strconv" "strings" "sync" "time" //"./utils" "os/exec" "github.com/boltdb/bolt" "gopkg.in/natefinch/lumberjack.v2" "github.com/yosssi/gmq/mqtt" "github.com/yosssi/gmq/mqtt/client" "github.com/gorilla/websocket" ) var clients = make(map[*websocket.Conn]bool) // connected clients var broadcast = make(chan Message) var BEACONS Beacons_list var Buttons_list map[string]Button var cli *client.Client var http_results HTTP_locations_list var http_results_lock sync.RWMutex var Latest_beacons_list map[string]Beacon var latest_list_lock sync.RWMutex var db *bolt.DB var err error var world = []byte("presence") var ( // /logpath = flag.String("logpath", "/data/var/log/presence/presence.log", "Log Path") ) var ( // Websocket http upgrader upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, CheckOrigin: func(r *http.Request) bool { return true }, } ) var settings = Settings{ Location_confidence: 4, Last_seen_threshold: 15, Beacon_metrics_size: 30, HA_send_interval: 5, HA_send_changes_only: false, } // utility function func parseButtonState(raw string) int64 { raw = strings.ToUpper(raw) // Minew B7 / C7 / D7 - frame tipo: 0201060303E1FF1216E1FFA103... if strings.HasPrefix(raw, "0201060303E1FF12") && len(raw) >= 38 { // La posizione 34-38 (indice 26:30) contiene il buttonCounter su 2 byte (hex) buttonField := raw[34:38] // NB: offset 34-38 zero-based if buttonValue, err := strconv.ParseInt(buttonField, 16, 64); err == nil { return buttonValue } } // Ingics (02010612FF590) if strings.HasPrefix(raw, "02010612FF590") && len(raw) >= 24 { counterField := raw[22:24] buttonState, err := strconv.ParseInt(counterField, 16, 64) if err == nil { return buttonState } } // Aggiungeremo qui facilmente nuovi beacon in futuro return 0 } func twos_comp(inp string) int64 { i, _ := strconv.ParseInt("0x"+inp, 0, 64) return i - 256 } func getBeaconID(incoming Incoming_json) string { unique_id := fmt.Sprintf("%s", incoming.MAC) /*if incoming.Beacon_type == "ibeacon" { unique_id = fmt.Sprintf("%s_%s_%s", incoming.UUID, incoming.Major, incoming.Minor) } else if incoming.Beacon_type == "eddystone" { unique_id = fmt.Sprintf("%s_%s", incoming.Namespace, incoming.Instance_id) } else if incoming.Beacon_type == "hb_button" { unique_id = fmt.Sprintf("%s_%s", incoming.Namespace, incoming.Instance_id) }*/ return unique_id } func incomingBeaconFilter(incoming Incoming_json) Incoming_json { out_json := incoming if incoming.Beacon_type == "hb_button" { //do additional checks here to detect if a Habby Bubbles Button // looks like 020104020a0011ff045600012d3859db59e1000b9453 raw_data := incoming.Data //company_id := []byte{0x04, 0x56} //product_id := []byte{0x00, 0x01} hb_button_prefix_str := fmt.Sprintf("02010612FF5900") if strings.HasPrefix(raw_data, hb_button_prefix_str) { out_json.Namespace = "ddddeeeeeeffff5544ff" //out_json.Instance_id = raw_data[24:36] counter_str := fmt.Sprintf("0x%s", raw_data[22:24]) counter, _ := strconv.ParseInt(counter_str, 0, 64) out_json.HB_ButtonCounter = counter battery_str := fmt.Sprintf("0x%s%s", raw_data[20:22], raw_data[18:20]) ////fmt.Println("battery has %s\n", battery_str) battery, _ := strconv.ParseInt(battery_str, 0, 64) out_json.HB_Battery = battery out_json.TX_power = fmt.Sprintf("0x%s", "4") out_json.Beacon_type = "hb_button" out_json.HB_ButtonMode = "presence_button" ///fmt.Println("Button adv has %#v\n", out_json) } } return out_json } func processButton(bbeacon Beacon, cl *client.Client) { btn := Button{Name: bbeacon.Name} btn.Button_id = bbeacon.Beacon_id btn.Button_type = bbeacon.Beacon_type btn.Button_location = bbeacon.Previous_location btn.Incoming_JSON = bbeacon.Incoming_JSON btn.Distance = bbeacon.Distance btn.Last_seen = bbeacon.Last_seen btn.HB_ButtonCounter = bbeacon.HB_ButtonCounter btn.HB_Battery = bbeacon.HB_Battery btn.HB_RandomNonce = bbeacon.HB_RandomNonce btn.HB_ButtonMode = bbeacon.HB_ButtonMode nonce, ok := Buttons_list[btn.Button_id] if !ok || nonce.HB_RandomNonce != btn.HB_RandomNonce { // send the button message to MQTT sendButtonMessage(btn, cl) } Buttons_list[btn.Button_id] = btn } func getiBeaconDistance(rssi int64, power string) float64 { ratio := float64(rssi) * (1.0 / float64(twos_comp(power))) //fmt.Printf("beaconpower: rssi %d ratio %e power %e \n",rssi, ratio, float64(twos_comp(power))) distance := 100.0 if ratio < 1.0 { distance = math.Pow(ratio, 10) } else { distance = (0.89976)*math.Pow(ratio, 7.7095) + 0.111 } return distance } func getBeaconDistance(incoming Incoming_json) float64 { distance := 1000.0 distance = getiBeaconDistance(incoming.RSSI, incoming.TX_power) //distance = math.Abs(float64(incoming.RSSI)) return distance } func getAverageDistance(beacon_metrics []beacon_metric) float64 { total := 0.0 for _, v := range beacon_metrics { total += v.distance } return (total / float64(len(beacon_metrics))) } func sendHARoomMessage(beacon_id string, beacon_name string, distance float64, location string, cl *client.Client) { //first make the json ha_msg, err := json.Marshal(HA_message{Beacon_id: beacon_id, Beacon_name: beacon_name, Distance: distance}) if err != nil { panic(err) } //send the message to HA err = cl.Publish(&client.PublishOptions{ QoS: mqtt.QoS1, TopicName: []byte("afa-systems/presence/ha/" + location), Message: ha_msg, }) if err != nil { panic(err) } } func sendButtonMessage(btn Button, cl *client.Client) { //first make the json btn_msg, err := json.Marshal(btn) if err != nil { panic(err) } //send the message to HA err = cl.Publish(&client.PublishOptions{ QoS: mqtt.QoS1, TopicName: []byte("afa-systems/presence/button/" + btn.Button_id), Message: btn_msg, }) if err != nil { panic(err) } } func sendButtonPressed(bcn Beacon, cl *client.Client) { //first make the json btn_msg, err := json.Marshal(bcn) if err != nil { panic(err) } //send the message to HA err = cl.Publish(&client.PublishOptions{ QoS: mqtt.QoS1, TopicName: []byte("afa-systems/presence/button/" + bcn.Beacon_id), Message: btn_msg, }) if err != nil { panic(err) } ///utils.Log.Printf("%s pressed ",bcn.Beacon_id) s := fmt.Sprintf("/usr/bin/php /usr/local/presence/alarm_handler.php --idt=%s --idr=%s --st=%d", bcn.Beacon_id, bcn.Incoming_JSON.Hostname, bcn.HB_ButtonCounter) ///utils.Log.Printf("%s",s) err, out, errout := Shellout(s) if err != nil { log.Printf("error: %v\n", err) } fmt.Println("--- stdout ---") fmt.Println(out) fmt.Println("--- stderr ---") fmt.Println(errout) } func getLikelyLocations(settings Settings, locations_list Locations_list, cl *client.Client) { // create the http results structure http_results_lock.Lock() http_results = HTTP_locations_list{} http_results.Beacons = make([]HTTP_location, 0) ///http_results.Buttons = make([]Button, 0) http_results_lock.Unlock() should_persist := false // iterate through the beacons we want to search for for _, beacon := range BEACONS.Beacons { if len(beacon.beacon_metrics) == 0 { ////fmt.Printf("beacon_metrics = 0:\n") continue } if (int64(time.Now().Unix()) - (beacon.beacon_metrics[len(beacon.beacon_metrics)-1].timestamp)) > settings.Last_seen_threshold { ////fmt.Printf("beacon_metrics timestamp = %s %s \n",beacon.Name, beacon.beacon_metrics[len(beacon.beacon_metrics)-1].timestamp ) if beacon.expired_location == "expired" { //beacon.Location_confidence = - 1 continue } else { beacon.expired_location = "expired" msg := Message{ Email: beacon.Previous_confident_location, Username: beacon.Name, Message: beacon.expired_location} res1B, _ := json.Marshal(msg) fmt.Println(string(res1B)) if err != nil { log.Printf("error: %v", err) } // Send the newly received message to the broadcast channel broadcast <- msg } } else { beacon.expired_location = "" } best_location := Best_location{} // go through its beacon metrics and pick out the location that appears most often loc_list := make(map[string]float64) seen_weight := 1.5 rssi_weight := 0.75 for _, metric := range beacon.beacon_metrics { loc, ok := loc_list[metric.location] if !ok { loc = seen_weight + (rssi_weight * (1.0 - (float64(metric.rssi) / -100.0))) } else { loc = loc + seen_weight + (rssi_weight * (1.0 - (float64(metric.rssi) / -100.0))) } loc_list[metric.location] = loc } best_name := "" ts := 0.0 for name, times_seen := range loc_list { if times_seen > ts { best_name = name ts = times_seen } } /////fmt.Printf("BEST LOCATION FOR %s IS: %s with score: %f\n", beacon.Name, best_name, ts) best_location = Best_location{name: best_name, distance: beacon.beacon_metrics[len(beacon.beacon_metrics)-1].distance, last_seen: beacon.beacon_metrics[len(beacon.beacon_metrics)-1].timestamp} beacon.Location_history = append(beacon.Location_history, best_location.name) if len(beacon.Location_history) > 10 { beacon.Location_history = beacon.Location_history[1:] // manteniamo solo gli ultimi 10 } // Calcoliamo la location più votata nello storico location_counts := make(map[string]int) for _, loc := range beacon.Location_history { location_counts[loc]++ } max_count := 0 most_common_location := "" for loc, count := range location_counts { if count > max_count { max_count = count most_common_location = loc } } // Applichiamo un filtro: consideriamo il cambio solo se almeno 7 su 10 votano per una location if max_count >= 7 { beacon.Previous_location = most_common_location if most_common_location == beacon.Previous_confident_location { beacon.Location_confidence++ } else { beacon.Location_confidence = 1 beacon.Previous_confident_location = most_common_location } } //create an http result from this r := HTTP_location{} r.Distance = best_location.distance r.Name = beacon.Name r.Beacon_name = beacon.Name r.Beacon_id = beacon.Beacon_id r.Beacon_type = beacon.Beacon_type r.HB_Battery = beacon.HB_Battery r.HB_ButtonMode = beacon.HB_ButtonMode r.HB_ButtonCounter = beacon.HB_ButtonCounter r.Location = best_location.name r.Last_seen = best_location.last_seen ////fmt.Printf("beacon.Location_confidence %s, settings.Location_confidence %s, beacon.Previous_confident_location %s: best_location.name %s\n",beacon.Location_confidence, settings.Location_confidence, beacon.Previous_confident_location, best_location.name) if (beacon.Location_confidence == settings.Location_confidence && beacon.Previous_confident_location != best_location.name) || beacon.expired_location == "expired" { // location has changed, send an mqtt message should_persist = true fmt.Printf("detected a change!!! %#v\n\n", beacon) if beacon.Previous_confident_location == "expired" && beacon.expired_location == "" { msg := Message{ Email: beacon.Previous_confident_location, Username: beacon.Name, Message: "OK"} res1B, _ := json.Marshal(msg) fmt.Println(string(res1B)) if err != nil { log.Printf("error: %v", err) } // Send the newly received message to the broadcast channel broadcast <- msg } beacon.Location_confidence = 0 location := "" if beacon.expired_location == "expired" { location = "expired" } else { location = best_location.name } //first make the json js, err := json.Marshal(Location_change{Beacon_ref: beacon, Name: beacon.Name, Beacon_name: beacon.Name, Previous_location: beacon.Previous_confident_location, New_location: location, Timestamp: time.Now().Unix()}) if err != nil { continue } //send the message err = cl.Publish(&client.PublishOptions{ QoS: mqtt.QoS1, TopicName: []byte("afa-systems/presence/changes"), Message: js, }) if err != nil { panic(err) } s := fmt.Sprintf("/usr/bin/php /usr/local/presence/alarm_handler.php --idt=%s --idr=%s --loct=%s", beacon.Beacon_id, beacon.Incoming_JSON.Hostname, location) ///utils.Log.Printf("%s",s) err, out, errout := Shellout(s) if err != nil { log.Printf("error: %v\n", err) } fmt.Println("--- stdout ---") fmt.Println(out) fmt.Println("--- stderr ---") fmt.Println(errout) //////beacon.logger.Printf("Log content: user id %v \n", best_location.name) if settings.HA_send_changes_only { sendHARoomMessage(beacon.Beacon_id, beacon.Name, best_location.distance, best_location.name, cl) } if beacon.expired_location == "expired" { beacon.Previous_confident_location = "expired" r.Location = "expired" } else { beacon.Previous_confident_location = best_location.name } ///beacon.Previous_confident_location = best_location.name } beacon.Previous_location = best_location.name r.Previous_confident_location = beacon.expired_location BEACONS.Beacons[beacon.Beacon_id] = beacon http_results_lock.Lock() http_results.Beacons = append(http_results.Beacons, r) http_results_lock.Unlock() if best_location.name != "" { if !settings.HA_send_changes_only { secs := int64(time.Now().Unix()) if secs%settings.HA_send_interval == 0 { sendHARoomMessage(beacon.Beacon_id, beacon.Name, best_location.distance, best_location.name, cl) } } } err := cl.Publish(&client.PublishOptions{ QoS: mqtt.QoS0, TopicName: []byte("afa-systems/presence"), Message: []byte(fmt.Sprintf("%s is most likely in %s with average distance %f", beacon.Name, best_location.name, best_location.distance)), }) if err != nil { panic(err) } } if should_persist { persistBeacons() } } func IncomingMQTTProcessor(updateInterval time.Duration, cl *client.Client, db *bolt.DB, logger []*user) chan<- Incoming_json { incoming_msgs_chan := make(chan Incoming_json, 2000) // load initial BEACONS BEACONS.Beacons = make(map[string]Beacon) // retrieve the data // create bucket if not exist err = db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists(world) if err != nil { return err } return nil }) err = db.View(func(tx *bolt.Tx) error { bucket := tx.Bucket(world) if bucket == nil { return err } key := []byte("beacons_list") val := bucket.Get(key) if val != nil { buf := bytes.NewBuffer(val) dec := gob.NewDecoder(buf) err = dec.Decode(&BEACONS) if err != nil { log.Fatal("decode error:", err) } } key = []byte("buttons_list") val = bucket.Get(key) if val != nil { buf := bytes.NewBuffer(val) dec := gob.NewDecoder(buf) err = dec.Decode(&Buttons_list) if err != nil { log.Fatal("decode error:", err) } } key = []byte("settings") val = bucket.Get(key) if val != nil { buf := bytes.NewBuffer(val) dec := gob.NewDecoder(buf) err = dec.Decode(&settings) if err != nil { log.Fatal("decode error:", err) } } return nil }) if err != nil { log.Fatal(err) } Latest_beacons_list = make(map[string]Beacon) Buttons_list = make(map[string]Button) //create a map of locations, looked up by hostnames locations_list := Locations_list{} ls := make(map[string]Location) locations_list.locations = ls ticker := time.NewTicker(updateInterval) go func() { for { select { case <-ticker.C: getLikelyLocations(settings, locations_list, cl) case incoming := <-incoming_msgs_chan: func() { defer func() { if err := recover(); err != nil { log.Println("work failed:", err) } }() incoming = incomingBeaconFilter(incoming) this_beacon_id := getBeaconID(incoming) now := time.Now().Unix() beacon, ok := BEACONS.Beacons[this_beacon_id] if !ok { //should be unique //if it's already in list, forget it. latest_list_lock.Lock() x, ok := Latest_beacons_list[this_beacon_id] if ok { //update its timestamp x.Last_seen = now x.Incoming_JSON = incoming x.Distance = getBeaconDistance(incoming) Latest_beacons_list[this_beacon_id] = x } else { Latest_beacons_list[this_beacon_id] = Beacon{Beacon_id: this_beacon_id, Beacon_type: incoming.Beacon_type, Last_seen: now, Incoming_JSON: incoming, Beacon_location: incoming.Hostname, Distance: getBeaconDistance(incoming)} } for k, v := range Latest_beacons_list { if (now - v.Last_seen) > 10 { // 10 seconds delete(Latest_beacons_list, k) } } latest_list_lock.Unlock() //continue return } beacon.Incoming_JSON = incoming beacon.Last_seen = now beacon.Beacon_type = incoming.Beacon_type beacon.HB_ButtonCounter = incoming.HB_ButtonCounter beacon.HB_Battery = incoming.HB_Battery beacon.HB_RandomNonce = incoming.HB_RandomNonce beacon.HB_ButtonMode = incoming.HB_ButtonMode ////fmt.Println("button pressed " + this_beacon_id + " at " + strconv.Itoa(int(incoming.HB_ButtonCounter)) ) if beacon.beacon_metrics == nil { beacon.beacon_metrics = make([]beacon_metric, settings.Beacon_metrics_size) } //create metric for this beacon this_metric := beacon_metric{} this_metric.distance = getBeaconDistance(incoming) this_metric.timestamp = now this_metric.rssi = int64(incoming.RSSI) this_metric.location = incoming.Hostname beacon.beacon_metrics = append(beacon.beacon_metrics, this_metric) ///fmt.Printf("APPENDING a metric from %s len %d\n", beacon.Name, len(beacon.beacon_metrics)) if len(beacon.beacon_metrics) > settings.Beacon_metrics_size { //fmt.Printf("deleting a metric from %s len %d\n", beacon.Name, len(beacon.beacon_metrics)) beacon.beacon_metrics = append(beacon.beacon_metrics[:0], beacon.beacon_metrics[0+1:]...) } //fmt.Printf("%#v\n", beacon.beacon_metrics) if beacon.HB_ButtonCounter_Prev != beacon.HB_ButtonCounter { beacon.HB_ButtonCounter_Prev = incoming.HB_ButtonCounter // send the button message to MQTT sendButtonPressed(beacon, cl) } BEACONS.Beacons[beacon.Beacon_id] = beacon /*if beacon.Beacon_type == "hb_button" { processButton(beacon, cl) }*/ //lookup location by hostname in locations location, ok := locations_list.locations[incoming.Hostname] if !ok { //create the location locations_list.locations[incoming.Hostname] = Location{} location, ok = locations_list.locations[incoming.Hostname] location.name = incoming.Hostname } locations_list.locations[incoming.Hostname] = location }() } } }() return incoming_msgs_chan } func ParseTimeStamp(utime string) (string, error) { i, err := strconv.ParseInt(utime, 10, 64) if err != nil { return "", err } t := time.Unix(i, 0) return t.Format(time.UnixDate), nil } var http_host_path_ptr *string // var https_host_path_ptr *string var httpws_host_path_ptr *string //var httpwss_host_path_ptr *string type Todo struct { Id string `json:"id"` Value string `json:"value" binding:"required"` } type Job interface { ExitChan() chan error Run(todos map[string]Todo) (map[string]Todo, error) } func ProcessJobs(jobs chan Job, db string) { for { j := <-jobs todos := make(map[string]Todo, 0) content, err := ioutil.ReadFile(db) if err == nil { if err = json.Unmarshal(content, &todos); err == nil { todosMod, err := j.Run(todos) if err == nil && todosMod != nil { b, err := json.Marshal(todosMod) if err == nil { err = ioutil.WriteFile(db, b, 0644) } } } } j.ExitChan() <- err } } type user struct { id string logger *log.Logger } const ShellToUse = "bash" func Shellout(command string) (error, string, string) { var stdout bytes.Buffer var stderr bytes.Buffer ///utils.Log.Printf("command: %s",command) cmd := exec.Command(ShellToUse, "-c", command) cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() return err, stdout.String(), stderr.String() } func createUser(id string, logWanted bool) user { var l *log.Logger if logWanted { // Here the log content will be added in the user log file userFIle := &lumberjack.Logger{ Filename: "/data/var/log/presence/presence/log_" + id + ".log", MaxSize: 250, // mb MaxBackups: 5, MaxAge: 10, // in days } l = log.New(userFIle, "User: ", log.Ldate|log.Ltime|log.Lshortfile) } else { // Here the log content will go nowhere l = log.New(ioutil.Discard, "User: ", log.Ldate|log.Ltime|log.Lshortfile) } return user{id, l} } func main() { loggers := []*user{} http_host_path_ptr = flag.String("http_host_path", "0.0.0.0:8080", "The host:port that the HTTP server should listen on") //https_host_path_ptr = flag.String("https_host_path", "0.0.0.0:5443", "The host:port that the HTTP server should listen on") httpws_host_path_ptr = flag.String("httpws_host_path", "0.0.0.0:8088", "The host:port websocket listen") //httpwss_host_path_ptr = flag.String("httpwss_host_path", "0.0.0.0:8443", "The host:port secure websocket listen") mqtt_host_ptr := flag.String("mqtt_host", "localhost:1883", "The host:port of the MQTT server to listen for beacons on") mqtt_username_ptr := flag.String("mqtt_username", "none", "The username needed to connect to the MQTT server, 'none' if it doesn't need one") mqtt_password_ptr := flag.String("mqtt_password", "none", "The password needed to connect to the MQTT server, 'none' if it doesn't need one") mqtt_client_id_ptr := flag.String("mqtt_client_id", "presence-detector", "The client ID for the MQTT server") flag.Parse() sigc := make(chan os.Signal, 1) signal.Notify(sigc, os.Interrupt, os.Kill) // Create an MQTT Client. cli := client.New(&client.Options{ // Define the processing of the error handler. ErrorHandler: func(err error) { fmt.Println(err) }, }) // Terminate the Client. defer cli.Terminate() //open the database db, err = bolt.Open("/data/conf/presence/presence.db", 0644, nil) if err != nil { log.Fatal(err) } defer db.Close() // Connect to the MQTT Server. err = cli.Connect(&client.ConnectOptions{ Network: "tcp", Address: *mqtt_host_ptr, ClientID: []byte(*mqtt_client_id_ptr), UserName: []byte(*mqtt_username_ptr), Password: []byte(*mqtt_password_ptr), }) if err != nil { panic(err) } incoming_updates_chan := IncomingMQTTProcessor(1*time.Second, cli, db, loggers) // Subscribe to topics. err = cli.Subscribe(&client.SubscribeOptions{ SubReqs: []*client.SubReq{ &client.SubReq{ TopicFilter: []byte("publish_out/#"), QoS: mqtt.QoS0, Handler: func(topicName, message []byte) { msgStr := string(message) t := strings.Split(string(topicName), "/") hostname := t[1] //Formato JSON multiplo //publish_out/170361001234 [{"timestamp":"2025-06-11T11:27:28.492Z","type":"Gateway","mac":"E4B3230DB5CC","nums":10},{"timestamp":"2025-06-11T11:27:28.483Z","mac":"36CE2D7CA4E5","rssi":-27,"rawData":"1EFF0600010F20226F50BB5F834F6C9CE3D876B0C3F665882955B368D3B96C"},{"timestamp":"2025-06-11T11:27:28.586Z","mac":"36CE2D7CA4E5","rssi":-30,"rawData":"1EFF0600010F20226F50BB5F834F6C9CE3D876B0C3F665882955B368D3B96C"},{"timestamp":"2025-06-11T11:27:28.612Z","mac":"406260A302FC","rssi":-35,"rawData":"02011A020A0B0BFF4C001006371AAE2F6F5B"},{"timestamp":"2025-06-11T11:27:28.798Z","mac":"36CE2D7CA4E5","rssi":-28,"rawData":"1EFF0600010F20226F50BB5F834F6C9CE3D876B0C3F665882955B368D3B96C"},{"timestamp":"2025-06-11T11:27:28.905Z","mac":"36CE2D7CA4E5","rssi":-30,"rawData":"1EFF0600010F20226F50BB5F834F6C9CE3D876B0C3F665882955B368D3B96C"},{"timestamp":"2025-06-11T11:27:28.945Z","mac":"C300003947DF","rssi":-32,"rawData":"0201061AFF4C000215FDA50693A4E24FB1AFCFC6EB0764782500000000C5"},{"timestamp":"2025-06-11T11:27:29.013Z","mac":"36CE2D7CA4E5","rssi":-29,"rawData":"1EFF0600010F20226F50BB5F834F6C9CE3D876B0C3F665882955B368D3B96C"},{"timestamp":"2025-06-11T11:27:29.120Z","mac":"36CE2D7CA4E5","rssi":-27,"rawData":"1EFF0600010F20226F50BB5F834F6C9CE3D876B0C3F665882955B368D3B96C"},{"timestamp":"2025-06-11T11:27:29.166Z","mac":"406260A302FC","rssi":-34,"rawData":"02011A020A0B0BFF4C001006371AAE2F6F5B"},{"timestamp":"2025-06-11T11:27:29.337Z","mac":"36CE2D7CA4E5","rssi":-26,"rawData":"1EFF0600010F20226F50BB5F834F6C9CE3D876B0C3F665882955B368D3B96C"}] if strings.HasPrefix(msgStr, "[") { var readings []RawReading err := json.Unmarshal(message, &readings) if err != nil { log.Printf("Errore parsing JSON: %v", err) return } for _, reading := range readings { if reading.Type == "Gateway" { continue } incoming := Incoming_json{ Hostname: hostname, MAC: reading.MAC, RSSI: int64(reading.RSSI), Data: reading.RawData, HB_ButtonCounter: parseButtonState(reading.RawData), } incoming_updates_chan <- incoming } } else { //publish_out/171061001180 $GPRP,C83F8F17DB35,F5B0B0419FEF,-44,02010612FF590080BC280103FFFFFFFF000000000000,1749648798 s := strings.Split(string(message), ",") if len(s) < 6 { log.Printf("Messaggio CSV non valido: %s", msgStr) return } rawdata := s[4] buttonCounter := parseButtonState(rawdata) if buttonCounter > 0 { incoming := Incoming_json{} i, _ := strconv.ParseInt(s[3], 10, 64) incoming.Hostname = hostname incoming.Beacon_type = "hb_button" incoming.MAC = s[1] incoming.RSSI = i incoming.Data = rawdata incoming.HB_ButtonCounter = buttonCounter read_line := strings.TrimRight(string(s[5]), "\r\n") it, err33 := strconv.Atoi(read_line) if err33 != nil { fmt.Println(it) fmt.Println(err33) os.Exit(2) } incoming_updates_chan <- incoming } } }, }, }, }) if err != nil { panic(err) } fmt.Println("CONNECTED TO MQTT") fmt.Println("\n ") fmt.Println("Visit http://" + *http_host_path_ptr + " on your browser to see the web interface") fmt.Println("\n ") go startServer() // Wait for receiving a signal. <-sigc // Disconnect the Network Connection. if err := cli.Disconnect(); err != nil { panic(err) } }