Add goal and defense LED control.

This commit is contained in:
Patrick Fairbank
2016-08-14 02:37:10 -07:00
parent b323ccd3a1
commit 93bb2f580d
4 changed files with 196 additions and 77 deletions

View File

@@ -8,7 +8,6 @@ package main
import (
"fmt"
"log"
"math/rand"
"time"
)
@@ -83,9 +82,15 @@ type Arena struct {
lastMatchTimeSec float64
savedMatch *Match
savedMatchResult *MatchResult
leftGoalHotFirst bool
lights Lights
muteMatchSounds bool
fieldReset bool
}
type RealtimeScoreFields struct {
Score int
TowerStrength int
DefensesStrength [5]int
}
var mainArena Arena // Named thusly to avoid polluting the global namespace with something more generic.
@@ -126,6 +131,8 @@ func (arena *Arena) Setup() {
arena.reloadDisplaysNotifier = NewNotifier()
arena.defenseSelectionNotifier = NewNotifier()
arena.lights.Setup()
// Load empty match as current.
arena.MatchState = PRE_MATCH
arena.LoadTestMatch()
@@ -138,8 +145,6 @@ func (arena *Arena) Setup() {
arena.savedMatchResult = &MatchResult{}
arena.allianceStationDisplays = make(map[string]string)
arena.allianceStationDisplayScreen = "match"
arena.lights.Setup()
}
// Loads a team into an alliance station, cleaning up the previous team there if there is one.
@@ -222,6 +227,8 @@ func (arena *Arena) LoadMatch(match *Match) error {
// Reset the realtime scores.
arena.redRealtimeScore = NewRealtimeScore()
arena.blueRealtimeScore = NewRealtimeScore()
arena.fieldReset = false
arena.lights.ClearAll()
// Notify any listeners about the new match.
arena.matchLoadTeamsNotifier.Notify(nil)
@@ -410,7 +417,6 @@ func (arena *Arena) Update() {
arena.MatchState = AUTO_PERIOD
arena.matchStartTime = time.Now()
arena.lastMatchTimeSec = -1
arena.leftGoalHotFirst = rand.Intn(2) == 1
auto = true
enabled = true
sendDsPacket = true
@@ -494,6 +500,8 @@ func (arena *Arena) Update() {
arena.sendDsPacket(auto, enabled)
arena.robotStatusNotifier.Notify(nil)
}
arena.handleLighting()
}
// Loops indefinitely to track and update the arena components.
@@ -522,3 +530,37 @@ func (arena *Arena) sendDsPacket(auto bool, enabled bool) {
func (realtimeScore *RealtimeScore) Score(opponentFouls []Foul) int {
return scoreSummary(&realtimeScore.CurrentScore, opponentFouls, mainArena.currentMatch.Type).Score
}
// Calculates the integer score, tower strength, and defenses strength for the given realtime snapshot.
func (realtimeScore *RealtimeScore) ScoreFields(opponentFouls []Foul) *RealtimeScoreFields {
scoreSummary := scoreSummary(&realtimeScore.CurrentScore, opponentFouls, mainArena.currentMatch.Type)
var defensesStrength [5]int
for i := 0; i < 5; i++ {
defensesStrength[i] = 2 - realtimeScore.CurrentScore.AutoDefensesCrossed[i] -
realtimeScore.CurrentScore.DefensesCrossed[i]
}
return &RealtimeScoreFields{scoreSummary.Score, scoreSummary.TowerStrength, defensesStrength}
}
// Manipulates the arena LED lighting based on the current state of the match.
func (arena *Arena) handleLighting() {
switch arena.MatchState {
case AUTO_PERIOD:
fallthrough
case PAUSE_PERIOD:
fallthrough
case TELEOP_PERIOD:
fallthrough
case ENDGAME_PERIOD:
redScoreFields := arena.redRealtimeScore.ScoreFields(arena.blueRealtimeScore.CurrentScore.Fouls)
blueScoreFields := arena.blueRealtimeScore.ScoreFields(arena.redRealtimeScore.CurrentScore.Fouls)
arena.lights.SetGoals(redScoreFields.TowerStrength, blueScoreFields.TowerStrength)
arena.lights.SetDefenses(redScoreFields.DefensesStrength, blueScoreFields.DefensesStrength)
case POST_MATCH:
if mainArena.fieldReset {
arena.lights.SetFieldReset()
} else {
arena.lights.ClearAll()
}
}
}

View File

@@ -12,12 +12,6 @@ import (
"text/template"
)
type RealtimeScoreFields struct {
Score int
TowerStrength int
DefensesStrength [5]int
}
// Renders the audience display to be chroma keyed over the video feed.
func AudienceDisplayHandler(w http.ResponseWriter, r *http.Request) {
if !UserIsReader(w, r) {
@@ -222,14 +216,3 @@ func AudienceDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
}
}
}
// Calculates the integer score, tower strength, and defenses strength for the given realtime snapshot.
func (realtimeScore *RealtimeScore) ScoreFields(opponentFouls []Foul) *RealtimeScoreFields {
scoreSummary := scoreSummary(&realtimeScore.CurrentScore, opponentFouls, mainArena.currentMatch.Type)
var defensesStrength [5]int
for i := 0; i < 5; i++ {
defensesStrength[i] = 2 - realtimeScore.CurrentScore.AutoDefensesCrossed[i] -
realtimeScore.CurrentScore.DefensesCrossed[i]
}
return &RealtimeScoreFields{scoreSummary.Score, scoreSummary.TowerStrength, defensesStrength}
}

202
lights.go
View File

@@ -11,6 +11,13 @@ import (
"time"
)
const (
RED_GOAL = "redGoal"
RED_DEFENSE = "redDefense"
BLUE_GOAL = "blueGoal"
BLUE_DEFENSE = "blueDefense"
)
type LightPacket [32]byte
type Lights struct {
@@ -78,11 +85,15 @@ func (lights *Lights) Setup() error {
}
lights.packets = make(map[string]*LightPacket)
lights.packets["red"] = &LightPacket{}
lights.packets["blue"] = &LightPacket{}
lights.packets[RED_GOAL] = &LightPacket{}
lights.packets[RED_DEFENSE] = &LightPacket{}
lights.packets[BLUE_GOAL] = &LightPacket{}
lights.packets[BLUE_DEFENSE] = &LightPacket{}
lights.oldPackets = make(map[string]*LightPacket)
lights.oldPackets["red"] = &LightPacket{}
lights.oldPackets["blue"] = &LightPacket{}
lights.oldPackets[RED_GOAL] = &LightPacket{}
lights.oldPackets[RED_DEFENSE] = &LightPacket{}
lights.oldPackets[BLUE_GOAL] = &LightPacket{}
lights.oldPackets[BLUE_DEFENSE] = &LightPacket{}
lights.sendLights()
@@ -98,34 +109,50 @@ func (lights *Lights) Setup() error {
func (lights *Lights) SetupConnections() error {
lights.connections = make(map[string]*net.Conn)
// Don't enable lights for a side if the controller address is not configured.
if len(eventSettings.RedGoalLightsAddress) != 0 {
conn, err := net.Dial("udp4", eventSettings.RedGoalLightsAddress)
lights.connections["red"] = &conn
if err != nil {
return err
}
} else {
lights.connections["red"] = nil
if err := lights.connect(RED_GOAL, eventSettings.RedGoalLightsAddress); err != nil {
return err
}
if len(eventSettings.BlueGoalLightsAddress) != 0 {
conn, err := net.Dial("udp4", eventSettings.BlueGoalLightsAddress)
lights.connections["blue"] = &conn
if err != nil {
return err
}
} else {
lights.connections["blue"] = nil
if err := lights.connect(RED_DEFENSE, eventSettings.RedDefenseLightsAddress); err != nil {
return err
}
if err := lights.connect(BLUE_GOAL, eventSettings.BlueGoalLightsAddress); err != nil {
return err
}
if err := lights.connect(BLUE_DEFENSE, eventSettings.BlueDefenseLightsAddress); err != nil {
return err
}
lights.newConnections = true
return nil
}
func (lights *Lights) connect(controller, address string) error {
// Don't enable lights for a side if the controller address is not configured.
if len(address) != 0 {
conn, err := net.Dial("udp4", address)
lights.connections[controller] = &conn
if err != nil {
return err
}
} else {
lights.connections[controller] = nil
}
return nil
}
func (lights *Lights) ClearAll() {
lights.packets[RED_GOAL].setAllColorFade("off", 10)
lights.packets[RED_DEFENSE].setAllColorFade("off", 10)
lights.packets[BLUE_GOAL].setAllColorFade("off", 10)
lights.packets[BLUE_DEFENSE].setAllColorFade("off", 10)
lights.sendLights()
}
// Turns all lights green to signal that the field is safe to enter.
func (lights *Lights) SetFieldReset() {
lights.packets["red"].setAllColor("green")
lights.packets["blue"].setAllColor("green")
lights.packets[RED_GOAL].setAllColor("green")
lights.packets[RED_DEFENSE].setAllColor("green")
lights.packets[BLUE_GOAL].setAllColor("green")
lights.packets[BLUE_DEFENSE].setAllColor("green")
lights.sendLights()
}
@@ -136,36 +163,46 @@ func (lights *Lights) SetMode(mode string) {
switch mode {
case "off":
lights.packets["red"].setAllColor("off")
lights.packets["blue"].setAllColor("off")
lights.packets[RED_GOAL].setAllColor("off")
lights.packets[RED_DEFENSE].setAllColor("off")
lights.packets[BLUE_GOAL].setAllColor("off")
lights.packets[BLUE_DEFENSE].setAllColor("off")
case "all_white":
lights.packets["red"].setAllColor("white")
lights.packets["blue"].setAllColor("white")
lights.packets[RED_GOAL].setAllColor("white")
lights.packets[RED_DEFENSE].setAllColor("white")
lights.packets[BLUE_GOAL].setAllColor("white")
lights.packets[BLUE_DEFENSE].setAllColor("white")
case "all_red":
lights.packets["red"].setAllColor("red")
lights.packets["blue"].setAllColor("red")
lights.packets[RED_GOAL].setAllColor("red")
lights.packets[RED_DEFENSE].setAllColor("red")
lights.packets[BLUE_GOAL].setAllColor("red")
lights.packets[BLUE_DEFENSE].setAllColor("red")
case "all_green":
lights.packets["red"].setAllColor("green")
lights.packets["blue"].setAllColor("green")
lights.packets[RED_GOAL].setAllColor("green")
lights.packets[RED_DEFENSE].setAllColor("green")
lights.packets[BLUE_GOAL].setAllColor("green")
lights.packets[BLUE_DEFENSE].setAllColor("green")
case "all_blue":
lights.packets["red"].setAllColor("blue")
lights.packets["blue"].setAllColor("blue")
lights.packets[RED_GOAL].setAllColor("blue")
lights.packets[RED_DEFENSE].setAllColor("blue")
lights.packets[BLUE_GOAL].setAllColor("blue")
lights.packets[BLUE_DEFENSE].setAllColor("blue")
}
lights.sendLights()
}
// Sends a control packet to the LED controllers only if their state needs to be updated.
func (lights *Lights) sendLights() {
for alliance, connection := range lights.connections {
if lights.newConnections || *lights.packets[alliance] != *lights.oldPackets[alliance] {
for controller, connection := range lights.connections {
if lights.newConnections || *lights.packets[controller] != *lights.oldPackets[controller] {
if connection != nil {
_, err := (*connection).Write(lights.packets[alliance][:])
_, err := (*connection).Write(lights.packets[controller][:])
if err != nil {
log.Printf("Failed to send %s light packet.", alliance)
log.Printf("Failed to send %s light packet.", controller)
}
}
}
*lights.oldPackets[alliance] = *lights.packets[alliance]
*lights.oldPackets[controller] = *lights.packets[controller]
}
lights.newConnections = false
}
@@ -178,11 +215,15 @@ func (lights *Lights) animate() {
case "strobe":
switch lights.animationCount {
case 1:
lights.packets["red"].setAllColor("white")
lights.packets["blue"].setAllColor("off")
lights.packets[RED_GOAL].setAllColor("white")
lights.packets[RED_DEFENSE].setAllColor("white")
lights.packets[BLUE_GOAL].setAllColor("off")
lights.packets[BLUE_DEFENSE].setAllColor("off")
case 2:
lights.packets["red"].setAllColor("off")
lights.packets["blue"].setAllColor("white")
lights.packets[RED_GOAL].setAllColor("off")
lights.packets[RED_DEFENSE].setAllColor("off")
lights.packets[BLUE_GOAL].setAllColor("white")
lights.packets[BLUE_DEFENSE].setAllColor("white")
fallthrough
default:
lights.animationCount = 0
@@ -190,36 +231,87 @@ func (lights *Lights) animate() {
lights.sendLights()
case "fade_red":
if lights.animationCount == 1 {
lights.packets["red"].setAllColorFade("red", 18)
lights.packets["blue"].setAllColorFade("red", 18)
lights.packets[RED_GOAL].setAllColorFade("red", 18)
lights.packets[RED_DEFENSE].setAllColorFade("red", 18)
lights.packets[BLUE_GOAL].setAllColorFade("red", 18)
lights.packets[BLUE_DEFENSE].setAllColorFade("red", 18)
} else if lights.animationCount == 61 {
lights.packets["red"].setAllColorFade("darkred", 18)
lights.packets["blue"].setAllColorFade("darkred", 18)
lights.packets[RED_GOAL].setAllColorFade("darkred", 18)
lights.packets[RED_DEFENSE].setAllColorFade("darkred", 18)
lights.packets[BLUE_GOAL].setAllColorFade("darkred", 18)
lights.packets[BLUE_DEFENSE].setAllColorFade("darkred", 18)
} else if lights.animationCount > 120 {
lights.animationCount = 0
}
lights.sendLights()
case "fade_blue":
if lights.animationCount == 1 {
lights.packets["red"].setAllColorFade("blue", 18)
lights.packets["blue"].setAllColorFade("blue", 18)
lights.packets[RED_GOAL].setAllColorFade("blue", 18)
lights.packets[RED_DEFENSE].setAllColorFade("blue", 18)
lights.packets[BLUE_GOAL].setAllColorFade("blue", 18)
lights.packets[BLUE_DEFENSE].setAllColorFade("blue", 18)
} else if lights.animationCount == 61 {
lights.packets["red"].setAllColorFade("darkblue", 18)
lights.packets["blue"].setAllColorFade("darkblue", 18)
lights.packets[RED_GOAL].setAllColorFade("darkblue", 18)
lights.packets[RED_DEFENSE].setAllColorFade("darkblue", 18)
lights.packets[BLUE_GOAL].setAllColorFade("darkblue", 18)
lights.packets[BLUE_DEFENSE].setAllColorFade("darkblue", 18)
} else if lights.animationCount > 120 {
lights.animationCount = 0
}
lights.sendLights()
case "fade_red_blue":
if lights.animationCount == 1 {
lights.packets["red"].setAllColorFade("blue", 18)
lights.packets["blue"].setAllColorFade("darkred", 18)
lights.packets[RED_GOAL].setAllColorFade("blue", 18)
lights.packets[RED_DEFENSE].setAllColorFade("blue", 18)
lights.packets[BLUE_GOAL].setAllColorFade("darkred", 18)
lights.packets[BLUE_DEFENSE].setAllColorFade("darkred", 18)
} else if lights.animationCount == 61 {
lights.packets["red"].setAllColorFade("darkblue", 18)
lights.packets["blue"].setAllColorFade("red", 18)
lights.packets[RED_GOAL].setAllColorFade("darkblue", 18)
lights.packets[RED_DEFENSE].setAllColorFade("darkblue", 18)
lights.packets[BLUE_GOAL].setAllColorFade("red", 18)
lights.packets[BLUE_DEFENSE].setAllColorFade("red", 18)
} else if lights.animationCount > 120 {
lights.animationCount = 0
}
lights.sendLights()
}
}
// Turns on a number of channels corresponding to the tower strength (all on for 8 or higher).
func (lights *Lights) SetGoals(redTowerStrength, blueTowerStrength int) {
for i := 0; i < 8; i++ {
if redTowerStrength > i {
lights.packets[RED_GOAL].setColorFade(i, "red", 10)
} else {
lights.packets[RED_GOAL].setColorFade(i, "off", 10)
}
if blueTowerStrength > i {
lights.packets[BLUE_GOAL].setColorFade(i, "blue", 10)
} else {
lights.packets[BLUE_GOAL].setColorFade(i, "off", 10)
}
}
lights.sendLights()
}
// Turns on the lights below the defenses, with one channel per defense.
func (lights *Lights) SetDefenses(redDefensesStrength, blueDefensesStrength [5]int) {
for i := 0; i < 5; i++ {
if redDefensesStrength[i] == 0 {
lights.packets[RED_DEFENSE].setColorFade(i, "off", 10)
} else if redDefensesStrength[i] == 1 {
lights.packets[RED_DEFENSE].setColorFade(i, "yellow", 10)
} else {
lights.packets[RED_DEFENSE].setColorFade(i, "red", 10)
}
if blueDefensesStrength[i] == 0 {
lights.packets[BLUE_DEFENSE].setColorFade(i, "off", 10)
} else if blueDefensesStrength[i] == 1 {
lights.packets[BLUE_DEFENSE].setColorFade(i, "yellow", 10)
} else {
lights.packets[BLUE_DEFENSE].setColorFade(i, "blue", 10)
}
}
lights.sendLights()
}

View File

@@ -229,6 +229,7 @@ func RefereeDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
// Don't allow clearing the field until the match is over.
continue
}
mainArena.fieldReset = true
mainArena.allianceStationDisplayScreen = "fieldReset"
mainArena.allianceStationDisplayNotifier.Notify(nil)
continue // Don't reload.
@@ -239,6 +240,7 @@ func RefereeDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
}
mainArena.redRealtimeScore.FoulsCommitted = true
mainArena.blueRealtimeScore.FoulsCommitted = true
mainArena.fieldReset = true
mainArena.allianceStationDisplayScreen = "fieldReset"
mainArena.allianceStationDisplayNotifier.Notify(nil)
mainArena.scoringStatusNotifier.Notify(nil)