| @@ -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 | |||
| @@ -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) { | |||
| @@ -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 | |||
| @@ -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= | |||
| @@ -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) | |||
| } | |||
| } | |||
| @@ -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")) | |||
| } | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| @@ -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) | |||