From 62df32f71513bf3946d9d7c203f85a2abf9e265e Mon Sep 17 00:00:00 2001 From: blazSmehov Date: Thu, 15 Jan 2026 15:54:52 +0100 Subject: [PATCH] feat: change used locations algorithm based on API call --- cmd/location/main.go | 36 +++-------- cmd/server/main.go | 6 +- go.mod | 1 + go.sum | 2 + internal/pkg/common/appcontext/context.go | 34 +++++----- .../pkg/controller/settings_controller.go | 63 ++++++++----------- internal/pkg/database/database.go | 2 +- internal/pkg/model/settings.go | 12 ++++ internal/pkg/model/types.go | 16 ----- internal/pkg/service/beacon_service.go | 17 ----- 10 files changed, 67 insertions(+), 122 deletions(-) create mode 100644 internal/pkg/model/settings.go diff --git a/cmd/location/main.go b/cmd/location/main.go index 4894339..05a0e39 100644 --- a/cmd/location/main.go +++ b/cmd/location/main.go @@ -53,7 +53,7 @@ func main() { defer locTicker.Stop() chRaw := make(chan model.BeaconAdvertisement, 2000) - chSettings := make(chan model.SettingsVal, 5) + chSettings := make(chan map[string]any, 5) wg.Add(3) go kafkaclient.Consume(rawReader, chRaw, ctx, &wg) @@ -65,7 +65,13 @@ eventLoop: case <-ctx.Done(): break eventLoop case <-locTicker.C: - getLikelyLocations(appState, writer) + settings := appState.GetSettings() + switch settings.CurrentAlgorithm { + case "filter": + getLikelyLocations(appState, writer) + case "ai": + fmt.Println("AI algorithm selected") + } case msg := <-chRaw: assignBeaconToList(msg, appState) case msg := <-chSettings: @@ -132,32 +138,6 @@ func getLikelyLocations(appState *appcontext.AppState, writer *kafka.Writer) { if beacon.LocationConfidence == settings.LocationConfidence && beacon.PreviousConfidentLocation != bestLocName { beacon.LocationConfidence = 0 - // js, err := json.Marshal(model.LocationChange{ - // Method: "LocationChange", - // BeaconRef: beacon, - // Name: beacon.Name, - // PreviousLocation: beacon.PreviousConfidentLocation, - // NewLocation: bestLocName, - // Timestamp: time.Now().Unix(), - // }) - - // if err != nil { - // eMsg := fmt.Sprintf("Error in marshaling: %v", err) - // slog.Error(eMsg) - // beacon.PreviousConfidentLocation = bestLocName - // beacon.PreviousLocation = bestLocName - // appState.UpdateBeacon(beacon.ID, beacon) - // continue - // } - - // msg := kafka.Message{ - // Value: js, - // } - - // err = writer.WriteMessages(context.Background(), msg) - // if err != nil { - // fmt.Println("Error in sending Kafka message") - // } } beacon.PreviousLocation = bestLocName diff --git a/cmd/server/main.go b/cmd/server/main.go index 3887d88..85deb0d 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -114,9 +114,6 @@ func main() { r := mux.NewRouter() - r.HandleFunc("/api/settings", controller.SettingsListController(appState, ctx)).Methods("GET") - r.HandleFunc("/api/settings", controller.SettingsEditController(settingsWriter, appState, ctx)).Methods("POST") - r.HandleFunc("/reslevis/getGateways", controller.GatewayListController(db)).Methods("GET") r.HandleFunc("/reslevis/postGateway", controller.GatewayAddController(db)).Methods("POST") r.HandleFunc("/reslevis/removeGateway/{id}", controller.GatewayDeleteController(db)).Methods("DELETE") @@ -142,6 +139,9 @@ func main() { r.HandleFunc("/configs/beacons/{id}", controller.ParserUpdateController(db, parserWriter, ctx)).Methods("PUT") r.HandleFunc("/configs/beacons/{id}", controller.ParserDeleteController(db, parserWriter, ctx)).Methods("DELETE") + r.HandleFunc("/reslevis/settings", controller.SettingsUpdateController(db, settingsWriter, ctx)).Methods("PATCH") + r.HandleFunc("/reslevis/settings", controller.SettingsListController(db)).Methods("GET") + wsHandler := http.HandlerFunc(serveWs(db, ctx)) restApiHandler := handlers.CORS(originsOk, headersOk, methodsOk)(r) mainHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/go.mod b/go.mod index cb5cbb8..42d5287 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/klauspost/compress v1.15.9 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pierrec/lz4/v4 v4.1.15 // indirect golang.org/x/crypto v0.42.0 // indirect golang.org/x/net v0.44.0 // indirect diff --git a/go.sum b/go.sum index aa667e1..eb49d90 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/internal/pkg/common/appcontext/context.go b/internal/pkg/common/appcontext/context.go index 55d64ae..7acc8b9 100644 --- a/internal/pkg/common/appcontext/context.go +++ b/internal/pkg/common/appcontext/context.go @@ -6,6 +6,7 @@ import ( "time" "github.com/AFASystems/presence/internal/pkg/model" + "github.com/mitchellh/mapstructure" "github.com/segmentio/kafka-go" ) @@ -31,15 +32,14 @@ func NewAppState() *AppState { Results: make(map[string]model.HTTPResult), }, settings: model.Settings{ - Settings: model.SettingsVal{ - LocationConfidence: 4, - LastSeenThreshold: 15, - BeaconMetricSize: 30, - HASendInterval: 5, - HASendChangesOnly: false, - RSSIEnforceThreshold: false, - RSSIMinThreshold: 100, - }, + CurrentAlgorithm: "filter", // possible values filter or AI + LocationConfidence: 4, + LastSeenThreshold: 15, + BeaconMetricSize: 30, + HASendInterval: 5, + HASendChangesOnly: false, + RSSIEnforceThreshold: false, + RSSIMinThreshold: 100, }, beaconEvents: model.BeaconEventList{ Beacons: make(map[string]model.BeaconEvent), @@ -283,17 +283,13 @@ func (m *AppState) GetBeaconCount() int { } // GetSettingsValue returns current settings as a value -func (m *AppState) GetSettingsValue() model.SettingsVal { - m.settings.Lock.RLock() - defer m.settings.Lock.RUnlock() - - return m.settings.Settings +func (m *AppState) GetSettingsValue() model.Settings { + return m.settings } // UpdateSettings updates the system settings (thread-safe) -func (m *AppState) UpdateSettings(newSettings model.SettingsVal) { - m.settings.Lock.Lock() - defer m.settings.Lock.Unlock() - - m.settings.Settings = newSettings +func (m *AppState) UpdateSettings(settings map[string]any) { + if err := mapstructure.Decode(settings, &m.settings); err != nil { + fmt.Printf("Error in persisting settings: %v\n", err) + } } diff --git a/internal/pkg/controller/settings_controller.go b/internal/pkg/controller/settings_controller.go index e98c53e..3a2e4de 100644 --- a/internal/pkg/controller/settings_controller.go +++ b/internal/pkg/controller/settings_controller.go @@ -3,64 +3,51 @@ package controller import ( "context" "encoding/json" - "fmt" "net/http" - "github.com/AFASystems/presence/internal/pkg/common/appcontext" "github.com/AFASystems/presence/internal/pkg/model" "github.com/segmentio/kafka-go" + "gorm.io/gorm" ) -func SettingsEditController(writer *kafka.Writer, appstate *appcontext.AppState, ctx context.Context) http.HandlerFunc { +func SettingsListController(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - decoder := json.NewDecoder(r.Body) - var inSettings model.SettingsVal - if err := decoder.Decode(&inSettings); err != nil { + var settings []model.Settings + db.Find(&settings) + res, err := json.Marshal(settings) + if err != nil { http.Error(w, err.Error(), 400) - fmt.Println("Error in decoding Settings body: ", err) return } - if !settingsCheck(inSettings) { - http.Error(w, "values must be greater than 0", 400) - fmt.Println("settings values must be greater than 0") - return - } + w.Write(res) + } +} - valueStr, err := json.Marshal(&inSettings) - if err != nil { - http.Error(w, "Error in encoding settings", 500) - fmt.Println("Error in encoding settings: ", err) +func SettingsUpdateController(db *gorm.DB, writer *kafka.Writer, ctx context.Context) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var updates map[string]any + if err := json.NewDecoder(r.Body).Decode(&updates); err != nil { + http.Error(w, "Invalid JSON", 400) return } - msg := kafka.Message{ - Value: valueStr, + if err := db.Model(&model.Settings{}).Where("id = ?", 1).Updates(updates).Error; err != nil { + http.Error(w, err.Error(), 500) + return } - if err := writer.WriteMessages(ctx, msg); err != nil { - fmt.Println("error in sending Kafka message") - http.Error(w, "Error in sending kafka message", 500) + eMsg, err := json.Marshal(updates) + if err != nil { + http.Error(w, "Error in marshaling settings updates", 400) return } + msg := kafka.Message{ + Value: eMsg, + } - // if all is OK persist settings - appstate.UpdateSettings(inSettings) - - w.Write([]byte("ok")) - } -} - -func settingsCheck(settings model.SettingsVal) bool { - if settings.LocationConfidence <= 0 || settings.LastSeenThreshold <= 0 || settings.HASendInterval <= 0 { - return false - } - - return true -} - -func SettingsListController(appstate *appcontext.AppState, ctx context.Context) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { + writer.WriteMessages(ctx, msg) + w.Write([]byte("Settings updated")) } } diff --git a/internal/pkg/database/database.go b/internal/pkg/database/database.go index 4c27e47..9f69c55 100644 --- a/internal/pkg/database/database.go +++ b/internal/pkg/database/database.go @@ -26,7 +26,7 @@ func Connect(cfg *config.Config) (*gorm.DB, error) { return nil, err } - if err := db.AutoMigrate(&model.Gateway{}, model.Zone{}, model.TrackerZones{}, model.Tracker{}, model.Config{}); err != nil { + if err := db.AutoMigrate(&model.Gateway{}, model.Zone{}, model.TrackerZones{}, model.Tracker{}, model.Config{}, model.Settings{}); err != nil { return nil, err } diff --git a/internal/pkg/model/settings.go b/internal/pkg/model/settings.go new file mode 100644 index 0000000..622b9c6 --- /dev/null +++ b/internal/pkg/model/settings.go @@ -0,0 +1,12 @@ +package model + +type Settings struct { + CurrentAlgorithm string + LocationConfidence int64 + LastSeenThreshold int64 + BeaconMetricSize int + HASendInterval int + HASendChangesOnly bool + RSSIEnforceThreshold bool + RSSIMinThreshold int64 +} diff --git a/internal/pkg/model/types.go b/internal/pkg/model/types.go index 4897625..b716f16 100644 --- a/internal/pkg/model/types.go +++ b/internal/pkg/model/types.go @@ -6,22 +6,6 @@ import ( "github.com/segmentio/kafka-go" ) -// Settings defines configuration parameters for presence detection behavior. -type SettingsVal struct { - LocationConfidence int64 `json:"location_confidence"` - LastSeenThreshold int64 `json:"last_seen_threshold"` - BeaconMetricSize int `json:"beacon_metrics_size"` - HASendInterval int64 `json:"ha_send_interval"` - HASendChangesOnly bool `json:"ha_send_changes_only"` - RSSIMinThreshold int64 `json:"rssi_min_threshold"` - RSSIEnforceThreshold bool `json:"enforce_rssi_threshold"` -} - -type Settings struct { - Settings SettingsVal - Lock sync.RWMutex -} - // BeaconAdvertisement represents the JSON payload received from beacon advertisements. type BeaconAdvertisement struct { ID string diff --git a/internal/pkg/service/beacon_service.go b/internal/pkg/service/beacon_service.go index 4a3bfa4..a02336d 100644 --- a/internal/pkg/service/beacon_service.go +++ b/internal/pkg/service/beacon_service.go @@ -60,23 +60,6 @@ func LocationToBeaconService(msg model.HTTPLocation, db *gorm.DB, writer *kafka. } } -// func LocationToBeaconService(msg model.HTTPLocation, appState *appcontext.AppState, ctx context.Context) error { -// id := msg.ID -// beacon, ok := appState.GetHTTPResult(id) -// if !ok { -// appState.UpdateHTTPResult(id, model.HTTPResult{ID: id, Position: msg.Location, Distance: msg.Distance, LastSeen: msg.LastSeen, PreviousConfidentLocation: msg.PreviousConfidentLocation}) -// } else { -// beacon.ID = id -// beacon.Position = msg.Location -// beacon.Distance = msg.Distance -// beacon.LastSeen = msg.LastSeen -// beacon.PreviousConfidentLocation = msg.PreviousConfidentLocation -// appState.UpdateHTTPResult(id, beacon) -// } - -// return nil -// } - func EventToBeaconService(msg model.BeaconEvent, appState *appcontext.AppState, ctx context.Context) error { id := msg.ID beacon, ok := appState.GetHTTPResult(id)