package main import ( "bytes" "encoding/gob" "encoding/json" "flag" "fmt" "log" "math" "net/http" "os" "os/signal" "strconv" "strings" "sync" "time" "io/ioutil" //"./utils" "gopkg.in/natefinch/lumberjack.v2" "os/exec" "github.com/boltdb/bolt" "github.com/yosssi/gmq/mqtt" "github.com/yosssi/gmq/mqtt/client" "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/gorilla/handlers" ) const ( // Time allowed to write the file to the client. writeWait = 10 * time.Second // Time allowed to read the next pong message from the client. pongWait = 60 * time.Second // Send pings to client with this period. Must be less than pongWait. pingPeriod = (pongWait * 9) / 10 beaconPeriod = 2 * time.Second ) // data structures type Settings struct { Location_confidence int64 `json:"location_confidence"` Last_seen_threshold int64 `json:"last_seen_threshold"` Beacon_metrics_size int `json:"beacon_metrics_size"` HA_send_interval int64 `json:"ha_send_interval"` HA_send_changes_only bool `json:"ha_send_changes_only"` } type Incoming_json struct { Hostname string `json:"hostname"` MAC string `json:"mac"` RSSI int64 `json:"rssi"` Is_scan_response string `json:"is_scan_response"` Ttype string `json:"type"` Data string `json:"data"` Beacon_type string `json:"beacon_type"` UUID string `json:"uuid"` Major string `json:"major"` Minor string `json:"minor"` TX_power string `json:"tx_power"` Namespace string `json:"namespace"` Instance_id string `json:"instance_id"` // button stuff HB_ButtonCounter int64 `json:"hb_button_counter"` HB_ButtonCounter_Prev int64 `json:"hb_button_counter"` HB_Battery int64 `json:"hb_button_battery"` HB_RandomNonce string `json:"hb_button_random"` HB_ButtonMode string `json:"hb_button_mode"` } type Advertisement struct { ttype string content string seen int64 } type beacon_metric struct { location string distance float64 rssi int64 timestamp int64 } type Location struct { name string lock sync.RWMutex } type Best_location struct { distance float64 name string last_seen int64 } type HTTP_location struct { Previous_confident_location string `json:"previous_confident_location"` Distance float64 `json:"distance"` Name string `json:"name"` Beacon_name string `json:"beacon_name"` Beacon_id string `json:"beacon_id"` Beacon_type string `json:"beacon_type"` HB_Battery int64 `json:"hb_button_battery"` HB_ButtonMode string `json:"hb_button_mode"` HB_ButtonCounter int64 `json:"hb_button_counter"` Location string `json:"location"` Last_seen int64 `json:"last_seen"` } type Location_change struct { Beacon_ref Beacon `json:"beacon_info"` Name string `json:"name"` Beacon_name string `json:"beacon_name"` Previous_location string `json:"previous_location"` New_location string `json:"new_location"` Timestamp int64 `json:"timestamp"` } type HA_message struct { Beacon_id string `json:"id"` Beacon_name string `json:"name"` Distance float64 `json:"distance"` } type HTTP_locations_list struct { Beacons []HTTP_location `json:"beacons"` //Buttons []Button `json:"buttons"` } type Beacon struct { Name string `json:"name"` Beacon_id string `json:"beacon_id"` Beacon_type string `json:"beacon_type"` Beacon_location string `json:"beacon_location"` Last_seen int64 `json:"last_seen"` Incoming_JSON Incoming_json `json:"incoming_json"` Distance float64 `json:"distance"` Previous_location string Previous_confident_location string expired_location string Location_confidence int64 Location_history []string beacon_metrics []beacon_metric HB_ButtonCounter int64 `json:"hb_button_counter"` HB_ButtonCounter_Prev int64 `json:"hb_button_counter"` HB_Battery int64 `json:"hb_button_battery"` HB_RandomNonce string `json:"hb_button_random"` HB_ButtonMode string `json:"hb_button_mode"` } type Button struct { Name string `json:"name"` Button_id string `json:"button_id"` Button_type string `json:"button_type"` Button_location string `json:"button_location"` Incoming_JSON Incoming_json `json:"incoming_json"` Distance float64 `json:"distance"` Last_seen int64 `json:"last_seen"` HB_ButtonCounter int64 `json:"hb_button_counter"` HB_Battery int64 `json:"hb_button_battery"` HB_RandomNonce string `json:"hb_button_random"` HB_ButtonMode string `json:"hb_button_mode"` } type Beacons_list struct { Beacons map[string]Beacon `json:"beacons"` lock sync.RWMutex } type Locations_list struct { locations map[string]Location lock sync.RWMutex } var clients = make(map[*websocket.Conn]bool) // connected clients var broadcast = make(chan Message) // broadcast channel // Define our message object type Message struct { Email string `json:"email"` Username string `json:"username"` Message string `json:"message"` } // Struttura per il parsing JSON multiplo type RawReading struct { Timestamp string `json:"timestamp"` Type string `json:"type"` MAC string `json:"mac"` RSSI int `json:"rssi"` RawData string `json:"rawData"` } // GLOBALS 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) // create the file if it doesn't exists with O_CREATE, Set the file up for read write, add the append flag and set the permission //f, err := os.OpenFile("/data/conf/presence/db.json", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0660) //if err != nil { // log.Fatal(err) //} // write to file, f.Write() //f.Write(btn_msg) } 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 } //fmt.Printf("beacon: %s list: %#v\n", beacon.Name, loc_list) // now go through the list and find the largest, that's the location 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} // //filter, only let this location become best if it was X times in a row // if best_location.name == beacon.Previous_location { // beacon.Location_confidence = beacon.Location_confidence + 1 // } else { // beacon.Location_confidence = 0 // /////fmt.Printf("beacon.Location_confidence %f\n", beacon.Location_confidence) // } // Aggiungiamo il nuovo best_location allo storico 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) } // Read in a new message as JSON and map it to a Message object //err := ws.ReadJSON(&msg) /*msg := Message{ Email: "apple", Username: "peach", Message: "change",} 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*/ ///utils.Log.Printf("%s changes ",beacon.Beacon_id) 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) } } } /////fmt.Printf("\n\n%s is most likely in %s with average distance %f \n\n", beacon.Name, best_location.name, best_location.distance) ////beacon.logger.Printf("Log content: user id %v \n", beacon.Name) // publish this to a topic // Publish a message. 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) } } /*for _, button := range Buttons_list { http_results.Buttons = append(http_results.Buttons, button) }*/ if should_persist { persistBeacons() } } /*func doSomething(bcon Beacon, testo string ) { bcon.logger.Printf("Log content: user id %v \n", beacon.Name) }*/ 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) } //debug list them out /*fmt.Println("Database beacons:") for _, beacon := range BEACONS.Beacons { fmt.Println("Database has known beacon: " + beacon.Beacon_id + " " + beacon.Name) dog := new(user) //createUser( beacon.Name, true) //user1 := createUser( beacon.Name, true) //doSomething(beacon, "hello") // userFIle := &lumberjack.Logger{ Filename: "/data/presence/presence/beacon_log_" + beacon.Name + ".log", MaxSize: 250, // mb MaxBackups: 5, MaxAge: 10, // in days } dog.id = beacon.Name dog.logger = log.New(userFIle, "User: ", log.Ldate|log.Ltime|log.Lshortfile) dog.logger.Printf("Log content: user id %v \n", beacon.Name) logger=append(logger,dog) } fmt.Println("leng has %d\n",len(logger)) fmt.Printf("%v", logger) fmt.Println("Settings has %#v\n", settings)*/ /**/ 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() ///fmt.Println("sawbeacon " + this_beacon_id + " at " + incoming.Hostname) //logger["FCB8351F5A21"].logger.Printf("Log content: user id \n") //if this beacon isn't in our search list, add it to the latest_beacons pile. 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{} // initialize empty-object json file if not found //if _, err := ioutil.ReadFile(Db); err != nil { // str := "{}" // if err = ioutil.WriteFile(Db, []byte(str), 0644); err != nil { // log.Fatal(err) // } //} // create channel to communicate over //jobs := make(chan Job) // start watching jobs channel for work //go ProcessJobs(jobs, Db) // create dependencies //client := &TodoClient{Jobs: jobs} //handlers := &TodoHandlers{Client: client} //work := WorkRequest{Name: name, Delay: delay} //jobs <- work 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() ///utils.NewLog(*logpath) ///utils.Log.Println("hello") // Set up channel on which to send signal notifications. 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 { //Formato CSV //ingics solo annuncio //publish_out/171061001180 $GPRP,C83F8F17DB35,F5B0B0419FEF,-44,02010612FF590080BC280102FFFFFFFF000000000000,1749648798 //ingics tasto premuto //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) } } func startServer() { headersOk := handlers.AllowedHeaders([]string{"X-Requested-With"}) originsOk := handlers.AllowedOrigins([]string{"*"}) methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "DELETE","OPTIONS"}) // Set up HTTP server r := mux.NewRouter() r.HandleFunc("/api/results", resultsHandler) r.HandleFunc("/api/beacons/{beacon_id}", beaconsDeleteHandler).Methods("DELETE") r.HandleFunc("/api/beacons", beaconsListHandler).Methods("GET") r.HandleFunc("/api/beacons", beaconsAddHandler).Methods("POST") //since beacons are hashmap, just have put and post be same thing. it'll either add or modify that entry r.HandleFunc("/api/beacons", beaconsAddHandler).Methods("PUT") r.HandleFunc("/api/latest-beacons", latestBeaconsListHandler).Methods("GET") r.HandleFunc("/api/settings", settingsListHandler).Methods("GET") r.HandleFunc("/api/settings", settingsEditHandler).Methods("POST") r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(http.Dir("static_html/js/")))) r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(http.Dir("static_html/css/")))) r.PathPrefix("/img/").Handler(http.StripPrefix("/img/", http.FileServer(http.Dir("static_html/img/")))) r.PathPrefix("/").Handler(http.FileServer(http.Dir("static_html/"))) http.Handle("/", r) mxWS := mux.NewRouter() mxWS.HandleFunc("/ws/api/beacons", serveWs) mxWS.HandleFunc("/ws/api/beacons/latest", serveLatestBeaconsWs) mxWS.HandleFunc("/ws/broadcast", handleConnections) http.Handle("/ws/", mxWS) go func() { log.Fatal(http.ListenAndServe(*httpws_host_path_ptr, nil)) }() // Start listening for incoming chat messages go handleMessages() ///"/conf/etc/cert/certs/services/htdocs/majornet.crt", "/conf/etc/cert/private/services/htdocs/majornet.key" http.ListenAndServe(*http_host_path_ptr, handlers.CORS(originsOk, headersOk, methodsOk)(r)) } func handleConnections(w http.ResponseWriter, r *http.Request) { // Upgrade initial GET request to a websocket ws, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Fatal(err) } // Make sure we close the connection when the function returns defer ws.Close() // Register our new client clients[ws] = true for { var msg Message // Read in a new message as JSON and map it to a Message object err := ws.ReadJSON(&msg) if err != nil { log.Printf("error: %v", err) delete(clients, ws) break } // Send the newly received message to the broadcast channel broadcast <- msg } } func handleMessages() { for { // Grab the next message from the broadcast channel msg := <-broadcast // Send it out to every client that is currently connected for client := range clients { err := client.WriteJSON(msg) if err != nil { log.Printf("error: %v", err) client.Close() delete(clients, client) } } } } func resultsHandler(w http.ResponseWriter, r *http.Request) { http_results_lock.RLock() js, err := json.Marshal(http_results) http_results_lock.RUnlock() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write(js) } func beaconsListHandler(w http.ResponseWriter, r *http.Request) { latest_list_lock.RLock() js, err := json.Marshal(BEACONS) latest_list_lock.RUnlock() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write(js) } func persistBeacons() error { // gob it first buf := &bytes.Buffer{} enc := gob.NewEncoder(buf) if err := enc.Encode(BEACONS); err != nil { return err } key := []byte("beacons_list") // store some data err = db.Update(func(tx *bolt.Tx) error { bucket, err := tx.CreateBucketIfNotExists(world) if err != nil { return err } err = bucket.Put(key, []byte(buf.String())) if err != nil { return err } return nil }) return nil } func persistSettings() error { // gob it first buf := &bytes.Buffer{} enc := gob.NewEncoder(buf) if err := enc.Encode(settings); err != nil { return err } key := []byte("settings") // store some data err = db.Update(func(tx *bolt.Tx) error { bucket, err := tx.CreateBucketIfNotExists(world) if err != nil { return err } err = bucket.Put(key, []byte(buf.String())) if err != nil { return err } return nil }) return nil } func beaconsAddHandler(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) var in_beacon Beacon err = decoder.Decode(&in_beacon) if err != nil { http.Error(w, err.Error(), 400) return } //make sure name and beacon_id are present if (len(strings.TrimSpace(in_beacon.Name)) == 0) || (len(strings.TrimSpace(in_beacon.Beacon_id)) == 0) { http.Error(w, "name and beacon_id cannot be blank", 400) return } BEACONS.Beacons[in_beacon.Beacon_id] = in_beacon err := persistBeacons() if err != nil { http.Error(w, "trouble persisting beacons list, create bucket", 500) return } w.Write([]byte("ok")) } func beaconsDeleteHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) beacon_id := vars["beacon_id"] delete(BEACONS.Beacons, beacon_id) _, ok := Buttons_list[beacon_id] if ok { delete(Buttons_list, beacon_id) } err := persistBeacons() if err != nil { http.Error(w, "trouble persisting beacons list, create bucket", 500) return } w.Write([]byte("ok")) } func latestBeaconsListHandler(w http.ResponseWriter, r *http.Request) { latest_list_lock.RLock() var la = make([]Beacon, 0) for _, b := range Latest_beacons_list { la = append(la, b) } latest_list_lock.RUnlock() js, err := json.Marshal(la) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write(js) } func settingsListHandler(w http.ResponseWriter, r *http.Request) { js, err := json.Marshal(settings) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write(js) } func settingsEditHandler(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) var in_settings Settings err = decoder.Decode(&in_settings) if err != nil { http.Error(w, err.Error(), 400) return } //make sure values are > 0 if (in_settings.Location_confidence <= 0) || (in_settings.Last_seen_threshold <= 0) || (in_settings.HA_send_interval <= 0) { http.Error(w, "values must be greater than 0", 400) return } settings = in_settings err := persistSettings() if err != nil { http.Error(w, "trouble persisting settings, create bucket", 500) return } w.Write([]byte("ok")) } //websocket stuff func reader(ws *websocket.Conn) { defer ws.Close() ws.SetReadLimit(512) ws.SetReadDeadline(time.Now().Add(pongWait)) ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) for { _, _, err := ws.ReadMessage() if err != nil { break } } } func writer(ws *websocket.Conn) { pingTicker := time.NewTicker(pingPeriod) beaconTicker := time.NewTicker(beaconPeriod) defer func() { pingTicker.Stop() beaconTicker.Stop() ws.Close() }() for { select { case <-beaconTicker.C: http_results_lock.RLock() js, err := json.Marshal(http_results) http_results_lock.RUnlock() if err != nil { js = []byte("error") } ws.SetWriteDeadline(time.Now().Add(writeWait)) if err := ws.WriteMessage(websocket.TextMessage, js); err != nil { return } case <-pingTicker.C: ws.SetWriteDeadline(time.Now().Add(writeWait)) if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil { return } } } } func serveWs(w http.ResponseWriter, r *http.Request) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { if _, ok := err.(websocket.HandshakeError); !ok { log.Println(err) } return } go writer(ws) reader(ws) } func latestBeaconWriter(ws *websocket.Conn) { pingTicker := time.NewTicker(pingPeriod) beaconTicker := time.NewTicker(beaconPeriod) defer func() { pingTicker.Stop() beaconTicker.Stop() ws.Close() }() for { select { case <-beaconTicker.C: latest_list_lock.RLock() var la = make([]Beacon, 0) for _, b := range Latest_beacons_list { la = append(la, b) } latest_list_lock.RUnlock() js, err := json.Marshal(la) if err != nil { js = []byte("error") } ws.SetWriteDeadline(time.Now().Add(writeWait)) if err := ws.WriteMessage(websocket.TextMessage, js); err != nil { return } case <-pingTicker.C: ws.SetWriteDeadline(time.Now().Add(writeWait)) if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil { return } } } } func serveLatestBeaconsWs(w http.ResponseWriter, r *http.Request) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { if _, ok := err.(websocket.HandshakeError); !ok { log.Println(err) } return } go latestBeaconWriter(ws) reader(ws) }