Split up display configuration and LED/PLC testing into separate setup pages.

This commit is contained in:
Patrick Fairbank
2018-09-03 15:24:37 -07:00
parent 03f357451a
commit 03208eaa7a
11 changed files with 232 additions and 177 deletions

View File

@@ -37,7 +37,7 @@ var handlePlcIoChange = function(data) {
$(function() {
// Set up the websocket back to the server.
websocket = new CheesyWebsocket("/setup/field/websocket", {
websocket = new CheesyWebsocket("/setup/led_plc/websocket", {
ledMode: function(event) {handleLedMode(event.data); },
plcIoChange: function(event) { handlePlcIoChange(event.data); }
});

View File

@@ -38,12 +38,13 @@
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Setup</a>
<ul class="dropdown-menu">
<li><a href="/setup/settings">Settings</a></li>
<li><a href="/setup/field">Field Configuration</a></li>
<li><a href="/setup/teams">Team List</a></li>
<li><a href="/setup/schedule">Match Scheduling</a></li>
<li><a href="/setup/alliance_selection">Alliance Selection</a></li>
<li><a href="/setup/lower_thirds">Lower Thirds</a></li>
<li><a href="/setup/sponsor_slides">Sponsor Slides</a></li>
<li><a href="/setup/displays">Display Configuration</a></li>
<li><a href="/setup/led_plc">LED and PLC Testing</a></li>
</ul>
</li>
<li class="dropdown">

View File

@@ -0,0 +1,41 @@
{{/*
Copyright 2018 Team 254. All Rights Reserved.
Author: pat@patfairbank.com (Patrick Fairbank)
UI for configuring the field displays.
*/}}
{{define "title"}}Display Configuration{{end}}
{{define "body"}}
<div class="row">
<div class="col-lg-3">
<div class="well">
<legend>Alliance Station Displays</legend>
{{range $displayId, $station := .AllianceStationDisplays}}
<form class="form-horizontal" action="" method="POST">
<div class="form-group">
<label class="col-lg-5 control-label">Display {{$displayId}}</label>
<div class="col-lg-7">
<input type="hidden" name="displayId" value="{{$displayId}}" />
<select class="form-control" name="allianceStation" onchange="this.form.submit();">
<option value=""></option>
<option value="R1"{{if eq $station "R1"}} selected{{end}}>Red 1</option>
<option value="R2"{{if eq $station "R2"}} selected{{end}}>Red 2</option>
<option value="R3"{{if eq $station "R3"}} selected{{end}}>Red 3</option>
<option value="B1"{{if eq $station "B1"}} selected{{end}}>Blue 1</option>
<option value="B2"{{if eq $station "B2"}} selected{{end}}>Blue 2</option>
<option value="B3"{{if eq $station "B3"}} selected{{end}}>Blue 3</option>
</select>
</div>
</div>
</form>
{{end}}
<legend>Reload All Displays</legend>
<div class="form-group">
<a href="/setup/displays/reload" class="btn btn-primary">Force Reload of All Displays</a>
</div>
</div>
</div>
</div>
{{end}}
{{define "script"}}
{{end}}

View File

@@ -1,113 +0,0 @@
{{/*
Copyright 2014 Team 254. All Rights Reserved.
Author: pat@patfairbank.com (Patrick Fairbank)
UI for controlling ephemeral aspects of the playing field.
*/}}
{{define "title"}}Field Configuration{{end}}
{{define "body"}}
<div class="row">
<div class="col-lg-3">
<div class="well">
<legend>Alliance Station Displays</legend>
{{range $displayId, $station := .AllianceStationDisplays}}
<form class="form-horizontal" action="/setup/field" method="POST">
<div class="form-group">
<label class="col-lg-5 control-label">Display {{$displayId}}</label>
<div class="col-lg-7">
<input type="hidden" name="displayId" value="{{$displayId}}" />
<select class="form-control" name="allianceStation" onchange="this.form.submit();">
<option value=""></option>
<option value="R1"{{if eq $station "R1"}} selected{{end}}>Red 1</option>
<option value="R2"{{if eq $station "R2"}} selected{{end}}>Red 2</option>
<option value="R3"{{if eq $station "R3"}} selected{{end}}>Red 3</option>
<option value="B1"{{if eq $station "B1"}} selected{{end}}>Blue 1</option>
<option value="B2"{{if eq $station "B2"}} selected{{end}}>Blue 2</option>
<option value="B3"{{if eq $station "B3"}} selected{{end}}>Blue 3</option>
</select>
</div>
</div>
</form>
{{end}}
<legend>Reload All Displays</legend>
<div class="form-group">
<a href="/setup/field/reload_displays" class="btn btn-primary">Force Reload of All Displays</a>
</div>
</div>
</div>
<div class="col-lg-7">
<div class="well">
<legend>PLC</legend>
<div class="row">
<div class="col-lg-4">
<table class="table">
<tr>
<th colspan="2">Inputs</th>
</tr>
{{range $i, $name := .InputNames}}
<tr>
<td>{{$name}}</td>
<td id="input{{$i}}"></td>
</tr>
{{end}}
</table>
</div>
<div class="col-lg-4">
<table class="table">
<tr>
<th colspan="2">Registers</th>
</tr>
{{range $i, $name := .RegisterNames}}
<tr>
<td>{{$name}}</td>
<td id="register{{$i}}"></td>
</tr>
{{end}}
</table>
</div>
<div class="col-lg-4">
<table class="table">
<tr>
<th colspan="2">Coils</th>
</tr>
{{range $i, $name := .CoilNames}}
<tr>
<td>{{$name}}</td>
<td id="coil{{$i}}"></td>
</tr>
{{end}}
</table>
</div>
</div>
</div>
</div>
<div class="col-lg-2">
<div class="well">
<legend>LEDs</legend>
<div class="form-group">
<label>Switch/Scale</label>
{{range $i, $name := .LedModeNames}}
<div class="radio">
<label>
<input type="radio" name="ledMode" value="{{$i}}" onclick="setLedMode();">{{$name}}
</label>
</div>
{{end}}
</div>
<div class="form-group">
<label>Vault</label>
{{range $i, $name := .VaultLedModeNames}}
<div class="radio">
<label>
<input type="radio" name="vaultLedMode" value="{{$i}}" onclick="setLedMode();">{{$name}}
</label>
</div>
{{end}}
</div>
</div>
</div>
</div>
{{end}}
{{define "script"}}
<script src="/static/js/setup_field.js"></script>
{{end}}

View File

@@ -0,0 +1,84 @@
{{/*
Copyright 2018 Team 254. All Rights Reserved.
Author: pat@patfairbank.com (Patrick Fairbank)
UI for testing the LEDs and PLC connected to the field.
*/}}
{{define "title"}}LED and PLC Testing{{end}}
{{define "body"}}
<div class="row">
<div class="col-lg-7">
<div class="well">
<legend>PLC</legend>
<div class="row">
<div class="col-lg-4">
<table class="table">
<tr>
<th colspan="2">Inputs</th>
</tr>
{{range $i, $name := .InputNames}}
<tr>
<td>{{$name}}</td>
<td id="input{{$i}}"></td>
</tr>
{{end}}
</table>
</div>
<div class="col-lg-4">
<table class="table">
<tr>
<th colspan="2">Registers</th>
</tr>
{{range $i, $name := .RegisterNames}}
<tr>
<td>{{$name}}</td>
<td id="register{{$i}}"></td>
</tr>
{{end}}
</table>
</div>
<div class="col-lg-4">
<table class="table">
<tr>
<th colspan="2">Coils</th>
</tr>
{{range $i, $name := .CoilNames}}
<tr>
<td>{{$name}}</td>
<td id="coil{{$i}}"></td>
</tr>
{{end}}
</table>
</div>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="well">
<legend>Switch/Scale LEDs</legend>
{{range $i, $name := .LedModeNames}}
<div class="radio">
<label>
<input type="radio" name="ledMode" value="{{$i}}" onclick="setLedMode();">{{$name}}
</label>
</div>
{{end}}
</div>
</div>
<div class="col-lg-2">
<div class="well">
<legend>Vault LEDs</legend>
{{range $i, $name := .VaultLedModeNames}}
<div class="radio">
<label>
<input type="radio" name="vaultLedMode" value="{{$i}}" onclick="setLedMode();">{{$name}}
</label>
</div>
{{end}}
</div>
</div>
</div>
{{end}}
{{define "script"}}
<script src="/static/js/setup_led_plc.js"></script>
{{end}}

View File

@@ -29,7 +29,7 @@ func TestPitDisplayWebsocket(t *testing.T) {
ws := websocket.NewTestWebsocket(conn)
// Check forced reloading as that is the only purpose the pit websocket serves.
recorder := web.getHttpResponse("/setup/field/reload_displays")
recorder := web.getHttpResponse("/setup/displays/reload")
assert.Equal(t, 303, recorder.Code)
readWebsocketType(t, ws, "reload")
}

56
web/setup_displays.go Normal file
View File

@@ -0,0 +1,56 @@
// Copyright 2018 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Web routes for configuring the field displays.
package web
import (
"github.com/Team254/cheesy-arena/model"
"net/http"
)
// Shows the displays configuration page.
func (web *Web) displaysGetHandler(w http.ResponseWriter, r *http.Request) {
if !web.userIsAdmin(w, r) {
return
}
template, err := web.parseFiles("templates/setup_displays.html", "templates/base.html")
if err != nil {
handleWebErr(w, err)
return
}
data := struct {
*model.EventSettings
AllianceStationDisplays map[string]string
}{web.arena.EventSettings, web.arena.AllianceStationDisplays}
err = template.ExecuteTemplate(w, "base", data)
if err != nil {
handleWebErr(w, err)
return
}
}
// Updates the display-station mapping for a single display.
func (web *Web) displaysPostHandler(w http.ResponseWriter, r *http.Request) {
if !web.userIsAdmin(w, r) {
return
}
displayId := r.PostFormValue("displayId")
allianceStation := r.PostFormValue("allianceStation")
web.arena.AllianceStationDisplays[displayId] = allianceStation
web.arena.MatchLoadNotifier.Notify()
http.Redirect(w, r, "/setup/displays", 303)
}
// Force-reloads all the websocket-connected displays.
func (web *Web) displaysReloadHandler(w http.ResponseWriter, r *http.Request) {
if !web.userIsAdmin(w, r) {
return
}
web.arena.ReloadDisplaysNotifier.Notify()
http.Redirect(w, r, "/setup/displays", 303)
}

View File

@@ -0,0 +1,25 @@
// Copyright 2018 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
package web
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestSetupDisplays(t *testing.T) {
web := setupTestWeb(t)
web.arena.AllianceStationDisplays["12345"] = ""
recorder := web.getHttpResponse("/setup/displays")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "12345")
assert.NotContains(t, recorder.Body.String(), "selected")
recorder = web.postHttpResponse("/setup/displays", "displayId=12345&allianceStation=B1")
assert.Equal(t, 303, recorder.Code)
recorder = web.getHttpResponse("/setup/displays")
assert.Contains(t, recorder.Body.String(), "12345")
assert.Contains(t, recorder.Body.String(), "selected")
}

View File

@@ -1,7 +1,7 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Copyright 2018 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Web routes for configuring the field components.
// Web routes for testing the field LEDs and PLC.
package web
@@ -18,13 +18,13 @@ import (
"net/http"
)
// Shows the field configuration page.
func (web *Web) fieldGetHandler(w http.ResponseWriter, r *http.Request) {
// Shows the LED/PLC test page.
func (web *Web) ledPlcGetHandler(w http.ResponseWriter, r *http.Request) {
if !web.userIsAdmin(w, r) {
return
}
template, err := web.parseFiles("templates/setup_field.html", "templates/base.html")
template, err := web.parseFiles("templates/setup_led_plc.html", "templates/base.html")
if err != nil {
handleWebErr(w, err)
return
@@ -32,14 +32,13 @@ func (web *Web) fieldGetHandler(w http.ResponseWriter, r *http.Request) {
plc := web.arena.Plc
data := struct {
*model.EventSettings
AllianceStationDisplays map[string]string
InputNames []string
RegisterNames []string
CoilNames []string
LedModeNames map[led.Mode]string
VaultLedModeNames map[vaultled.Mode]string
}{web.arena.EventSettings, web.arena.AllianceStationDisplays, plc.GetInputNames(), plc.GetRegisterNames(),
plc.GetCoilNames(), led.ModeNames, vaultled.ModeNames}
InputNames []string
RegisterNames []string
CoilNames []string
LedModeNames map[led.Mode]string
VaultLedModeNames map[vaultled.Mode]string
}{web.arena.EventSettings, plc.GetInputNames(), plc.GetRegisterNames(), plc.GetCoilNames(), led.ModeNames,
vaultled.ModeNames}
err = template.ExecuteTemplate(w, "base", data)
if err != nil {
handleWebErr(w, err)
@@ -47,31 +46,8 @@ func (web *Web) fieldGetHandler(w http.ResponseWriter, r *http.Request) {
}
}
// Updates the display-station mapping for a single display.
func (web *Web) fieldPostHandler(w http.ResponseWriter, r *http.Request) {
if !web.userIsAdmin(w, r) {
return
}
displayId := r.PostFormValue("displayId")
allianceStation := r.PostFormValue("allianceStation")
web.arena.AllianceStationDisplays[displayId] = allianceStation
web.arena.MatchLoadNotifier.Notify()
http.Redirect(w, r, "/setup/field", 303)
}
// Force-reloads all the websocket-connected displays.
func (web *Web) fieldReloadDisplaysHandler(w http.ResponseWriter, r *http.Request) {
if !web.userIsAdmin(w, r) {
return
}
web.arena.ReloadDisplaysNotifier.Notify()
http.Redirect(w, r, "/setup/field", 303)
}
// The websocket endpoint for sending realtime updates to the field setup page.
func (web *Web) fieldWebsocketHandler(w http.ResponseWriter, r *http.Request) {
// The websocket endpoint for sending realtime updates to the LED/PLC test page.
func (web *Web) ledPlcWebsocketHandler(w http.ResponseWriter, r *http.Request) {
if !web.userIsAdmin(w, r) {
return
}

View File

@@ -1,4 +1,4 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Copyright 2018 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
package web
@@ -15,28 +15,12 @@ import (
"github.com/mitchellh/mapstructure"
)
func TestSetupField(t *testing.T) {
web := setupTestWeb(t)
web.arena.AllianceStationDisplays["12345"] = ""
recorder := web.getHttpResponse("/setup/field")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "12345")
assert.NotContains(t, recorder.Body.String(), "selected")
recorder = web.postHttpResponse("/setup/field", "displayId=12345&allianceStation=B1")
assert.Equal(t, 303, recorder.Code)
recorder = web.getHttpResponse("/setup/field")
assert.Contains(t, recorder.Body.String(), "12345")
assert.Contains(t, recorder.Body.String(), "selected")
}
func TestSetupFieldWebsocket(t *testing.T) {
func TestSetupLedPlcWebsocket(t *testing.T) {
web := setupTestWeb(t)
server, wsUrl := web.startTestServer()
defer server.Close()
conn, _, err := gorillawebsocket.DefaultDialer.Dial(wsUrl+"/setup/field/websocket", nil)
conn, _, err := gorillawebsocket.DefaultDialer.Dial(wsUrl+"/setup/led_plc/websocket", nil)
assert.Nil(t, err)
defer conn.Close()
ws := websocket.NewTestWebsocket(conn)

View File

@@ -148,10 +148,11 @@ func (web *Web) newHandler() http.Handler {
router.HandleFunc("/setup/alliance_selection/reset", web.allianceSelectionResetHandler).Methods("POST")
router.HandleFunc("/setup/alliance_selection/finalize", web.allianceSelectionFinalizeHandler).Methods("POST")
router.HandleFunc("/setup/alliance_selection/publish", web.allianceSelectionPublishHandler).Methods("POST")
router.HandleFunc("/setup/field", web.fieldGetHandler).Methods("GET")
router.HandleFunc("/setup/field", web.fieldPostHandler).Methods("POST")
router.HandleFunc("/setup/field/reload_displays", web.fieldReloadDisplaysHandler).Methods("GET")
router.HandleFunc("/setup/field/websocket", web.fieldWebsocketHandler).Methods("GET")
router.HandleFunc("/setup/displays", web.displaysGetHandler).Methods("GET")
router.HandleFunc("/setup/displays", web.displaysPostHandler).Methods("POST")
router.HandleFunc("/setup/displays/reload", web.displaysReloadHandler).Methods("GET")
router.HandleFunc("/setup/led_plc", web.ledPlcGetHandler).Methods("GET")
router.HandleFunc("/setup/led_plc/websocket", web.ledPlcWebsocketHandler).Methods("GET")
router.HandleFunc("/setup/lower_thirds", web.lowerThirdsGetHandler).Methods("GET")
router.HandleFunc("/setup/lower_thirds/websocket", web.lowerThirdsWebsocketHandler).Methods("GET")
router.HandleFunc("/setup/sponsor_slides", web.sponsorSlidesGetHandler).Methods("GET")