3 コミット

20個のファイルの変更196行の追加37行の削除
分割表示
  1. バイナリ
      bridge
  2. バイナリ
      decoder
  3. +0
    -2
      internal/app/server/events.go
  4. +6
    -0
      internal/app/server/routes.go
  5. +2
    -3
      internal/pkg/apiclient/auth.go
  6. +0
    -8
      internal/pkg/apiclient/data.go
  7. +1
    -3
      internal/pkg/apiclient/updatedb.go
  8. +0
    -1
      internal/pkg/apiclient/utils.go
  9. +1
    -2
      internal/pkg/bridge/handler.go
  10. +0
    -6
      internal/pkg/common/utils/beacons.go
  11. +75
    -0
      internal/pkg/controller/floor_controller.go
  12. +51
    -0
      internal/pkg/controller/tracks_controller.go
  13. +1
    -1
      internal/pkg/database/database.go
  14. +11
    -0
      internal/pkg/model/floor.go
  15. +3
    -0
      internal/pkg/model/tracks.go
  16. +27
    -2
      internal/pkg/service/beacon_service.go
  17. バイナリ
      location
  18. +9
    -0
      scripts/api/tracks.sh
  19. +9
    -9
      scripts/build/build.sh
  20. バイナリ
      server

バイナリ
bridge ファイルの表示


バイナリ
decoder ファイルの表示


+ 0
- 2
internal/app/server/events.go ファイルの表示

@@ -3,7 +3,6 @@ package server
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"time"

@@ -57,7 +56,6 @@ func RunEventLoop(ctx context.Context, a *ServerApp) {
continue
}
if msg.Type == "Eddystone" && msg.Battery < 3000 {
fmt.Printf("Sending alert for battery low: %+v\n", msg)
service.SendAlert(id, "BatteryLow", a.KafkaManager.GetWriter("alert"), ctx, a.DB)
}
case <-beaconTicker.C:


+ 6
- 0
internal/app/server/routes.go ファイルの表示

