From 2882a16b29bfcfe95aaefbb849beb4e60352ae06 Mon Sep 17 00:00:00 2001 From: Corey Applegate Date: Sat, 30 Nov 2024 14:20:17 -0600 Subject: [PATCH 1/2] Add Setting --- model/event_settings.go | 2 ++ model/event_settings_test.go | 1 + templates/setup_settings.html | 9 +++++++++ web/setup_settings.go | 1 + 4 files changed, 13 insertions(+) diff --git a/model/event_settings.go b/model/event_settings.go index 5e53459a..44805a4b 100644 --- a/model/event_settings.go +++ b/model/event_settings.go @@ -23,6 +23,7 @@ type EventSettings struct { SelectionRound3Order string SelectionShowUnpickedTeams bool TbaDownloadEnabled bool + AlternateIOEnabled bool TbaPublishingEnabled bool TbaEventCode string TbaSecretId string @@ -74,6 +75,7 @@ func (database *Database) GetEventSettings() (*EventSettings, error) { SelectionRound3Order: "", SelectionShowUnpickedTeams: true, TbaDownloadEnabled: true, + AlternateIOEnabled: false, ApChannel: 36, WarmupDurationSec: game.MatchTiming.WarmupDurationSec, AutoDurationSec: game.MatchTiming.AutoDurationSec, diff --git a/model/event_settings_test.go b/model/event_settings_test.go index 059704a7..bf249678 100644 --- a/model/event_settings_test.go +++ b/model/event_settings_test.go @@ -25,6 +25,7 @@ func TestEventSettingsReadWrite(t *testing.T) { SelectionRound3Order: "", SelectionShowUnpickedTeams: true, TbaDownloadEnabled: true, + AlternateIOEnabled: true, ApChannel: 36, WarmupDurationSec: 0, AutoDurationSec: 15, diff --git a/templates/setup_settings.html b/templates/setup_settings.html index 255ec850..b05f20a2 100644 --- a/templates/setup_settings.html +++ b/templates/setup_settings.html @@ -234,6 +234,15 @@ +
+ +
+ +
+
Team Signs diff --git a/web/setup_settings.go b/web/setup_settings.go index 8b55c8c8..d1ebe163 100644 --- a/web/setup_settings.go +++ b/web/setup_settings.go @@ -85,6 +85,7 @@ func (web *Web) settingsPostHandler(w http.ResponseWriter, r *http.Request) { eventSettings.SelectionRound3Order = r.PostFormValue("selectionRound3Order") eventSettings.SelectionShowUnpickedTeams = r.PostFormValue("selectionShowUnpickedTeams") == "on" eventSettings.TbaDownloadEnabled = r.PostFormValue("tbaDownloadEnabled") == "on" + eventSettings.AlternateIOEnabled = r.PostFormValue("alternateIOEnabled") == "on" eventSettings.TbaPublishingEnabled = r.PostFormValue("tbaPublishingEnabled") == "on" eventSettings.TbaEventCode = r.PostFormValue("tbaEventCode") eventSettings.TbaSecretId = r.PostFormValue("tbaSecretId") From 2a37fd03a08947b29ebe016b4bbe876d6cd3fc46 Mon Sep 17 00:00:00 2001 From: Corey Applegate Date: Sat, 30 Nov 2024 22:57:39 -0600 Subject: [PATCH 2/2] Station Stops Installed --- field/arena.go | 16 +++------ plc/plc.go | 23 ++++++++++++ static/css/scoring_panel.css | 70 ++++++++++++++++++++++++++++++++++++ static/js/scoring_panel.js | 17 +++++++++ templates/scoring_panel.html | 56 ++++++++++++++++++++++------- web/match_play.go | 2 ++ web/scoring_panel.go | 60 +++++++++++++++++++++++++++++++ 7 files changed, 221 insertions(+), 23 deletions(-) diff --git a/field/arena.go b/field/arena.go index 3176089e..42d8c5ac 100644 --- a/field/arena.go +++ b/field/arena.go @@ -924,18 +924,12 @@ var blueAmplifiedTimeRemaining_ons = false // Updates the score given new input information from the field PLC, and actuates PLC outputs accordingly. func (arena *Arena) handlePlcInputOutput() { if !arena.Plc.IsEnabled() { - // Declare and initialize arrays - redEStops, blueEStops := [3]bool{}, [3]bool{} - redAStops, blueAStops := [3]bool{}, [3]bool{} - // Fill arrays with false values - for i := range redEStops { - redEStops[i] = false - redAStops[i] = false - } - for i := range blueEStops { - blueEStops[i] = false - blueAStops[i] = false + // Handle PLC functions that are always active. + if arena.Plc.GetFieldEStop() && !arena.matchAborted { + arena.AbortMatch() } + redEStops, blueEStops := arena.Plc.GetTeamEStops() + redAStops, blueAStops := arena.Plc.GetTeamAStops() arena.handleTeamStop("R1", redEStops[0], redAStops[0]) arena.handleTeamStop("R2", redEStops[1], redAStops[1]) arena.handleTeamStop("R3", redEStops[2], redAStops[2]) diff --git a/plc/plc.go b/plc/plc.go index 30aaf80b..337cb98d 100644 --- a/plc/plc.go +++ b/plc/plc.go @@ -18,6 +18,8 @@ type Plc interface { SetAddress(address string) IsEnabled() bool IsHealthy() bool + SetAlternateIOStopState(input int, state bool) + ResetEstops() IoChangeNotifier() *websocket.Notifier Run() GetArmorBlockStatuses() map[string]bool @@ -164,6 +166,25 @@ func (plc *ModbusPlc) SetAddress(address string) { } } +func (plc *ModbusPlc) ResetEstops(){ + plc.inputs[red1EStop] = true + plc.inputs[red2EStop] = true + plc.inputs[red3EStop] = true + plc.inputs[blue1EStop] = true + plc.inputs[blue2EStop] = true + plc.inputs[blue3EStop] = true + plc.inputs[red1AStop] = true + plc.inputs[red2AStop] = true + plc.inputs[red3AStop] = true + plc.inputs[blue1AStop] = true + plc.inputs[blue2AStop] = true + plc.inputs[blue3AStop] = true +} + +// used for Alternate IO stops +func (plc *ModbusPlc) SetAlternateIOStopState(input int, state bool){ + plc.inputs[input] = state +} // Returns true if the PLC is enabled in the configurations. func (plc *ModbusPlc) IsEnabled() bool { return plc.address != "" @@ -186,6 +207,7 @@ func (plc *ModbusPlc) Run() { if !plc.IsEnabled() { // No PLC is configured; just allow the loop to continue to simulate inputs and outputs. plc.isHealthy = false + } else { err := plc.connect() if err != nil { @@ -403,6 +425,7 @@ func (plc *ModbusPlc) update() { // Detect any changes in input or output and notify listeners if so. if plc.inputs != plc.oldInputs || plc.registers != plc.oldRegisters || plc.coils != plc.oldCoils { + log.Print("changes in input or output") plc.ioChangeNotifier.Notify() plc.oldInputs = plc.inputs plc.oldRegisters = plc.registers diff --git a/static/css/scoring_panel.css b/static/css/scoring_panel.css index c077ed19..28186285 100644 --- a/static/css/scoring_panel.css +++ b/static/css/scoring_panel.css @@ -181,6 +181,7 @@ body { .shortcut { margin: 0 0.2vw; font-size: 1vw; + width: 100%; align-self: flex-start; } .row{ @@ -219,4 +220,73 @@ body { } .icon { width: 100%; +} + +.eStops { + width: 98%; + display: flex; + flex-direction: row; + justify-content: space-evenly; + align-items: center; + border-radius: 25px; + border: 1px solid #333; +} + .teamEstopPanle { + width: 10vw; + height: auto; + margin: 0.4vw 3vw; + justify-content: space-between; + align-items: center; + } + + .octagon-button { + position: relative; + width: 10vw; /* Adjust width */ + height: 10vw; /* Adjust height */ + clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + font-size: 2vw; + color: rgb(255, 255, 255); /* Text color */ + cursor: pointer; + border: none; + } + + .octagon-button .icon { + margin-top: 1px; + } + + .octagon-button:hover { + background: #fbff06; /* Change color on hover */ + color: rgb(0, 0, 0); /* Text color */ + } + + .iconCentered{ + display: flex; + align-items: center; /* Vertically center the content */ + justify-content: center; /* Horizontally center the content */ + text-align: center; /* Center text alignment */ + height: auto; /* Allow dynamic height adjustment */ + line-height: 1.5; /* Adjust line spacing if needed */ + padding: 10px; /* Add padding to increase perceived size */ + font-size: 1.2em; /* Increase font size for visibility */ + } + .shorcutCentered { + align-items: center; /* Vertically center the content */ + justify-content: center; /* Horizontally center the content */ + margin-bottom: 5px; /* Space between text and icon */ + text-align: center; /* Center text within its container */ + } + + .hidden { + display: none; /* Completely hides the element */ +} +.btnE { + background: rgb(255, 0, 0); +} +.btnA { + background: rgb(0, 119, 255); } \ No newline at end of file diff --git a/static/js/scoring_panel.js b/static/js/scoring_panel.js index 297c7ae3..c93bdf1a 100644 --- a/static/js/scoring_panel.js +++ b/static/js/scoring_panel.js @@ -117,3 +117,20 @@ $(function() { realtimeScore: function(event) { handleRealtimeScore(event.data); }, }); }); + +// Set initial visibility state +let bool = false; + +// Function to toggle visibility of the panel +function toggleEstopPanel() { + const panel = document.querySelector('.eStops'); + if (bool) { + panel.classList.remove('hidden'); // Hide the panel + } else { + panel.classList.add('hidden'); // Show the panel + } + bool = !bool; // Toggle the boolean state +} + +// Attach event listener to the button +document.getElementById('toggleButton').addEventListener('click', toggleEstopPanel); diff --git a/templates/scoring_panel.html b/templates/scoring_panel.html index 4c14eb30..0cff5b3e 100644 --- a/templates/scoring_panel.html +++ b/templates/scoring_panel.html @@ -19,16 +19,16 @@
Speaker - {{template "goaladd" dict "plus" "S" "icon1" "Auto+"}} - {{template "2goal" dict "btn1" "O" "icon1" "Auto+" "btn2" "o" "icon2" "Auto-" "count" "autoSpeakerNotes"}} - {{template "2goal" dict "btn1" "P" "icon1" "Amped+" "btn2" "p" "icon2" "Amped-" "count" "teleopAmplifiedSpeakerNotes"}} - {{template "2goal" dict "btn1" "I" "icon1" "Teliop+" "btn2" "i" "icon2" "Teliop-" "count" "teleopUnamplifiedSpeakerNotes"}} + {{template "goaladd" dict "plus" "S" "txt1" "Auto+"}} + {{template "2goal" dict "btn1" "O" "txt1" "Auto+" "btn2" "o" "txt2" "Auto-" "count" "autoSpeakerNotes"}} + {{template "2goal" dict "btn1" "P" "txt1" "Amped+" "btn2" "p" "txt2" "Amped-" "count" "teleopAmplifiedSpeakerNotes"}} + {{template "2goal" dict "btn1" "I" "txt1" "Teliop+" "btn2" "i" "txt2" "Teliop-" "count" "teleopUnamplifiedSpeakerNotes"}}
AMP - {{template "goaladd" dict "plus" "A" "icon1" "Sensor"}} - {{template "2goal" dict "btn1" "U" "icon1" "Auto+" "btn2" "u" "icon2" "Auto-" "count" "autoAmpNotes"}} - {{template "2goal" dict "btn1" "Y" "icon1" "Teliop+" "btn2" "y" "icon2" "Teliop-" "count" "teleopAmpNotes"}} - {{template "2goal" dict "btn1" "B" "icon1" "bank+" "btn2" "b" "icon2" "bank-" "count" "bankedAmpNotes"}} + {{template "goaladd" dict "plus" "A" "txt1" "Sensor"}} + {{template "2goal" dict "btn1" "U" "txt1" "Auto+" "btn2" "u" "txt2" "Auto-" "count" "autoAmpNotes"}} + {{template "2goal" dict "btn1" "Y" "txt1" "Teliop+" "btn2" "y" "txt2" "Teliop-" "count" "teleopAmpNotes"}} + {{template "2goal" dict "btn1" "B" "txt1" "bank+" "btn2" "b" "txt2" "bank-" "count" "bankedAmpNotes"}}
Coop Button
@@ -93,7 +93,16 @@
+ +
EStops + {{template "2stops" dict "btn1" "E1" "txt1" "EStop" "btn2" "A1" "txt2" "AStop"}} + {{template "2stops" dict "btn1" "E2" "txt1" "EStop" "btn2" "A2" "txt2" "AStop"}} + {{template "2stops" dict "btn1" "E3" "txt1" "EStop" "btn2" "A3" "txt2" "AStop"}} +
+ + +
@@ -128,18 +137,19 @@
{{.plus}}
- {{.icon1}} + {{.txt1}}
{{.plus}}
{{end}} + {{define "goalsub"}}
{{.minus}}
- {{.icon1}} + {{.txt1}}
{{.minus}}
@@ -153,7 +163,7 @@
{{.btn1}}
- {{.icon1}} + {{.txt1}}
{{.btn1}}
@@ -161,7 +171,29 @@
{{.btn2}}
- {{.icon2}} + {{.txt2}} +
+
{{.minus}}
+
+
+{{end}} + +{{define "2stops"}} +
+
+
{{.btn1}} +
+ {{.txt1}} +
+
+
{{.btn1}}
+
+
 
+
+
{{.btn2}} +
+ {{.txt2}} +
{{.minus}}
diff --git a/web/match_play.go b/web/match_play.go index 546ced68..d0eee35b 100644 --- a/web/match_play.go +++ b/web/match_play.go @@ -107,6 +107,8 @@ func (web *Web) matchPlayMatchLoadHandler(w http.ResponseWriter, r *http.Request handleWebErr(w, err) return } + + web.arena.Plc.ResetEstops() } // The websocket endpoint for the match play client to send control commands and receive status updates. diff --git a/web/scoring_panel.go b/web/scoring_panel.go index 067b5629..13cb8b1f 100644 --- a/web/scoring_panel.go +++ b/web/scoring_panel.go @@ -299,6 +299,66 @@ func (web *Web) scoringPanelWebsocketHandler(w http.ResponseWriter, r *http.Requ web.arena.CurrentMatch.Type == model.Playoff) log.Printf("coopButton Pressed") scoreChanged = true + case "E1": + if web.arena.EventSettings.AlternateIOEnabled{ + if alliance == "red"{ + web.arena.Plc.SetAlternateIOStopState(1,false) + }else{ + web.arena.Plc.SetAlternateIOStopState(7,false) + } + log.Printf("E1 Pressed") + scoreChanged = true + } + case "A1": + if web.arena.EventSettings.AlternateIOEnabled{ + if alliance == "red"{ + web.arena.Plc.SetAlternateIOStopState(2,false) + }else{ + web.arena.Plc.SetAlternateIOStopState(8,false) + } + log.Printf("A1 Pressed") + scoreChanged = true + } + case "E2": + if web.arena.EventSettings.AlternateIOEnabled{ + if alliance == "red"{ + web.arena.Plc.SetAlternateIOStopState(3,false) + }else{ + web.arena.Plc.SetAlternateIOStopState(9,false) + } + log.Printf("E2 Pressed") + scoreChanged = true + } + case "A2": + if web.arena.EventSettings.AlternateIOEnabled{ + if alliance == "red"{ + web.arena.Plc.SetAlternateIOStopState(4,false) + }else{ + web.arena.Plc.SetAlternateIOStopState(10,false) + } + log.Printf("A2 Pressed") + scoreChanged = true + } + case "E3": + if web.arena.EventSettings.AlternateIOEnabled{ + if alliance == "red"{ + web.arena.Plc.SetAlternateIOStopState(5,false) + }else{ + web.arena.Plc.SetAlternateIOStopState(11,false) + } + log.Printf("E3 Pressed") + scoreChanged = true + } + case "A3": + if web.arena.EventSettings.AlternateIOEnabled{ + if alliance == "red"{ + web.arena.Plc.SetAlternateIOStopState(6,false) + }else{ + web.arena.Plc.SetAlternateIOStopState(12,false) + } + log.Printf("A3 Pressed") + scoreChanged = true + } } if scoreChanged {