Skip to content

Commit 39edbff

Browse files
committed
Rework remote control protocol
1 parent 3ae68ed commit 39edbff

File tree

7 files changed

+454
-187
lines changed

7 files changed

+454
-187
lines changed

cmd/ssl-remote-control-client/main.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,22 +78,22 @@ func main() {
7878
}
7979
commands["substitution"] = func(args []string) {
8080
c.sendRequest(&rcon.RemoteControlToController{
81-
Msg: &rcon.RemoteControlToController_SubstituteBot{SubstituteBot: true},
81+
Msg: &rcon.RemoteControlToController_RequestRobotSubstitution{RequestRobotSubstitution: true},
8282
})
8383
}
8484
commands["no_substitution"] = func(args []string) {
8585
c.sendRequest(&rcon.RemoteControlToController{
86-
Msg: &rcon.RemoteControlToController_SubstituteBot{SubstituteBot: false},
86+
Msg: &rcon.RemoteControlToController_RequestRobotSubstitution{RequestRobotSubstitution: false},
8787
})
8888
}
8989
commands["timeout"] = func(args []string) {
9090
c.sendRequest(&rcon.RemoteControlToController{
91-
Msg: &rcon.RemoteControlToController_Timeout{Timeout: true},
91+
Msg: &rcon.RemoteControlToController_RequestTimeout{RequestTimeout: true},
9292
})
9393
}
9494
commands["no_timeout"] = func(args []string) {
9595
c.sendRequest(&rcon.RemoteControlToController{
96-
Msg: &rcon.RemoteControlToController_Timeout{Timeout: false},
96+
Msg: &rcon.RemoteControlToController_RequestTimeout{RequestTimeout: false},
9797
})
9898
}
9999
commands["challenge"] = func(args []string) {
@@ -103,12 +103,12 @@ func main() {
103103
}
104104
commands["emergency"] = func(args []string) {
105105
c.sendRequest(&rcon.RemoteControlToController{
106-
Msg: &rcon.RemoteControlToController_EmergencyStop{EmergencyStop: true},
106+
Msg: &rcon.RemoteControlToController_RequestEmergencyStop{RequestEmergencyStop: true},
107107
})
108108
}
109109
commands["no_emergency"] = func(args []string) {
110110
c.sendRequest(&rcon.RemoteControlToController{
111-
Msg: &rcon.RemoteControlToController_EmergencyStop{EmergencyStop: false},
111+
Msg: &rcon.RemoteControlToController_RequestEmergencyStop{RequestEmergencyStop: false},
112112
})
113113
}
114114

internal/app/engine/engine.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type Engine struct {
3333
gcState *GcState
3434
trackerLastUpdate map[string]time.Time
3535
mutex sync.Mutex
36+
mutexEnqueueBlocking sync.Mutex
3637
noProgressDetector NoProgressDetector
3738
ballPlacementCoordinator BallPlacementCoordinator
3839
botNumberProcessor BotNumberProcessor
@@ -84,6 +85,30 @@ func (e *Engine) Enqueue(change *statemachine.Change) {
8485
e.queue <- change
8586
}
8687

88+
// EnqueueBlocking adds the change to the change queue and waits for application
89+
func (e *Engine) EnqueueBlocking(change *statemachine.Change) error {
90+
e.mutexEnqueueBlocking.Lock()
91+
defer e.mutexEnqueueBlocking.Unlock()
92+
hook := make(chan HookOut)
93+
hookId := "enqueue-blocking"
94+
e.RegisterHook(hookId, hook)
95+
defer func() {
96+
e.UnregisterHook(hookId)
97+
close(hook)
98+
}()
99+
e.Enqueue(change)
100+
for {
101+
select {
102+
case hookOut := <-hook:
103+
if hookOut.Change == change {
104+
return nil
105+
}
106+
case <-time.After(100 * time.Millisecond):
107+
return errors.Errorf("Failed to apply change: %s", change.String())
108+
}
109+
}
110+
}
111+
87112
func isNonMajorityOrigin(origins []string) bool {
88113
for _, origin := range origins {
89114
if origin == "UI" || origin == "Engine" || origin == "StateMachine" {

internal/app/rcon/server_remotecontrol.go

Lines changed: 150 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"github.com/RoboCup-SSL/ssl-game-controller/internal/app/statemachine"
99
"github.com/google/uuid"
1010
"github.com/pkg/errors"
11-
"google.golang.org/protobuf/types/known/timestamppb"
1211
"io"
1312
"log"
1413
"net"
@@ -135,23 +134,8 @@ func (s *RemoteControlServer) SendRequest(teamName string, request *ControllerTo
135134
return errors.Errorf("Remote control client '%v' not connected", teamName)
136135
}
137136

138-
func (c *RemoteControlClient) replyWithState(reply *ControllerReply) {
139-
teamState := c.gcEngine.CurrentState().TeamState[c.team.String()]
140-
emergencyStopDueIn := c.gcEngine.EmergencyStopDueIn(*c.team)
141-
var emergencyStopIn *float32
142-
if emergencyStopDueIn != nil {
143-
emergencyStopIn = new(float32)
144-
*emergencyStopIn = float32(emergencyStopDueIn.Seconds())
145-
}
137+
func (c *RemoteControlClient) reply(reply *ControllerReply) {
146138
response := &ControllerToRemoteControl{
147-
State: &RemoteControlTeamState{
148-
KeeperId: teamState.Goalkeeper,
149-
SubstituteBot: timeSet(teamState.RequestsBotSubstitutionSince),
150-
EmergencyStop: timeSet(teamState.RequestsEmergencyStopSince),
151-
EmergencyStopIn: emergencyStopIn,
152-
Timeout: timeSet(teamState.RequestsTimeoutSince),
153-
ChallengeFlag: gameEventPresent(c.gcEngine.CurrentState().GameEvents, state.GameEvent_CHALLENGE_FLAG, *c.team),
154-
},
155139
ControllerReply: reply,
156140
}
157141

@@ -160,8 +144,24 @@ func (c *RemoteControlClient) replyWithState(reply *ControllerReply) {
160144
}
161145
}
162146

163-
func (c *RemoteControlClient) reply(reply *ControllerReply) {
147+
func (c *RemoteControlClient) replyWithState(reply *ControllerReply) {
148+
teamState := c.gcEngine.CurrentState().TeamState[c.team.String()]
149+
emergencyStopIn := c.findEmergencyStopDueIn()
150+
yellowCardsDue := c.findYellowCardDueTimes()
151+
availableRequests := c.findAvailableRequestTypes()
152+
activeRequests := c.findActiveRequestTypes()
153+
164154
response := &ControllerToRemoteControl{
155+
State: &RemoteControlTeamState{
156+
KeeperId: teamState.Goalkeeper,
157+
AvailableRequests: availableRequests,
158+
ActiveRequests: activeRequests,
159+
EmergencyStopIn: &emergencyStopIn,
160+
TimeoutsLeft: teamState.TimeoutsLeft,
161+
ChallengeFlagsLeft: teamState.ChallengeFlags,
162+
MaxRobots: teamState.MaxAllowedBots,
163+
YellowCardsDue: yellowCardsDue,
164+
},
165165
ControllerReply: reply,
166166
}
167167

@@ -170,62 +170,159 @@ func (c *RemoteControlClient) reply(reply *ControllerReply) {
170170
}
171171
}
172172

173-
func timeSet(t *timestamppb.Timestamp) (set *bool) {
174-
set = new(bool)
175-
*set = t != nil
176-
return
173+
func (c *RemoteControlClient) findEmergencyStopDueIn() float32 {
174+
emergencyStopDueIn := c.gcEngine.EmergencyStopDueIn(*c.team)
175+
var emergencyStopIn float32
176+
if emergencyStopDueIn != nil {
177+
emergencyStopIn = float32(emergencyStopDueIn.Seconds())
178+
}
179+
return emergencyStopIn
177180
}
178181

179-
func gameEventPresent(events []*state.GameEvent, eventType state.GameEvent_Type, team state.Team) (present *bool) {
180-
present = new(bool)
181-
for _, event := range events {
182-
if *event.Type == eventType && event.ByTeam() == team {
183-
*present = true
182+
func (c *RemoteControlClient) findYellowCardDueTimes() []float32 {
183+
teamState := c.gcEngine.CurrentState().TeamState[c.team.String()]
184+
var yellowCardsDue []float32
185+
for _, yc := range teamState.YellowCards {
186+
if yc.TimeRemaining != nil && yc.TimeRemaining.AsDuration() > 0 {
187+
yellowCardsDue = append(yellowCardsDue, float32(yc.TimeRemaining.AsDuration().Seconds()))
184188
}
185189
}
186-
return
190+
return yellowCardsDue
191+
}
192+
193+
func (c *RemoteControlClient) findActiveRequestTypes() []RemoteControlRequestType {
194+
currentState := c.gcEngine.CurrentState()
195+
teamState := currentState.TeamState[c.team.String()]
196+
197+
var activeRequests []RemoteControlRequestType
198+
if teamState.RequestsBotSubstitutionSince != nil {
199+
activeRequests = append(activeRequests, RemoteControlRequestType_ROBOT_SUBSTITUTION)
200+
}
201+
if teamState.RequestsTimeoutSince != nil {
202+
activeRequests = append(activeRequests, RemoteControlRequestType_TIMEOUT)
203+
}
204+
if currentState.HasGameEventByTeam(state.GameEvent_CHALLENGE_FLAG, *c.team) {
205+
activeRequests = append(activeRequests, RemoteControlRequestType_CHALLENGE_FLAG)
206+
}
207+
if teamState.RequestsEmergencyStopSince != nil {
208+
activeRequests = append(activeRequests, RemoteControlRequestType_EMERGENCY_STOP)
209+
}
210+
return activeRequests
211+
}
212+
213+
func (c *RemoteControlClient) findAvailableRequestTypes() []RemoteControlRequestType {
214+
var availableRequests []RemoteControlRequestType
215+
if c.checkRequestEmergencyStop() == nil {
216+
availableRequests = append(availableRequests, RemoteControlRequestType_EMERGENCY_STOP)
217+
}
218+
if c.checkRequestTimeout() == nil {
219+
availableRequests = append(availableRequests, RemoteControlRequestType_TIMEOUT)
220+
}
221+
if c.checkRequestRobotSubstitution() == nil {
222+
availableRequests = append(availableRequests, RemoteControlRequestType_ROBOT_SUBSTITUTION)
223+
}
224+
if c.checkRequestChallengeFlag() == nil {
225+
availableRequests = append(availableRequests, RemoteControlRequestType_CHALLENGE_FLAG)
226+
}
227+
if c.checkChangeKeeper() == nil {
228+
availableRequests = append(availableRequests, RemoteControlRequestType_CHANGE_KEEPER_ID)
229+
}
230+
return availableRequests
231+
}
232+
233+
func (c *RemoteControlClient) checkRequestEmergencyStop() error {
234+
gameStateType := *c.gcEngine.CurrentState().GameState.Type
235+
switch gameStateType {
236+
case state.GameState_RUNNING,
237+
state.GameState_KICKOFF,
238+
state.GameState_PENALTY,
239+
state.GameState_FREE_KICK:
240+
return nil
241+
}
242+
return errors.Errorf("Game state is invalid: %s", gameStateType)
243+
}
244+
245+
func (c *RemoteControlClient) checkRequestTimeout() error {
246+
gameStateType := *c.gcEngine.CurrentState().GameState.Type
247+
if gameStateType == state.GameState_TIMEOUT {
248+
return errors.New("Timeout is active")
249+
}
250+
return nil
251+
}
252+
253+
func (c *RemoteControlClient) checkRequestRobotSubstitution() error {
254+
gameStateType := *c.gcEngine.CurrentState().GameState.Type
255+
if gameStateType == state.GameState_HALT {
256+
return errors.New("Game is halted")
257+
}
258+
return nil
259+
}
260+
261+
func (c *RemoteControlClient) checkRequestChallengeFlag() error {
262+
currentState := c.gcEngine.CurrentState()
263+
teamState := currentState.TeamState[c.team.String()]
264+
if currentState.HasGameEventByTeam(state.GameEvent_CHALLENGE_FLAG, *c.team) {
265+
return errors.New("Challenge flag already requested")
266+
}
267+
if *teamState.ChallengeFlags <= 0 {
268+
return errors.New("No more challenge flags left")
269+
} else if *teamState.TimeoutsLeft <= 0 {
270+
return errors.New("No more timeouts left")
271+
}
272+
return nil
273+
}
274+
275+
func (c *RemoteControlClient) checkChangeKeeper() error {
276+
currentState := c.gcEngine.CurrentState()
277+
teamState := currentState.TeamState[c.team.String()]
278+
return mayChangeKeeper(c.gcEngine.CurrentGcState(), teamState)
187279
}
188280

189281
func (c *RemoteControlClient) processRequest(request *RemoteControlToController) error {
190282

191283
currentState := c.gcEngine.CurrentState()
192-
teamState := *currentState.TeamInfo(*c.team)
284+
teamState := currentState.TeamInfo(*c.team)
193285

194286
if x, ok := request.GetMsg().(*RemoteControlToController_DesiredKeeper); ok && *teamState.Goalkeeper != x.DesiredKeeper {
195-
if err := mayChangeKeeper(c.gcEngine.CurrentGcState(), &teamState); err != nil {
196-
return errors.Wrap(err, "Remote control requests to change keeper, but: ")
287+
if err := mayChangeKeeper(c.gcEngine.CurrentGcState(), teamState); err != nil {
288+
return errors.Wrap(err, "Can not change keeper id")
197289
}
198290
c.updateTeamConfig(&statemachine.UpdateTeamState{
199291
Goalkeeper: &x.DesiredKeeper,
200292
})
201293
}
202294

203-
if x, ok := request.GetMsg().(*RemoteControlToController_SubstituteBot); ok {
204-
if *timeSet(teamState.RequestsBotSubstitutionSince) != x.SubstituteBot {
295+
if x, ok := request.GetMsg().(*RemoteControlToController_RequestRobotSubstitution); ok {
296+
robotSubstitutionRequested := teamState.RequestsBotSubstitutionSince != nil
297+
if robotSubstitutionRequested != x.RequestRobotSubstitution {
298+
if err := c.checkRequestRobotSubstitution(); err != nil {
299+
return errors.Wrap(err, "Can not request robot substitution")
300+
}
205301
c.updateTeamConfig(&statemachine.UpdateTeamState{
206-
RequestsBotSubstitution: &x.SubstituteBot,
302+
RequestsBotSubstitution: &x.RequestRobotSubstitution,
207303
})
208304
}
209305
return nil
210306
}
211307

212-
if x, ok := request.GetMsg().(*RemoteControlToController_Timeout); ok {
213-
if (*timeSet(teamState.RequestsTimeoutSince)) != x.Timeout {
308+
if x, ok := request.GetMsg().(*RemoteControlToController_RequestTimeout); ok {
309+
timeoutRequested := teamState.RequestsTimeoutSince != nil
310+
if timeoutRequested != x.RequestTimeout {
311+
if err := c.checkRequestTimeout(); err != nil {
312+
return errors.Wrap(err, "Can not request timeout")
313+
}
214314
c.updateTeamConfig(&statemachine.UpdateTeamState{
215-
RequestsTimeout: &x.Timeout,
315+
RequestsTimeout: &x.RequestTimeout,
216316
})
217317
}
218318
return nil
219319
}
220320

221321
if request.GetRequest() == RemoteControlToController_CHALLENGE_FLAG {
222-
if *teamState.ChallengeFlags <= 0 {
223-
return errors.New("No more challenge flags left")
224-
} else if *teamState.TimeoutsLeft <= 0 {
225-
return errors.New("No more timeouts left")
322+
if err := c.checkRequestChallengeFlag(); err != nil {
323+
return errors.Wrap(err, "Can not request challenge flag")
226324
}
227325
eventType := state.GameEvent_CHALLENGE_FLAG
228-
log.Println("Request challenge flag via remote control")
229326
c.gcEngine.EnqueueGameEvent(&state.GameEvent{
230327
Type: &eventType,
231328
Origin: []string{*origin(c.team)},
@@ -238,13 +335,14 @@ func (c *RemoteControlClient) processRequest(request *RemoteControlToController)
238335
return nil
239336
}
240337

241-
if x, ok := request.GetMsg().(*RemoteControlToController_EmergencyStop); ok {
242-
if *currentState.GameState.Type != state.GameState_RUNNING {
243-
return errors.New("Game is not running, can not request emergency stop")
244-
}
245-
if *timeSet(teamState.RequestsEmergencyStopSince) != x.EmergencyStop {
338+
if x, ok := request.GetMsg().(*RemoteControlToController_RequestEmergencyStop); ok {
339+
emergencyStopRequested := teamState.RequestsEmergencyStopSince != nil
340+
if emergencyStopRequested != x.RequestEmergencyStop {
341+
if err := c.checkRequestEmergencyStop(); err != nil {
342+
return errors.Wrap(err, "Can not request emergency stop")
343+
}
246344
c.updateTeamConfig(&statemachine.UpdateTeamState{
247-
RequestsEmergencyStop: &x.EmergencyStop,
345+
RequestsEmergencyStop: &x.RequestEmergencyStop,
248346
})
249347
}
250348
return nil
@@ -256,12 +354,15 @@ func (c *RemoteControlClient) processRequest(request *RemoteControlToController)
256354
func (c *RemoteControlClient) updateTeamConfig(update *statemachine.UpdateTeamState) {
257355
log.Println("Update team config via remote control: ", update.String())
258356
update.ForTeam = c.team
259-
c.gcEngine.Enqueue(&statemachine.Change{
357+
err := c.gcEngine.EnqueueBlocking(&statemachine.Change{
260358
Origin: origin(c.team),
261359
Change: &statemachine.Change_UpdateTeamState{
262360
UpdateTeamState: update,
263361
},
264362
})
363+
if err != nil {
364+
log.Println("Failed to update team state: ", err)
365+
}
265366
}
266367

267368
func origin(team *state.Team) *string {

internal/app/rcon/server_team.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ func (s *TeamServer) processRequest(teamClient TeamClient, request *TeamToContro
201201
teamState := *currentState.TeamInfo(teamClient.team)
202202

203203
if x, ok := request.GetMsg().(*TeamToController_SubstituteBot); ok {
204-
if *timeSet(teamState.RequestsBotSubstitutionSince) != x.SubstituteBot {
204+
robotSubstitutionRequested := teamState.RequestsBotSubstitutionSince != nil
205+
if robotSubstitutionRequested != x.SubstituteBot {
205206
log.Printf("Team %v requests to change bot substituation intent to %v", teamClient.id, x.SubstituteBot)
206207
s.gcEngine.Enqueue(&statemachine.Change{
207208
Origin: &teamClient.id,
@@ -240,11 +241,11 @@ func (s *TeamServer) processRequest(teamClient TeamClient, request *TeamToContro
240241
func mayChangeKeeper(gcState *engine.GcState, teamState *state.TeamInfo) error {
241242
ball := gcState.TrackerStateGc.Ball
242243
if ball == nil {
243-
return errors.New("GC does not know the ball position.")
244+
return errors.New("GC does not know the ball position")
244245
}
245246
if (*teamState.OnPositiveHalf && *ball.Pos.X > 0) ||
246247
(!*teamState.OnPositiveHalf && *ball.Pos.X < 0) {
247-
return errors.New("Ball is not in the opponents half.")
248+
return errors.New("Ball is not in the opponents half")
248249
}
249250
return nil
250251
}

0 commit comments

Comments
 (0)