mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 13:46:44 -04:00
Make PLC inputs/outputs on field setup page update in realtime.
This commit is contained in:
30
field/plc.go
30
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)))
|
||||
}
|
||||
}
|
||||
|
||||
28
static/js/setup_field.js
Normal file
28
static/js/setup_field.js
Normal file
@@ -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); }
|
||||
});
|
||||
});
|
||||
@@ -44,10 +44,10 @@
|
||||
<tr>
|
||||
<th colspan="2">Inputs</th>
|
||||
</tr>
|
||||
{{range $i, $value := .Inputs}}
|
||||
{{range $i, $name := .InputNames}}
|
||||
<tr>
|
||||
<td>{{index $.InputNames $i}}</td>
|
||||
<td>{{$value}}</td>
|
||||
<td>{{$name}}</td>
|
||||
<td id="input{{$i}}"></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
@@ -57,10 +57,10 @@
|
||||
<tr>
|
||||
<th colspan="2">Registers</th>
|
||||
</tr>
|
||||
{{range $i, $value := .Registers}}
|
||||
{{range $i, $name := .RegisterNames}}
|
||||
<tr>
|
||||
<td>{{index $.RegisterNames $i}}</td>
|
||||
<td>{{$value}}</td>
|
||||
<td>{{$name}}</td>
|
||||
<td id="register{{$i}}"></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
@@ -70,10 +70,10 @@
|
||||
<tr>
|
||||
<th colspan="2">Coils</th>
|
||||
</tr>
|
||||
{{range $i, $value := .Coils}}
|
||||
{{range $i, $name := .CoilNames}}
|
||||
<tr>
|
||||
<td>{{index $.CoilNames $i}}</td>
|
||||
<td>{{$value}}</td>
|
||||
<td>{{$name}}</td>
|
||||
<td id="coil{{$i}}"></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
@@ -101,4 +101,5 @@
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "script"}}
|
||||
<script src="/static/js/setup_field.js"></script>
|
||||
{{end}}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user