diff --git a/field/plc.go b/field/plc.go
index 185f16b..9441161 100644
--- a/field/plc.go
+++ b/field/plc.go
@@ -13,14 +13,18 @@ import (
)
type Plc struct {
- IsHealthy bool
- address string
- handler *modbus.TCPClientHandler
- client modbus.Client
- Inputs [inputCount]bool
- Registers [registerCount]uint16
- Coils [coilCount]bool
- cycleCounter int
+ IsHealthy bool
+ address string
+ handler *modbus.TCPClientHandler
+ client modbus.Client
+ Inputs [inputCount]bool
+ Registers [registerCount]uint16
+ Coils [coilCount]bool
+ oldInputs [inputCount]bool
+ oldRegisters [registerCount]uint16
+ oldCoils [coilCount]bool
+ IoChangeNotifier *Notifier
+ cycleCounter int
}
const (
@@ -100,6 +104,8 @@ func (plc *Plc) SetAddress(address string) {
// Loops indefinitely to read inputs from and write outputs to PLC.
func (plc *Plc) Run() {
+ plc.IoChangeNotifier = NewNotifier()
+
for {
if plc.handler == nil {
if plc.address == "" {
@@ -131,6 +137,14 @@ func (plc *Plc) Run() {
plc.cycleCounter = 0
}
+ // 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 {
+ plc.IoChangeNotifier.Notify(nil)
+ plc.oldInputs = plc.Inputs
+ plc.oldRegisters = plc.Registers
+ plc.oldCoils = plc.Coils
+ }
+
time.Sleep(time.Until(startTime.Add(time.Millisecond * plcLoopPeriodMs)))
}
}
diff --git a/static/js/setup_field.js b/static/js/setup_field.js
new file mode 100644
index 0000000..e349b3f
--- /dev/null
+++ b/static/js/setup_field.js
@@ -0,0 +1,28 @@
+// Copyright 2018 Team 254. All Rights Reserved.
+// Author: pat@patfairbank.com (Patrick Fairbank)
+//
+// Client-side logic for the field setup page.
+
+var websocket;
+
+// Handles a websocket message to update the PLC IO status.
+var handlePlcIoChange = function(data) {
+ $.each(data.Inputs, function(index, input) {
+ $("#input" + index).text(input)
+ });
+
+ $.each(data.Registers, function(index, register) {
+ $("#register" + index).text(register)
+ });
+
+ $.each(data.Coils, function(index, coil) {
+ $("#coil" + index).text(coil)
+ });
+};
+
+$(function() {
+ // Set up the websocket back to the server.
+ websocket = new CheesyWebsocket("/setup/field/websocket", {
+ plcIoChange: function(event) { handlePlcIoChange(event.data); }
+ });
+});
diff --git a/templates/setup_field.html b/templates/setup_field.html
index 0a3f67f..55453d7 100644
--- a/templates/setup_field.html
+++ b/templates/setup_field.html
@@ -44,10 +44,10 @@
| Inputs |
- {{range $i, $value := .Inputs}}
+ {{range $i, $name := .InputNames}}
- | {{index $.InputNames $i}} |
- {{$value}} |
+ {{$name}} |
+ |
{{end}}
@@ -57,10 +57,10 @@
| Registers |
- {{range $i, $value := .Registers}}
+ {{range $i, $name := .RegisterNames}}
- | {{index $.RegisterNames $i}} |
- {{$value}} |
+ {{$name}} |
+ |
{{end}}
@@ -70,10 +70,10 @@
| Coils |
- {{range $i, $value := .Coils}}
+ {{range $i, $name := .CoilNames}}
- | {{index $.CoilNames $i}} |
- {{$value}} |
+ {{$name}} |
+ |
{{end}}
@@ -101,4 +101,5 @@
{{end}}
{{define "script"}}
+
{{end}}
diff --git a/web/setup_field.go b/web/setup_field.go
index f50402b..1b4a891 100644
--- a/web/setup_field.go
+++ b/web/setup_field.go
@@ -9,6 +9,7 @@ import (
"github.com/Team254/cheesy-arena/field"
"github.com/Team254/cheesy-arena/led"
"github.com/Team254/cheesy-arena/model"
+ "log"
"net/http"
"strconv"
)
@@ -28,17 +29,13 @@ func (web *Web) fieldGetHandler(w http.ResponseWriter, r *http.Request) {
data := struct {
*model.EventSettings
AllianceStationDisplays map[string]string
- Inputs []bool
InputNames []string
- Registers []uint16
RegisterNames []string
- Coils []bool
CoilNames []string
CurrentLedMode led.Mode
LedModeNames map[led.Mode]string
- }{web.arena.EventSettings, web.arena.AllianceStationDisplays, plc.Inputs[:], plc.GetInputNames(), plc.Registers[:],
- plc.GetRegisterNames(), plc.Coils[:], plc.GetCoilNames(), web.arena.ScaleLeds.GetCurrentMode(),
- led.ModeNames}
+ }{web.arena.EventSettings, web.arena.AllianceStationDisplays, plc.GetInputNames(), plc.GetRegisterNames(),
+ plc.GetCoilNames(), web.arena.ScaleLeds.GetCurrentMode(), led.ModeNames}
err = template.ExecuteTemplate(w, "base", data)
if err != nil {
handleWebErr(w, err)
@@ -88,3 +85,54 @@ func (web *Web) fieldTestPostHandler(w http.ResponseWriter, r *http.Request) {
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) {
+ if !web.userIsAdmin(w, r) {
+ return
+ }
+
+ websocket, err := NewWebsocket(w, r)
+ if err != nil {
+ handleWebErr(w, err)
+ return
+ }
+ defer websocket.Close()
+
+ plcIoChangeListener := web.arena.Plc.IoChangeNotifier.Listen()
+ defer close(plcIoChangeListener)
+
+ // Send the PLC status immediately upon connection.
+ data := struct {
+ Inputs []bool
+ Registers []uint16
+ Coils []bool
+ }{web.arena.Plc.Inputs[:], web.arena.Plc.Registers[:], web.arena.Plc.Coils[:]}
+ err = websocket.Write("plcIoChange", data)
+ if err != nil {
+ log.Printf("Websocket error: %s", err)
+ return
+ }
+
+ for {
+ var messageType string
+ var message interface{}
+ select {
+ case _, ok := <-plcIoChangeListener:
+ if !ok {
+ return
+ }
+ messageType = "plcIoChange"
+ message = struct {
+ Inputs []bool
+ Registers []uint16
+ Coils []bool
+ }{web.arena.Plc.Inputs[:], web.arena.Plc.Registers[:], web.arena.Plc.Coils[:]}
+ }
+ err = websocket.Write(messageType, message)
+ if err != nil {
+ // The client has probably closed the connection; nothing to do here.
+ return
+ }
+ }
+}
diff --git a/web/web.go b/web/web.go
index 1f05ea3..685316d 100644
--- a/web/web.go
+++ b/web/web.go
@@ -152,6 +152,7 @@ func (web *Web) newHandler() http.Handler {
router.HandleFunc("/setup/field", web.fieldPostHandler).Methods("POST")
router.HandleFunc("/setup/field/reload_displays", web.fieldReloadDisplaysHandler).Methods("GET")
router.HandleFunc("/setup/field/test", web.fieldTestPostHandler).Methods("POST")
+ router.HandleFunc("/setup/field/websocket", web.fieldWebsocketHandler).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")