diff --git a/arena.go b/arena.go index 2310916..7b7349c 100644 --- a/arena.go +++ b/arena.go @@ -89,6 +89,7 @@ type Arena struct { savedMatch *Match savedMatchResult *MatchResult leftGoalHotFirst bool + lights Lights } var mainArena Arena // Named thusly to avoid polluting the global namespace with something more generic. @@ -141,7 +142,7 @@ func (arena *Arena) Setup() { arena.allianceStationDisplays = make(map[string]string) arena.allianceStationDisplayScreen = "blank" - SetupLights() + arena.lights.Setup() } // Loads a team into an alliance station, cleaning up the previous team there if there is one. @@ -511,16 +512,16 @@ func (arena *Arena) handleLighting(alliance string, score *RealtimeScore) { switch arena.MatchState { case AUTO_PERIOD: leftSide := arena.MatchTimeSec() < float64(arena.matchTiming.AutoDurationSec)/2 == arena.leftGoalHotFirst - SetHotGoalLights(alliance, leftSide) + arena.lights.SetHotGoal(alliance, leftSide) case TELEOP_PERIOD: if score.AutoLeftoverBalls == 0 && score.CurrentCycle.Assists == 0 { - SetPedestalLight(alliance) + arena.lights.SetPedestal(alliance) } else { - ClearPedestalLight(alliance) + arena.lights.ClearPedestal(alliance) } - SetAssistGoalLights(alliance, score.CurrentCycle.Assists) + arena.lights.SetAssistGoal(alliance, score.CurrentCycle.Assists) default: - ClearGoalLights(alliance) - ClearPedestalLight(alliance) + arena.lights.ClearGoal(alliance) + arena.lights.ClearPedestal(alliance) } } diff --git a/db/migrations/20140524160241_CreateEventSettings.sql b/db/migrations/20140524160241_CreateEventSettings.sql index ebbebf0..0f8a4df 100644 --- a/db/migrations/20140524160241_CreateEventSettings.sql +++ b/db/migrations/20140524160241_CreateEventSettings.sql @@ -9,6 +9,8 @@ CREATE TABLE event_settings ( selectionround3order VARCHAR(1), teaminfodownloadenabled bool, alliancedisplayhotgoals bool, + redgoallightsaddress VARCHAR(255), + bluegoallightsaddress VARCHAR(255), tbapublishingenabled bool, tbaeventcode VARCHAR(16), tbasecretid VARCHAR(255), diff --git a/event_settings.go b/event_settings.go index 9bcb746..b31c3de 100644 --- a/event_settings.go +++ b/event_settings.go @@ -15,6 +15,8 @@ type EventSettings struct { SelectionRound3Order string TeamInfoDownloadEnabled bool AllianceDisplayHotGoals bool + RedGoalLightsAddress string + BlueGoalLightsAddress string TbaPublishingEnabled bool TbaEventCode string TbaSecretId string diff --git a/lights.go b/lights.go index f3a10a7..39791d5 100644 --- a/lights.go +++ b/lights.go @@ -6,67 +6,154 @@ package main import ( - "fmt" + "log" + "net" ) -var hotGoalLights map[string]bool -var assistLights map[string]int -var pedestalLights map[string]bool -var newMatch bool +type LightPacket [24]byte -func SetupLights() { - hotGoalLights = make(map[string]bool) - assistLights = make(map[string]int) - pedestalLights = make(map[string]bool) - newMatch = true +type Lights struct { + connections map[string]*net.Conn + packets map[string]*LightPacket + oldPackets map[string]*LightPacket + hotGoal string + newConnections bool } -func SetHotGoalLights(alliance string, leftSide bool) { - if !newMatch && hotGoalLights[alliance] == leftSide { - return +func (lightPacket *LightPacket) setColor(channel int, color string) { + switch color { + case "red": + lightPacket.setRgb(channel, 16, 0, 0) + case "blue": + lightPacket.setRgb(channel, 0, 0, 16) + case "yellow": + lightPacket.setRgb(channel, 16, 16, 0) + default: + lightPacket.setRgb(channel, 13, 12, 11) } - newMatch = false - hotGoalLights[alliance] = leftSide +} + +func (lightPacket *LightPacket) setRgb(channel int, red byte, green byte, blue byte) { + lightPacket[channel*3] = red + lightPacket[channel*3+1] = green + lightPacket[channel*3+2] = blue +} + +func (lights *Lights) Setup() error { + err := lights.SetupConnections() + if err != nil { + return err + } + + lights.packets = make(map[string]*LightPacket) + lights.packets["red"] = &LightPacket{} + lights.packets["blue"] = &LightPacket{} + lights.oldPackets = make(map[string]*LightPacket) + lights.oldPackets["red"] = &LightPacket{} + lights.oldPackets["blue"] = &LightPacket{} + + lights.sendLights() + return nil +} + +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 + } + } + if len(eventSettings.BlueGoalLightsAddress) != 0 { + conn, err := net.Dial("udp4", eventSettings.BlueGoalLightsAddress) + lights.connections["blue"] = &conn + if err != nil { + return err + } + } + lights.newConnections = true + return nil +} + +func (lights *Lights) SetHotGoal(alliance string, leftSide bool) { if leftSide { - fmt.Printf("Setting left %s goal hot\n", alliance) - mainArena.hotGoalLightNotifier.Notify("left") + lights.packets[alliance].setRgb(0, 0, 0, 0) + lights.packets[alliance].setRgb(1, 0, 0, 0) + lights.packets[alliance].setRgb(2, 0, 0, 0) + lights.packets[alliance].setColor(3, "yellow") + lights.packets[alliance].setColor(4, "yellow") + lights.packets[alliance].setColor(5, "yellow") + lights.hotGoal = "left" } else { - fmt.Printf("Setting right %s goal hot\n", alliance) - mainArena.hotGoalLightNotifier.Notify("right") + lights.packets[alliance].setColor(0, "yellow") + lights.packets[alliance].setColor(1, "yellow") + lights.packets[alliance].setColor(2, "yellow") + lights.packets[alliance].setRgb(3, 0, 0, 0) + lights.packets[alliance].setRgb(4, 0, 0, 0) + lights.packets[alliance].setRgb(5, 0, 0, 0) + lights.hotGoal = "right" } + lights.sendLights() } -func SetAssistGoalLights(alliance string, numAssists int) { - newMatch = true - mainArena.hotGoalLightNotifier.Notify("") - - if assistLights[alliance] == numAssists { - return +func (lights *Lights) SetAssistGoal(alliance string, numAssists int) { + lights.packets[alliance].setRgb(0, 0, 0, 0) + lights.packets[alliance].setRgb(1, 0, 0, 0) + lights.packets[alliance].setRgb(2, 0, 0, 0) + lights.packets[alliance].setRgb(3, 0, 0, 0) + lights.packets[alliance].setRgb(4, 0, 0, 0) + lights.packets[alliance].setRgb(5, 0, 0, 0) + if numAssists > 0 { + lights.packets[alliance].setColor(2, alliance) + lights.packets[alliance].setColor(3, alliance) } - assistLights[alliance] = numAssists - if numAssists <= 0 { - fmt.Printf("Clearing %s goal lights\n", alliance) - } else if numAssists < 3 { - fmt.Printf("Setting %s goal to %d assists\n", alliance, numAssists) + if numAssists > 1 { + lights.packets[alliance].setColor(1, alliance) + lights.packets[alliance].setColor(4, alliance) + } + if numAssists > 2 { + lights.packets[alliance].setColor(0, alliance) + lights.packets[alliance].setColor(5, alliance) + } + lights.hotGoal = "" + lights.sendLights() +} + +func (lights *Lights) ClearGoal(alliance string) { + lights.SetAssistGoal(alliance, 0) +} + +func (lights *Lights) SetPedestal(alliance string) { + if alliance == "red" { + lights.packets["blue"].setColor(6, alliance) } else { - fmt.Printf("Setting %s goal to 3 assists\n", alliance) + lights.packets["red"].setColor(6, alliance) } + lights.sendLights() } -func ClearGoalLights(alliance string) { - SetAssistGoalLights(alliance, 0) +func (lights *Lights) ClearPedestal(alliance string) { + if alliance == "red" { + lights.packets["blue"].setRgb(6, 0, 0, 0) + } else { + lights.packets["red"].setRgb(6, 0, 0, 0) + } + lights.sendLights() } -func SetPedestalLight(alliance string) { - if pedestalLights[alliance] == false { - pedestalLights[alliance] = true - fmt.Printf("Setting %s pedestal\n", alliance) - } -} - -func ClearPedestalLight(alliance string) { - if pedestalLights[alliance] == true { - pedestalLights[alliance] = false - fmt.Printf("Clearing %s pedestal\n", alliance) +func (lights *Lights) sendLights() { + for alliance, connections := range lights.connections { + if lights.newConnections || *lights.packets[alliance] != *lights.oldPackets[alliance] { + _, err := (*connections).Write(lights.packets[alliance][:]) + if err != nil { + log.Printf("Failed to send %s light packet.", alliance) + } + mainArena.hotGoalLightNotifier.Notify(lights.hotGoal) + } + *lights.oldPackets[alliance] = *lights.packets[alliance] } + lights.newConnections = false } diff --git a/setup_settings.go b/setup_settings.go index 94d1c86..cc1af7a 100644 --- a/setup_settings.go +++ b/setup_settings.go @@ -38,11 +38,14 @@ func SettingsPostHandler(w http.ResponseWriter, r *http.Request) { renderSettings(w, r, "Number of alliances must be between 2 and 16.") return } + eventSettings.NumElimAlliances = numAlliances eventSettings.SelectionRound2Order = r.PostFormValue("selectionRound2Order") eventSettings.SelectionRound3Order = r.PostFormValue("selectionRound3Order") eventSettings.TeamInfoDownloadEnabled = r.PostFormValue("teamInfoDownloadEnabled") == "on" eventSettings.AllianceDisplayHotGoals = r.PostFormValue("allianceDisplayHotGoals") == "on" + eventSettings.RedGoalLightsAddress = r.PostFormValue("redGoalLightsAddress") + eventSettings.BlueGoalLightsAddress = r.PostFormValue("blueGoalLightsAddress") eventSettings.TbaPublishingEnabled = r.PostFormValue("tbaPublishingEnabled") == "on" eventSettings.TbaEventCode = r.PostFormValue("tbaEventCode") eventSettings.TbaSecretId = r.PostFormValue("tbaSecretId") @@ -58,6 +61,14 @@ func SettingsPostHandler(w http.ResponseWriter, r *http.Request) { handleWebErr(w, err) return } + + // Set up the light controller connections again in case the address changed. + err = mainArena.lights.SetupConnections() + if err != nil { + handleWebErr(w, err) + return + } + http.Redirect(w, r, "/setup/settings", 302) } diff --git a/templates/settings.html b/templates/settings.html index 4310dfd..99c9029 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -91,12 +91,6 @@ -