@@ -57,8 +57,14 @@ func (a *ServerApp) RegisterRoutes() http.Handler {
r.HandleFunc("/reslevis/alerts/{id}", controller.AlertUpdateStatusController(a.DB, a.ctx)).Methods("PATCH")
r.HandleFunc("/reslevis/alerts/{id}", controller.AlertDeleteController(a.DB, a.ctx)).Methods("DELETE")

r.HandleFunc("/reslevis/getFloors", controller.FloorListController(a.DB, a.ctx)).Methods("GET")
r.HandleFunc("/reslevis/postFloor", controller.FloorAddController(a.DB, a.ctx)).Methods("POST")
r.HandleFunc("/reslevis/updateFloor", controller.FloorUpdateController(a.DB, a.ctx)).Methods("PUT")
r.HandleFunc("/reslevis/removeFloor/{id}", controller.FloorDeleteController(a.DB, a.ctx)).Methods("DELETE")

// Tracks
r.HandleFunc("/reslevis/getTracks/{id}", controller.TracksListController(a.DB, a.ctx)).Methods("GET")
r.HandleFunc("/reslevis/getTracks", controller.TracksListAllController(a.DB, a.ctx)).Methods("GET")

r.HandleFunc("/reslevis/health", controller.HealthController(a.AppState, a.ctx)).Methods("GET")



+ 2
- 3
internal/pkg/apiclient/auth.go ファイルの表示

@@ -26,7 +26,6 @@ func GetToken(ctx context.Context, cfg *config.Config, client *http.Client) (str

req, err := http.NewRequest("POST", fmt.Sprintf("%s/realms/API.Server.local/protocol/openid-connect/token", cfg.APIAuthURL), strings.NewReader(formData.Encode()))
if err != nil {
fmt.Println("error", err)
return "", err
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
@@ -34,16 +33,16 @@ func GetToken(ctx context.Context, cfg *config.Config, client *http.Client) (str
req = req.WithContext(ctx)
res, err := client.Do(req)
if err != nil {
fmt.Println("error", err)
return "", err
}

var j response

if err := json.NewDecoder(res.Body).Decode(&j); err != nil {
fmt.Println("error", err)
return "", err
}

fmt.Printf("token: %s\n", j.Token)

return j.Token, nil
}

+ 0
- 8
internal/pkg/apiclient/data.go ファイルの表示

@@ -12,14 +12,12 @@ import (
func GetTrackers(token string, client *http.Client, cfg *config.Config) ([]model.Tracker, error) {
res, err := getRequest(token, "getTrackers", client, cfg)
if err != nil {
fmt.Printf("error get trackers: %+v\n", err)
return []model.Tracker{}, err
}

var i []model.Tracker
err = json.NewDecoder(res.Body).Decode(&i)
if err != nil {
fmt.Printf("error decode trackers: %+v\n", err)
return []model.Tracker{}, err
}

@@ -29,7 +27,6 @@ func GetTrackers(token string, client *http.Client, cfg *config.Config) ([]model
func GetGateways(token string, client *http.Client, cfg *config.Config) ([]model.Gateway, error) {
res, err := getRequest(token, "getGateways", client, cfg)
if err != nil {
fmt.Printf("error get gateways: %+v\n", err)
return []model.Gateway{}, err
}

@@ -76,7 +73,6 @@ func InferPosition(token string, client *http.Client, cfg *config.Config) (model
url := fmt.Sprintf("%s/ble-ai/infer", cfg.APIBaseURL)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
fmt.Printf("error new request: %+v\n", err)
return model.PositionResponse{}, err
}

@@ -84,20 +80,16 @@ func InferPosition(token string, client *http.Client, cfg *config.Config) (model

res, err := client.Do(req)
if err != nil {
fmt.Printf("error do request: %+v\n", err)
return model.PositionResponse{}, err
}

fmt.Printf("res.status: %s\n", res.Status)
if res.StatusCode != 200 {
fmt.Printf("error status code: %d\n", res.StatusCode)
return model.PositionResponse{}, fmt.Errorf("status code: %d", res.StatusCode)
}

var i model.PositionResponse
err = json.NewDecoder(res.Body).Decode(&i)
if err != nil {
fmt.Printf("error decode response: %+v\n", err)
return model.PositionResponse{}, err
}



+ 1
- 3
internal/pkg/apiclient/updatedb.go ファイルの表示

@@ -31,8 +31,7 @@ func UpdateDB(db *gorm.DB, ctx context.Context, cfg *config.Config, writer *kafk
if trackers, err := GetTrackers(token, client, cfg); err == nil {
syncTable(db, trackers)
if err := controller.SendKafkaMessage(writer, &model.ApiUpdate{Method: "DELETE", MAC: "all"}, ctx); err != nil {
msg := fmt.Sprintf("Error in sending delete all from lookup message: %v", err)
slog.Error(msg)
slog.Error("Error in sending delete all from lookup message", "error", err)
}

for _, v := range trackers {
@@ -64,7 +63,6 @@ func UpdateDB(db *gorm.DB, ctx context.Context, cfg *config.Config, writer *kafk
if inferredPosition, err := InferPosition(token, client, cfg); err == nil {
for _, v := range inferredPosition.Items {
mac := convertMac(v.Mac)
fmt.Println(mac)
db.Model(&model.Tracker{}).Where("mac = ?", mac).Update("x", v.X).Update("y", v.Y)
}
}


+ 0
- 1
internal/pkg/apiclient/utils.go ファイルの表示

@@ -14,7 +14,6 @@ func setHeader(req *http.Request, token string) {

func getRequest(token, route string, client *http.Client, cfg *config.Config) (*http.Response, error) {
url := fmt.Sprintf("%s/reslevis/%s", cfg.APIBaseURL, route)
fmt.Printf("url: %s\n", url)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err


+ 1
- 2
internal/pkg/bridge/handler.go ファイルの表示

@@ -3,7 +3,6 @@ package bridge
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"strings"
"time"
@@ -60,7 +59,7 @@ func HandleMQTTMessage(topic string, payload []byte, appState *appcontext.AppSta
}
return
} else {
fmt.Println("CSV message: ", msgStr)
slog.Debug("CSV message received", "topic", topic, "message", msgStr)
}
// CSV format: validate minimum fields (e.g. 6 columns); full parsing can be added later
s := strings.Split(msgStr, ",")


+ 0
- 6
internal/pkg/common/utils/beacons.go ファイルの表示

@@ -1,8 +1,6 @@
package utils

import (
"fmt"

"github.com/AFASystems/presence/internal/pkg/common/appcontext"
"github.com/AFASystems/presence/internal/pkg/model"
)
@@ -45,14 +43,11 @@ func LoopADStructures(b []byte, i [][2]int, id string, parserRegistry *model.Par
for _, r := range i {
ad := b[r[0]:r[1]]
if !isValidADStructure(ad) {
fmt.Println("invalid ad structure: ", beacon)
break
}
for name, parser := range parserRegistry.ParserList {
if parser.CanParse(ad) {
event, ok := parser.Parse(name, ad)
fmt.Println("beacon id: ", id)
fmt.Println("parser can parse: ", name)
if ok {
event.ID = id
event.Name = id
@@ -60,7 +55,6 @@ func LoopADStructures(b []byte, i [][2]int, id string, parserRegistry *model.Par
}
}
}
// fmt.Println("no parser can parse: ", beacon)
}

return be


+ 75
- 0
internal/pkg/controller/floor_controller.go ファイルの表示

@@ -0,0 +1,75 @@
package controller

import (
"context"
"encoding/json"
"net/http"

"github.com/AFASystems/presence/internal/pkg/api/response"
"github.com/AFASystems/presence/internal/pkg/model"
"github.com/AFASystems/presence/internal/pkg/validation"
"github.com/gorilla/mux"
"gorm.io/gorm"
)

func FloorAddController(db *gorm.DB, context context.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var floor model.Floor
if err := json.NewDecoder(r.Body).Decode(&floor); err != nil {
response.BadRequest(w, "invalid request body")
return
}
if err := validation.Struct(&floor); err != nil {
response.BadRequest(w, err.Error())
return
}
if err := db.WithContext(context).Create(&floor).Error; err != nil {
response.InternalError(w, "failed to create floor", err)
return
}

response.JSON(w, http.StatusCreated, map[string]string{"status": "created"})
}
}

func FloorListController(db *gorm.DB, context context.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var floors []model.Floor
if err := db.WithContext(context).Find(&floors).Error; err != nil {
response.InternalError(w, "failed to list floors", err)
return
}
if err := db.WithContext(context).Find(&floors).Error; err != nil {
response.InternalError(w, "failed to list floors", err)
return
}
response.JSON(w, http.StatusOK, floors)
}
}

func FloorUpdateController(db *gorm.DB, context context.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var floor model.Floor
if err := json.NewDecoder(r.Body).Decode(&floor); err != nil {
response.BadRequest(w, "invalid request body")
return
}
}
}

func FloorDeleteController(db *gorm.DB, context context.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
res := db.WithContext(context).Delete(&model.Floor{}, "id = ?", id)
if res.RowsAffected == 0 {
response.NotFound(w, "floor not found")
return
}
if res.Error != nil {
response.InternalError(w, "failed to delete floor", res.Error)
return
}

response.JSON(w, http.StatusOK, map[string]string{"status": "deleted"})
}
}

+ 51
- 0
internal/pkg/controller/tracks_controller.go ファイルの表示

@@ -2,6 +2,7 @@ package controller

import (
"context"
"log/slog"
"net/http"
"strconv"
"time"
@@ -42,3 +43,53 @@ func TracksListController(db *gorm.DB, context context.Context) http.HandlerFunc
response.JSON(w, http.StatusOK, tracks)
}
}

func TracksListAllController(db *gorm.DB, context context.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
lStr := query.Get("limit")
if lStr == "" {
lStr = "100"
}

limit, err := strconv.Atoi(lStr)
if err != nil {
slog.Error("Error in converting limit string to integer value")
response.InternalError(w, "internal server error", err)
return
}

offsetStr := query.Get("offset")
if offsetStr == "" {
offsetStr = "0"
}

offset, err := strconv.Atoi(offsetStr)
if err != nil {
slog.Error("Error in converting offset string to integer value")
response.InternalError(w, "internal server error", err)
return
}

var tracks []model.Tracks

// Subquery to get the latest timestamp per tracker
subQuery := db.Model(&model.Tracks{}).
Select("tracker, MAX(timestamp) AS max_timestamp").
Group("tracker")

if err := db.WithContext(context).
Table("tracks").
Select("tracks.*").
Joins("JOIN (?) AS latest ON tracks.tracker = latest.tracker AND tracks.timestamp = latest.max_timestamp", subQuery).
Order("tracks.timestamp DESC").
Limit(limit).
Offset(offset).
Find(&tracks).Error; err != nil {
response.InternalError(w, "failed to list tracks", err)
return
}

response.JSON(w, http.StatusOK, tracks)
}
}

+ 1
- 1
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{}, appcontext.Settings{}, model.Tracks{}, &model.Alert{}); err != nil {
if err := db.AutoMigrate(&model.Gateway{}, model.Zone{}, model.TrackerZones{}, model.Tracker{}, model.Config{}, appcontext.Settings{}, model.Tracks{}, &model.Alert{}, model.Floor{}); err != nil {
return nil, err
}



+ 11
- 0
internal/pkg/model/floor.go ファイルの表示

@@ -0,0 +1,11 @@
package model

type Floor struct {
ID string `gorm:"unique;primaryKey"`
Name string `json:"name"`
FloorNumber int `json:"floornumber"`
Image string `json:"image"`
Description string `json:"description"`
Scale int `json:"scale"`
Building string `json:"building"`
}

+ 3
- 0
internal/pkg/model/tracks.go ファイルの表示

@@ -17,4 +17,7 @@ type Tracks struct {
Floor string `json:"floor"`
Signal int64 `json:"signal"`
Building string `json:"building"`
X float32 `json:"x"`
Y float32 `json:"y"`
Z float32 `json:"z"`
}

+ 27
- 2
internal/pkg/service/beacon_service.go ファイルの表示

@@ -75,7 +75,29 @@ func LocationToBeaconService(msg model.HTTPLocation, db *gorm.DB, writer *kafka.
return
}

if err := db.Create(&model.Tracks{UUID: msg.ID, Timestamp: time.Now(), Gateway: gw.ID, GatewayMac: gw.MAC, Tracker: msg.ID, Floor: gw.Floor, Building: gw.Building, TrackerMac: tracker.MAC, Signal: msg.RSSI}).Error; err != nil {
var floor model.Floor
if err := db.Where("id = ?", gw.Floor).First(&floor).Error; err != nil {
msg := fmt.Sprintf("Floor not found for ID: %s", gw.Floor)
slog.Error(msg)
return
}

fmt.Printf("floor: %d\n", floor.FloorNumber)

if err := db.Create(&model.Tracks{
UUID: msg.ID,
Timestamp: time.Now(),
Gateway: gw.ID,
GatewayMac: gw.MAC,
Tracker: msg.ID,
Floor: gw.Floor,
Building: gw.Building,
TrackerMac: tracker.MAC,
Signal: msg.RSSI,
X: gw.X,
Y: gw.Y,
Z: float32(floor.FloorNumber),
}).Error; err != nil {
msg := fmt.Sprintf("Error in saving distance for beacon: %v", err)
slog.Error(msg)
return
@@ -92,6 +114,7 @@ func LocationToBeaconService(msg model.HTTPLocation, db *gorm.DB, writer *kafka.
}

func LocationToBeaconServiceAI(msg model.HTTPLocation, db *gorm.DB, writer *kafka.Writer, ctx context.Context) {
fmt.Printf("msg: %+v\n", msg)
tracker, err := findTracker(msg, db)
if err != nil {
msg := fmt.Sprintf("Error in finding tracker: %v", err)
@@ -113,7 +136,9 @@ func LocationToBeaconServiceAI(msg model.HTTPLocation, db *gorm.DB, writer *kafk
return
}

if err := db.Create(&model.Tracks{UUID: tracker.ID, Timestamp: time.Now(), Gateway: gw.ID, GatewayMac: gw.MAC, Tracker: tracker.ID, Floor: gw.Floor, Building: gw.Building, TrackerMac: tracker.MAC}).Error; err != nil {
// fmt.Printf("gw: %+v\n", gw)

if err := db.Create(&model.Tracks{UUID: tracker.ID, Timestamp: time.Now(), Gateway: gw.ID, GatewayMac: gw.MAC, Tracker: tracker.ID, Floor: gw.Floor, Building: gw.Building, TrackerMac: tracker.MAC, X: msg.X, Y: msg.Y, Z: msg.Z}).Error; err != nil {
msg := fmt.Sprintf("Error in saving distance for beacon: %v", err)
slog.Error(msg)
return


バイナリ
location ファイルの表示


+ 9
- 0
scripts/api/tracks.sh ファイルの表示

@@ -31,3 +31,12 @@ echo "==================================="
echo "Query Parameters: limit, from (RFC3339), to (RFC3339)"
echo "Get tracker UUIDs from: GET /reslevis/getTrackers"
echo "==================================="

echo "4. Get all latest positions for all trackers:"
curl -s -X GET "${BASE_URL}/reslevis/getTracks" | jq '.'
echo -e "\n"

echo "==================================="
echo "Query Parameters: limit, from (RFC3339), to (RFC3339)"
echo "Get tracker UUIDs from: GET /reslevis/getTrackers"
echo "==================================="

+ 9
- 9
scripts/build/build.sh ファイルの表示

@@ -1,20 +1,20 @@
#!/bin/bash

# Build the server
docker build -t afasystemadmin/ble-ai-localizer:server_v1.1 -f ../../build/package/Dockerfile.server ../../
¸
docker build -t afasystemadmin/ble-ai-localizer:server_v1.2 -f ../../build/package/Dockerfile.server ../../
# Build the location
docker build -t afasystemadmin/ble-ai-localizer:location_v1.1 -f ../../build/package/Dockerfile.location ../../
docker build -t afasystemadmin/ble-ai-localizer:location_v1.2 -f ../../build/package/Dockerfile.location ../../

# Build the decoder
docker build -t afasystemadmin/ble-ai-localizer:decoder_v1.1 -f ../../build/package/Dockerfile.decoder ../../
docker build -t afasystemadmin/ble-ai-localizer:decoder_v1.2 -f ../../build/package/Dockerfile.decoder ../../

# Build the bridge
docker build -t afasystemadmin/ble-ai-localizer:bridge_v1.1 -f ../../build/package/Dockerfile.bridge ../../
docker build -t afasystemadmin/ble-ai-localizer:bridge_v1.2 -f ../../build/package/Dockerfile.bridge ../../

docker image ls

docker push afasystemadmin/ble-ai-localizer:server_v1.1
docker push afasystemadmin/ble-ai-localizer:location_v1.1
docker push afasystemadmin/ble-ai-localizer:decoder_v1.1
docker push afasystemadmin/ble-ai-localizer:bridge_v1.1
docker push afasystemadmin/ble-ai-localizer:server_v1.2
docker push afasystemadmin/ble-ai-localizer:location_v1.2
docker push afasystemadmin/ble-ai-localizer:decoder_v1.2
docker push afasystemadmin/ble-ai-localizer:bridge_v1.2

バイナリ
server ファイルの表示


読み込み中…
キャンセル
保存