diff --git a/main.go b/main.go index a584c5f..5ff5694 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,6 @@ package main import ( "fmt" - "jannex/robot-control-manager/modules/cache" "jannex/robot-control-manager/modules/config" "jannex/robot-control-manager/modules/database" "jannex/robot-control-manager/modules/robot" @@ -72,9 +71,6 @@ func main() { router.SetupRoutes(app) - // TODO: Remove this - cache.SetPermitJoin(true) - rcmlogger.AddSystemLog("Server started") robot.LoadRobotsFromDatabase() diff --git a/modules/cache/permitjoin.go b/modules/cache/permitjoin.go index e609f94..0edd5da 100644 --- a/modules/cache/permitjoin.go +++ b/modules/cache/permitjoin.go @@ -18,3 +18,10 @@ func IsPermitJoinEnabled() bool { return permitJoin } + +func GetPermitJoin() bool { + pjMu.RLock() + defer pjMu.RUnlock() + + return permitJoin +} diff --git a/modules/cache/robots.go b/modules/cache/robots.go index 13bf816..3f0cc40 100644 --- a/modules/cache/robots.go +++ b/modules/cache/robots.go @@ -3,6 +3,7 @@ package cache import ( "jannex/robot-control-manager/modules/structs" "jannex/robot-control-manager/modules/utils" + "sort" "sync" "git.ex.umbach.dev/Alex/roese-utils/rspagination" @@ -57,6 +58,11 @@ func GetAllRobots(query rspagination.PageQuery) structs.RobotsResponse { }) } + // sort after created at + sort.SliceStable(r, func(i, j int) bool { + return r[i].CreatedAt.After(r[j].CreatedAt) + }) + start, end := rspagination.GetPage(len(r), query.Page, utils.RobotsPageLimit) return structs.RobotsResponse{ diff --git a/modules/robot/robot.go b/modules/robot/robot.go index d203805..81e6df3 100644 --- a/modules/robot/robot.go +++ b/modules/robot/robot.go @@ -216,23 +216,23 @@ func BroadcastSSEMessage(message structs.SSEMessage) { } } -/* -func AddJobNameToJobsWaitingList(r *structs.Robot, jobName string) { - r.JobMutex.Lock() - defer r.JobMutex.Unlock() +var PermitJoinTimer *time.Timer - r.JobsWaitingNameList = append(r.JobsWaitingNameList, jobName) -} - -func RemoveJobNameFromJobsWaitingList(r *structs.Robot, jobName string) { - r.JobMutex.Lock() - defer r.JobMutex.Unlock() - - for i, j := range r.JobsWaitingNameList { - if j == jobName { - r.JobsWaitingNameList = append(r.JobsWaitingNameList[:i], r.JobsWaitingNameList[i+1:]...) - break - } +func PermitJoinAutoDisableHandler() { + if PermitJoinTimer != nil { + PermitJoinTimer.Stop() } + + PermitJoinTimer = time.NewTimer(utils.RobotsPermitJoinAutoDisable * time.Second) + + <-PermitJoinTimer.C + + cache.SetPermitJoin(false) + + BroadcastSSEMessage(structs.SSEMessage{ + Cmd: utils.SSESentCmdPermitJoinUpdated, + Body: 0, + }) + + logger.AddSystemLog("Permit join disabled by timer") } -*/ diff --git a/modules/structs/permitjoin.go b/modules/structs/permitjoin.go index e0d0359..c0143e0 100644 --- a/modules/structs/permitjoin.go +++ b/modules/structs/permitjoin.go @@ -3,3 +3,8 @@ package structs type PermitJoinParam struct { Enabled uint8 } + +// swagger:model PermitJoinResponse +type PermitJoinResponse struct { + Enabled bool +} diff --git a/modules/utils/globals.go b/modules/utils/globals.go index fa9efc9..4b533d4 100644 --- a/modules/utils/globals.go +++ b/modules/utils/globals.go @@ -4,6 +4,7 @@ const ( RobotPingRetries = 3 RobotPingHandlerInterval = 5 // seconds RobotsPageLimit = 10 + RobotsPermitJoinAutoDisable = 120 // seconds UnauthorizedRobotsPageLimit = 10 minRobotNameLength = "2" @@ -32,6 +33,7 @@ const ( SSESentCmdRobotUpdated = 6 SSESentCmdUpdateRobotCurrentJob = 7 SSESentCmdUpdateRobotJobsWaitingCount = 8 + SSESentCmdPermitJoinUpdated = 9 ) var ( diff --git a/public/swagger/swagger.json b/public/swagger/swagger.json index 05efcfb..819ae03 100644 --- a/public/swagger/swagger.json +++ b/public/swagger/swagger.json @@ -141,6 +141,27 @@ } } }, + "/permitjoin": { + "get": { + "description": "This is used to get the current permit join status.\n", + "produces": [ + "application/json" + ], + "tags": [ + "permitjoin" + ], + "summary": "Get permit join.", + "operationId": "getPermitJoin", + "responses": { + "200": { + "description": "Permit join status", + "schema": { + "$ref": "#/definitions/PermitJoinResponse" + } + } + } + } + }, "/permitjoin/{enabled}": { "post": { "description": "This is used to enable or disable permit join.\n", @@ -213,6 +234,41 @@ "description": "Permit join is enabled" } } + }, + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "robot" + ], + "summary": "Update robot.", + "operationId": "robotUpdate", + "parameters": [ + { + "description": "Update robot body.", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Robot" + } + } + ], + "responses": { + "200": { + "description": "Robot updated" + }, + "400": { + "description": "Invalid request body" + }, + "422": { + "description": "Robot not found" + } + } } }, "/robot/authorize/{robotId}": { @@ -322,6 +378,53 @@ } }, "definitions": { + "APIRobot": { + "type": "object", + "properties": { + "Address": { + "type": "string" + }, + "ConnectedAt": { + "type": "string", + "format": "date-time" + }, + "CreatedAt": { + "type": "string", + "format": "date-time" + }, + "CurrentJobName": { + "type": "string" + }, + "FirmwareVersion": { + "type": "string" + }, + "Id": { + "type": "string" + }, + "JobsWaitingCount": { + "type": "integer", + "format": "int64" + }, + "JobsWaitingNameList": { + "type": "array", + "items": { + "type": "string" + } + }, + "Name": { + "type": "string" + }, + "Status": { + "type": "integer", + "format": "uint8" + }, + "Type": { + "type": "integer", + "format": "uint8" + } + }, + "x-go-package": "jannex/robot-control-manager/modules/structs" + }, "ControlBody": { "type": "object", "properties": { @@ -381,66 +484,11 @@ }, "x-go-package": "jannex/robot-control-manager/modules/structs" }, - "Mutex": { - "description": "A Mutex must not be copied after first use.\n\nIn the terminology of the Go memory model,\nthe n'th call to Unlock “synchronizes before” the m'th call to Lock\nfor any n \u003c m.\nA successful call to TryLock is equivalent to a call to Lock.\nA failed call to TryLock does not establish any “synchronizes before”\nrelation at all.", - "type": "object", - "title": "A Mutex is a mutual exclusion lock.\nThe zero value for a Mutex is an unlocked mutex.", - "x-go-package": "sync" - }, - "Robot": { + "PermitJoinResponse": { "type": "object", "properties": { - "Address": { - "type": "string" - }, - "ConnectedAt": { - "type": "string", - "format": "date-time" - }, - "CreatedAt": { - "type": "string", - "format": "date-time" - }, - "CurrentJobId": { - "type": "string" - }, - "CurrentJobName": { - "type": "string" - }, - "Id": { - "type": "string" - }, - "JobMutex": { - "$ref": "#/definitions/Mutex" - }, - "JobsWaitingCount": { - "type": "integer", - "format": "int64" - }, - "JobsWaitingNameList": { - "type": "array", - "items": { - "type": "string" - } - }, - "LastTaskAt": { - "type": "string", - "format": "date-time" - }, - "Name": { - "type": "string" - }, - "PingRetries": { - "type": "integer", - "format": "uint8" - }, - "Status": { - "type": "integer", - "format": "uint8" - }, - "Type": { - "type": "integer", - "format": "uint8" + "Enabled": { + "type": "boolean" } }, "x-go-package": "jannex/robot-control-manager/modules/structs" @@ -451,7 +499,7 @@ "Robots": { "type": "array", "items": { - "$ref": "#/definitions/Robot" + "$ref": "#/definitions/APIRobot" } }, "TotalPages": { @@ -484,6 +532,9 @@ "type": "string", "format": "date-time" }, + "FirmwareVersion": { + "type": "string" + }, "Id": { "type": "string" }, diff --git a/routers/api/v1/permitjoin/permitjoin.go b/routers/api/v1/permitjoin/permitjoin.go index 2137baa..430ea85 100644 --- a/routers/api/v1/permitjoin/permitjoin.go +++ b/routers/api/v1/permitjoin/permitjoin.go @@ -3,7 +3,9 @@ package permitjoin import ( "jannex/robot-control-manager/modules/cache" "jannex/robot-control-manager/modules/logger" + "jannex/robot-control-manager/modules/robot" "jannex/robot-control-manager/modules/structs" + "jannex/robot-control-manager/modules/utils" "git.ex.umbach.dev/Alex/roese-utils/rsutils" "github.com/gofiber/fiber/v2" @@ -38,12 +40,38 @@ func SetPermitJoin(c *fiber.Ctx) error { if params.Enabled == 0 { cache.SetPermitJoin(false) logger.AddSystemLog("Permit join disabled") + + robot.PermitJoinTimer.Stop() } else { cache.SetPermitJoin(true) logger.AddSystemLog("Permit join enabled") + + go robot.PermitJoinAutoDisableHandler() } - // TODO: sse + robot.BroadcastSSEMessage(structs.SSEMessage{ + Cmd: utils.SSESentCmdPermitJoinUpdated, + Body: params.Enabled == 1, + }) return c.SendStatus(fiber.StatusOK) } + +func GetPermitJoin(c *fiber.Ctx) error { + // swagger:operation GET /permitjoin permitjoin getPermitJoin + // --- + // summary: Get permit join. + // description: | + // This is used to get the current permit join status. + // produces: + // - application/json + // responses: + // "200": + // description: Permit join status + // schema: + // "$ref": "#/definitions/PermitJoinResponse" + + return c.JSON(structs.PermitJoinResponse{ + Enabled: cache.GetPermitJoin(), + }) +} diff --git a/routers/api/v1/robot/robot.go b/routers/api/v1/robot/robot.go index 53d1560..d10b903 100644 --- a/routers/api/v1/robot/robot.go +++ b/routers/api/v1/robot/robot.go @@ -9,6 +9,7 @@ import ( "jannex/robot-control-manager/modules/utils" "time" + "git.ex.umbach.dev/Alex/roese-utils/rspagination" "git.ex.umbach.dev/Alex/roese-utils/rsutils" "github.com/gofiber/fiber/v2" "github.com/google/uuid" @@ -71,9 +72,17 @@ func FirstRequest(c *fiber.Ctx) error { cache.AddUnauthorizedRobot(&newUnauthorizedRobot) + totalPages := cache.GetAllUnauthorizedRobots(rspagination.PageQuery{Page: 1}).TotalPages + robot.BroadcastSSEMessage(structs.SSEMessage{ - Cmd: utils.SSESentCmdAddUnauthorizedRobot, - Body: &newUnauthorizedRobot, + Cmd: utils.SSESentCmdAddUnauthorizedRobot, + Body: struct { + UnauthorizedRobot *structs.UnauthorizedRobot + TotalPages int + }{ + UnauthorizedRobot: &newUnauthorizedRobot, + TotalPages: totalPages, + }, }) logger.AddSystemLog("Unauthorized robot connected with id %v and type %v", body.Id, utils.GetRobotTypeString(body.Type)) @@ -101,10 +110,7 @@ func FirstRequest(c *fiber.Ctx) error { FirmwareVersion: newRobot.FirmwareVersion, }) - robot.BroadcastSSEMessage(structs.SSEMessage{ - Cmd: utils.SSESentCmdAddRobot, - Body: &newRobot, - }) + addRobotSSEMessage(&newRobot) logger.AddSystemLog("Robot connected with id %v and type %v", body.Id, utils.GetRobotTypeString(body.Type)) } @@ -112,6 +118,23 @@ func FirstRequest(c *fiber.Ctx) error { return c.JSON(structs.StatusResponse{Status: "ok"}) } +func addRobotSSEMessage(newRobot *structs.Robot) { + totalPages := cache.GetAllRobots(rspagination.PageQuery{Page: 1}).TotalPages + + robot.BroadcastSSEMessage(structs.SSEMessage{ + Cmd: utils.SSESentCmdAddRobot, + Body: struct { + Robot *structs.Robot + TotalPages int + UnauthorizedRobotsTotalPages int + }{ + Robot: newRobot, + TotalPages: totalPages, + UnauthorizedRobotsTotalPages: cache.GetAllUnauthorizedRobots(rspagination.PageQuery{Page: 1}).TotalPages, + }, + }) +} + func AuthorizeRobot(c *fiber.Ctx) error { // swagger:operation POST /robot/authorize/{robotId} robot robotAuthorize // --- @@ -169,10 +192,7 @@ func AuthorizeRobot(c *fiber.Ctx) error { cache.RemoveUnauthorizedRobotById(params.RobotId) - robot.BroadcastSSEMessage(structs.SSEMessage{ - Cmd: utils.SSESentCmdAddRobot, - Body: &newRobot, - }) + addRobotSSEMessage(&newRobot) logger.AddSystemLog("Robot authorized with id %v and type %v", params.RobotId, utils.GetRobotTypeString(unauthorizedRobot.Type)) @@ -210,8 +230,14 @@ func DeleteRobot(c *fiber.Ctx) error { cache.RemoveRobotById(params.RobotId) robot.BroadcastSSEMessage(structs.SSEMessage{ - Cmd: utils.SSESentCmdRemoveRobot, - Body: params.RobotId, + Cmd: utils.SSESentCmdRemoveRobot, + Body: struct { + RobotId string + TotalPages int + }{ + RobotId: params.RobotId, + TotalPages: cache.GetAllRobots(rspagination.PageQuery{Page: 1}).TotalPages, + }, }) logger.AddSystemLog("Robot deleted with id %v", params.RobotId) @@ -249,8 +275,14 @@ func DenyUnauthorizedRobot(c *fiber.Ctx) error { cache.RemoveUnauthorizedRobotById(params.RobotId) robot.BroadcastSSEMessage(structs.SSEMessage{ - Cmd: utils.SSESentCmdRemoveUnauthorizedRobot, - Body: params.RobotId, + Cmd: utils.SSESentCmdRemoveUnauthorizedRobot, + Body: struct { + UnauthorizedRobotId string + TotalPages int + }{ + UnauthorizedRobotId: params.RobotId, + TotalPages: cache.GetAllUnauthorizedRobots(rspagination.PageQuery{Page: 1}).TotalPages, + }, }) logger.AddSystemLog("Unauthorized robot denied with id %v", params.RobotId) diff --git a/routers/router/router.go b/routers/router/router.go index 84faa04..87b836a 100644 --- a/routers/router/router.go +++ b/routers/router/router.go @@ -32,6 +32,7 @@ func SetupRoutes(app *fiber.App) { pj := v1.Group("/permitjoin") pj.Post("/:enabled", permitjoin.SetPermitJoin) + pj.Get("/", permitjoin.GetPermitJoin) v1.Get("/sse", sse.SSE) diff --git a/testclient/testrobot.py b/testclient/testrobot.py index 4c8e943..791787d 100644 --- a/testclient/testrobot.py +++ b/testclient/testrobot.py @@ -3,12 +3,13 @@ from flask import Flask, request, jsonify import requests import time +import random robot_control_server_url = 'http://localhost:50055/v1' class RexRobot: - def __init__(self): - self.id = "B29" + def __init__(self, id): + self.id = id self.firmwareVersion = "0.0.1" self.currentJobId = "" @@ -25,7 +26,17 @@ class RexRobot: def setCurrentJobId(self, jobId): self.currentJobId = jobId -rex = RexRobot() +# generate random id +def randomId(): + return random.randint(0, 100000) + +# 10 random rexrobot + +for i in range(10): + RexRobot(str(randomId())) + + +rex = RexRobot("test") app = Flask(__name__) @@ -48,4 +59,4 @@ def ping(): return jsonify({'status': 'ok'}) if __name__ == '__main__': - app.run(debug=False, port=5000) \ No newline at end of file + app.run(debug=False, port=5003) \ No newline at end of file