From 347ad0488506995eb6228e118e114bf59f3b3f31 Mon Sep 17 00:00:00 2001 From: Patrick Fairbank Date: Sat, 18 Aug 2018 21:01:42 -0700 Subject: [PATCH] Make PLC inputs/outputs on field setup page update in realtime. --- field/plc.go | 30 ++++++++++++++----- static/js/setup_field.js | 28 ++++++++++++++++++ templates/setup_field.html | 19 ++++++------ web/setup_field.go | 60 ++++++++++++++++++++++++++++++++++---- web/web.go | 1 + 5 files changed, 115 insertions(+), 23 deletions(-) create mode 100644 static/js/setup_field.js 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")