Added golangci linting and improved what was required

This commit is contained in:
Nicholas Thompson
2019-03-07 19:04:15 +02:00
committed by ncthompson
parent 206159cdea
commit 2456f45836
15 changed files with 244 additions and 255 deletions

29
.golangci.yml Normal file
View File

@@ -0,0 +1,29 @@
run:
deadline: 10m
linters:
enable-all: false
enable:
# Enabled by default in golangci-lint v1.13.2
- deadcode
- errcheck
- govet
- ineffassign
- typecheck
- varcheck
# Disabled by default in golangci-lint v1.13.2
- dupl
- goconst
- gofmt
- golint
- unconvert
# The following result in high memory usage (>1GB)
# https://github.com/golangci/golangci-lint/issues/337
- staticcheck
- structcheck
- gosimple
- unused
issues:
max-per-linter: 0
max-same-issues: 0

View File

@@ -29,8 +29,7 @@
.PHONY: test install gofmt docker statik .PHONY: test install gofmt docker statik
build: build:
go build ./cmd/invertergui/ go build ./cmd/...
go build ./cmd/invertercli/
all: build gofmt test all: build gofmt test
@@ -51,3 +50,6 @@ docker:
statik: statik:
statik -p=frontend -src=./frontend/root statik -p=frontend -src=./frontend/root
lint:
golangci-lint run

View File

@@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"github.com/hpdvanwyk/invertergui/frontend" "github.com/hpdvanwyk/invertergui/frontend"
"github.com/hpdvanwyk/invertergui/mk2if"
"github.com/hpdvanwyk/invertergui/webgui" "github.com/hpdvanwyk/invertergui/webgui"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
@@ -13,7 +14,7 @@ import (
func main() { func main() {
addr := flag.String("addr", ":8080", "TCP address to listen on.") addr := flag.String("addr", ":8080", "TCP address to listen on.")
mk2 := NewMk2Mock() mk2 := mk2if.NewMk2Mock()
gui := webgui.NewWebGui(mk2) gui := webgui.NewWebGui(mk2)
http.Handle("/", frontend.NewStatic()) http.Handle("/", frontend.NewStatic())

View File

@@ -1,90 +0,0 @@
package main
import (
"fmt"
"time"
"github.com/hpdvanwyk/invertergui/mk2if"
)
type mock struct {
c chan *mk2if.Mk2Info
}
func NewMk2Mock() mk2if.Mk2If {
tmp := &mock{
c: make(chan *mk2if.Mk2Info, 1),
}
go tmp.genMockValues()
return tmp
}
func genBaseLeds(state mk2if.LEDstate) map[mk2if.Led]mk2if.LEDstate {
return map[mk2if.Led]mk2if.LEDstate{
mk2if.LedMain: state,
mk2if.LedAbsorption: state,
mk2if.LedBulk: state,
mk2if.LedFloat: state,
mk2if.LedInverter: state,
mk2if.LedOverload: state,
mk2if.LedLowBattery: state,
mk2if.LedTemperature: state,
}
}
func (m *mock) GetMk2Info() *mk2if.Mk2Info {
return &mk2if.Mk2Info{
OutCurrent: 2.0,
InCurrent: 2.3,
OutVoltage: 230.0,
InVoltage: 230.1,
BatVoltage: 25,
BatCurrent: -10,
InFrequency: 50,
OutFrequency: 50,
ChargeState: 1,
Errors: nil,
Timestamp: time.Now(),
LEDs: genBaseLeds(mk2if.LedOff),
}
}
func (m *mock) C() chan *mk2if.Mk2Info {
return m.c
}
func (m *mock) Close() {
}
func (m *mock) genMockValues() {
mult := 1.0
ledState := mk2if.LedOff
for {
input := &mk2if.Mk2Info{
OutCurrent: 2.0 * mult,
InCurrent: 2.3 * mult,
OutVoltage: 230.0 * mult,
InVoltage: 230.1 * mult,
BatVoltage: 25 * mult,
BatCurrent: -10 * mult,
InFrequency: 50 * mult,
OutFrequency: 50 * mult,
ChargeState: 1 * mult,
Errors: nil,
Timestamp: time.Now(),
Valid: true,
LEDs: genBaseLeds(ledState),
}
ledState = (ledState + 1) % 3
mult = mult - 0.1
if mult < 0 {
mult = 1.0
}
fmt.Printf("Sending\n")
m.c <- input
time.Sleep(1 * time.Second)
}
}

View File

@@ -3,14 +3,15 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"github.com/hpdvanwyk/invertergui/mk2if"
"github.com/mikepb/go-serial"
"io" "io"
"log" "log"
"net" "net"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/hpdvanwyk/invertergui/mk2if"
"github.com/mikepb/go-serial"
) )
// Basic CLI to serve as example lib usage // Basic CLI to serve as example lib usage
@@ -46,10 +47,11 @@ func main() {
} }
defer p.Close() defer p.Close()
mk2, err := mk2if.NewMk2Connection(p) mk2, err := mk2if.NewMk2Connection(p)
defer mk2.Close()
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer mk2.Close()
c := mk2.C() c := mk2.C()
sigterm := make(chan os.Signal, 1) sigterm := make(chan os.Signal, 1)
signal.Notify(sigterm, syscall.SIGTERM, os.Interrupt) signal.Notify(sigterm, syscall.SIGTERM, os.Interrupt)

View File

@@ -76,10 +76,10 @@ func main() {
} }
defer p.Close() defer p.Close()
mk2, err := mk2if.NewMk2Connection(p) mk2, err := mk2if.NewMk2Connection(p)
defer mk2.Close()
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer mk2.Close()
gui := webgui.NewWebGui(mk2) gui := webgui.NewWebGui(mk2)

View File

@@ -7,20 +7,10 @@ import (
"net/http" "net/http"
) )
type static struct {
http.FileSystem
}
func NewStatic() http.Handler { func NewStatic() http.Handler {
statikFs, err := fs.New() statikFs, err := fs.New()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
return &static{ return http.FileServer(statikFs)
FileSystem: statikFs,
}
}
func (s *static) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.FileServer(s).ServeHTTP(w, r)
} }

View File

@@ -16,70 +16,67 @@ type scaling struct {
} }
type mk2Ser struct { type mk2Ser struct {
info *Mk2Info info *Mk2Info
report *Mk2Info p io.ReadWriter
p io.ReadWriter scales []scaling
sc []scaling scaleCount int
scN int run chan struct{}
run chan struct{} frameLock bool
locked bool infochan chan *Mk2Info
sync.RWMutex wg sync.WaitGroup
infochan chan *Mk2Info
wg sync.WaitGroup
} }
func NewMk2Connection(dev io.ReadWriter) (Mk2If, error) { func NewMk2Connection(dev io.ReadWriter) (Mk2If, error) {
mk2 := &mk2Ser{} mk2 := &mk2Ser{}
mk2.p = dev mk2.p = dev
mk2.info = &Mk2Info{} mk2.info = &Mk2Info{}
mk2.report = &Mk2Info{} mk2.scaleCount = 0
mk2.scN = 0 mk2.frameLock = false
mk2.locked = false mk2.scales = make([]scaling, 0, 14)
mk2.sc = make([]scaling, 0)
mk2.setTarget() mk2.setTarget()
mk2.run = make(chan struct{}) mk2.run = make(chan struct{})
mk2.infochan = make(chan *Mk2Info) mk2.infochan = make(chan *Mk2Info)
mk2.wg.Add(1) mk2.wg.Add(1)
go mk2.frameLock() go mk2.frameLocker()
return mk2, nil return mk2, nil
} }
// Locks to incoming frame. // Locks to incoming frame.
func (mk2 *mk2Ser) frameLock() { func (m *mk2Ser) frameLocker() {
frame := make([]byte, 256) frame := make([]byte, 256)
var size byte var size byte
for { for {
select { select {
case <-mk2.run: case <-m.run:
mk2.wg.Done() m.wg.Done()
return return
default: default:
} }
if mk2.locked { if m.frameLock {
size = mk2.readByte() size = m.readByte()
l, err := io.ReadFull(mk2.p, frame[0:int(size)+1]) l, err := io.ReadFull(m.p, frame[0:int(size)+1])
if err != nil { if err != nil {
mk2.addError(fmt.Errorf("Read Error: %v", err)) m.addError(fmt.Errorf("Read Error: %v", err))
mk2.locked = false m.frameLock = false
} else if l != int(size)+1 { } else if l != int(size)+1 {
mk2.addError(errors.New("Read Length Error")) m.addError(errors.New("Read Length Error"))
mk2.locked = false m.frameLock = false
} else { } else {
mk2.handleFrame(size, frame[0:int(size+1)]) m.handleFrame(size, frame[0:int(size+1)])
} }
} else { } else {
tmp := mk2.readByte() tmp := m.readByte()
if tmp == 0xff || tmp == 0x20 { if tmp == 0xff || tmp == 0x20 {
l, err := io.ReadFull(mk2.p, frame[0:int(size)]) l, err := io.ReadFull(m.p, frame[0:int(size)])
if err != nil { if err != nil {
mk2.addError(fmt.Errorf("Read Error: %v", err)) m.addError(fmt.Errorf("Read Error: %v", err))
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} else if l != int(size) { } else if l != int(size) {
mk2.addError(errors.New("Read Length Error")) m.addError(errors.New("Read Length Error"))
} else { } else {
if checkChecksum(size, tmp, frame[0:int(size)]) { if checkChecksum(size, tmp, frame[0:int(size)]) {
mk2.locked = true m.frameLock = true
log.Printf("Locked") log.Printf("Locked")
} }
} }
@@ -90,109 +87,98 @@ func (mk2 *mk2Ser) frameLock() {
} }
// Close Mk2 // Close Mk2
func (mk2 *mk2Ser) Close() { func (m *mk2Ser) Close() {
close(mk2.run) close(m.run)
mk2.wg.Wait() m.wg.Wait()
} }
// Returns last known state with all reported errors since previous poll. func (m *mk2Ser) C() chan *Mk2Info {
// Mk2Info.Valid will be false if no polling has completed. return m.infochan
func (mk2 *mk2Ser) GetMk2Info() *Mk2Info {
mk2.RLock()
defer mk2.RUnlock()
return mk2.report
} }
func (mk2 *mk2Ser) C() chan *Mk2Info { func (m *mk2Ser) readByte() byte {
return mk2.infochan
}
func (mk2 *mk2Ser) readByte() byte {
buffer := make([]byte, 1) buffer := make([]byte, 1)
_, err := io.ReadFull(mk2.p, buffer) _, err := io.ReadFull(m.p, buffer)
if err != nil { if err != nil {
mk2.addError(fmt.Errorf("Read error: %v", err)) m.addError(fmt.Errorf("Read error: %v", err))
return 0 return 0
} }
return buffer[0] return buffer[0]
} }
// Adds error to error slice. // Adds error to error slice.
func (mk2 *mk2Ser) addError(err error) { func (m *mk2Ser) addError(err error) {
if mk2.info.Errors == nil { if m.info.Errors == nil {
mk2.info.Errors = make([]error, 0) m.info.Errors = make([]error, 0)
} }
mk2.info.Errors = append(mk2.info.Errors, err) m.info.Errors = append(m.info.Errors, err)
mk2.info.Valid = false m.info.Valid = false
} }
// Updates report. // Updates report.
func (mk2 *mk2Ser) updateReport() { func (m *mk2Ser) updateReport() {
mk2.Lock() m.info.Timestamp = time.Now()
defer mk2.Unlock()
mk2.info.Timestamp = time.Now()
mk2.report = mk2.info
select { select {
case mk2.infochan <- mk2.info: case m.infochan <- m.info:
default: default:
} }
mk2.info = &Mk2Info{} m.info = &Mk2Info{}
} }
// Checks for valid frame and chooses decoding. // Checks for valid frame and chooses decoding.
func (mk2 *mk2Ser) handleFrame(l byte, frame []byte) { func (m *mk2Ser) handleFrame(l byte, frame []byte) {
if checkChecksum(l, frame[0], frame[1:]) { if checkChecksum(l, frame[0], frame[1:]) {
switch frame[0] { switch frame[0] {
case 0xff: case 0xff:
switch frame[1] { switch frame[1] {
case 0x56: // V case 0x56: // V
mk2.versionDecode(frame[2:]) m.versionDecode(frame[2:])
case 0x57: case 0x57:
switch frame[2] { switch frame[2] {
case 0x8e: case 0x8e:
mk2.scaleDecode(frame[2:]) m.scaleDecode(frame[2:])
case 0x85: case 0x85:
mk2.stateDecode(frame[2:]) m.stateDecode(frame[2:])
} }
case 0x4C: // L case 0x4C: // L
mk2.ledDecode(frame[2:]) m.ledDecode(frame[2:])
} }
case 0x20: case 0x20:
switch frame[5] { switch frame[5] {
case 0x0C: case 0x0C:
mk2.dcDecode(frame[1:]) m.dcDecode(frame[1:])
case 0x08: case 0x08:
mk2.acDecode(frame[1:]) m.acDecode(frame[1:])
} }
} }
} else { } else {
log.Printf("Failed") log.Printf("Invalid incoming frame checksum: %x", frame)
mk2.locked = false m.frameLock = false
} }
} }
// Set the target VBus device. // Set the target VBus device.
func (mk *mk2Ser) setTarget() { func (m *mk2Ser) setTarget() {
cmd := make([]byte, 3) cmd := make([]byte, 3)
cmd[0] = 0x41 // A cmd[0] = 0x41 // A
cmd[1] = 0x01 cmd[1] = 0x01
cmd[2] = 0x00 cmd[2] = 0x00
mk.sendCommand(cmd) m.sendCommand(cmd)
} }
// Request the scaling factor for entry 'in'. // Request the scaling factor for entry 'in'.
func (mk *mk2Ser) reqScaleFactor(in byte) { func (m *mk2Ser) reqScaleFactor(in byte) {
cmd := make([]byte, 4) cmd := make([]byte, 4)
cmd[0] = 0x57 // W cmd[0] = 0x57 // W
cmd[1] = 0x36 cmd[1] = 0x36
cmd[2] = in cmd[2] = in
mk.sendCommand(cmd) m.sendCommand(cmd)
} }
// Decode the scale factor frame. // Decode the scale factor frame.
func (mk *mk2Ser) scaleDecode(frame []byte) { func (m *mk2Ser) scaleDecode(frame []byte) {
scl := uint16(frame[2])<<8 + uint16(frame[1]) scl := uint16(frame[2])<<8 + uint16(frame[1])
ofs := int16(uint16(frame[5])<<8 + uint16(frame[4])) ofs := int16(uint16(frame[5])<<8 + uint16(frame[4]))
@@ -203,11 +189,11 @@ func (mk *mk2Ser) scaleDecode(frame []byte) {
} else { } else {
tmp.scale = math.Abs(float64(scl)) tmp.scale = math.Abs(float64(scl))
} }
mk.sc = append(mk.sc, tmp) m.scales = append(m.scales, tmp)
mk.scN++ m.scaleCount++
if mk.scN < 14 { if m.scaleCount < 14 {
mk.reqScaleFactor(byte(mk.scN)) m.reqScaleFactor(byte(m.scaleCount))
} else { } else {
log.Print("Monitoring starting.") log.Print("Monitoring starting.")
} }
@@ -215,28 +201,28 @@ func (mk *mk2Ser) scaleDecode(frame []byte) {
} }
// Decode the version number // Decode the version number
func (mk *mk2Ser) versionDecode(frame []byte) { func (m *mk2Ser) versionDecode(frame []byte) {
mk.info.Version = 0 m.info.Version = 0
mk.info.Valid = true m.info.Valid = true
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
mk.info.Version += uint32(frame[i]) << uint(i) * 8 m.info.Version += uint32(frame[i]) << uint(i) * 8
} }
if mk.scN < 14 { if m.scaleCount < 14 {
log.Print("Get scaling factors.") log.Print("Get scaling factors.")
mk.reqScaleFactor(byte(mk.scN)) m.reqScaleFactor(byte(m.scaleCount))
} else { } else {
// Send DC status request // Send DC status request
cmd := make([]byte, 2) cmd := make([]byte, 2)
cmd[0] = 0x46 //F cmd[0] = 0x46 //F
cmd[1] = 0 cmd[1] = 0
mk.sendCommand(cmd) m.sendCommand(cmd)
} }
} }
// Apply scaling to float // Apply scaling to float
func (mk *mk2Ser) applyScale(value float64, scale int) float64 { func (m *mk2Ser) applyScale(value float64, scale int) float64 {
return mk.sc[scale].scale * (value + mk.sc[scale].offset) return m.scales[scale].scale * (value + m.scales[scale].offset)
} }
// Convert bytes->int16->float // Convert bytes->int16->float
@@ -250,57 +236,57 @@ func getUnsigned(data []byte) float64 {
} }
// Decodes DC frame. // Decodes DC frame.
func (mk *mk2Ser) dcDecode(frame []byte) { func (m *mk2Ser) dcDecode(frame []byte) {
mk.info.BatVoltage = mk.applyScale(getSigned(frame[5:7]), 4) m.info.BatVoltage = m.applyScale(getSigned(frame[5:7]), 4)
usedC := mk.applyScale(getUnsigned(frame[7:10]), 5) usedC := m.applyScale(getUnsigned(frame[7:10]), 5)
chargeC := mk.applyScale(getUnsigned(frame[10:13]), 5) chargeC := m.applyScale(getUnsigned(frame[10:13]), 5)
mk.info.BatCurrent = usedC - chargeC m.info.BatCurrent = usedC - chargeC
mk.info.OutFrequency = 10 / (mk.applyScale(float64(frame[13]), 7)) m.info.OutFrequency = 10 / (m.applyScale(float64(frame[13]), 7))
// Send L1 status request // Send L1 status request
cmd := make([]byte, 2) cmd := make([]byte, 2)
cmd[0] = 0x46 //F cmd[0] = 0x46 //F
cmd[1] = 1 cmd[1] = 1
mk.sendCommand(cmd) m.sendCommand(cmd)
} }
// Decodes AC frame. // Decodes AC frame.
func (mk *mk2Ser) acDecode(frame []byte) { func (m *mk2Ser) acDecode(frame []byte) {
mk.info.InVoltage = mk.applyScale(getSigned(frame[5:7]), 0) m.info.InVoltage = m.applyScale(getSigned(frame[5:7]), 0)
mk.info.InCurrent = mk.applyScale(getSigned(frame[7:9]), 1) m.info.InCurrent = m.applyScale(getSigned(frame[7:9]), 1)
mk.info.OutVoltage = mk.applyScale(getSigned(frame[9:11]), 2) m.info.OutVoltage = m.applyScale(getSigned(frame[9:11]), 2)
mk.info.OutCurrent = mk.applyScale(getSigned(frame[11:13]), 3) m.info.OutCurrent = m.applyScale(getSigned(frame[11:13]), 3)
if frame[13] == 0xff { if frame[13] == 0xff {
mk.info.InFrequency = 0 m.info.InFrequency = 0
} else { } else {
mk.info.InFrequency = 10 / (mk.applyScale(float64(frame[13]), 8)) m.info.InFrequency = 10 / (m.applyScale(float64(frame[13]), 8))
} }
// Send status request // Send status request
cmd := make([]byte, 1) cmd := make([]byte, 1)
cmd[0] = 0x4C //F cmd[0] = 0x4C //F
mk.sendCommand(cmd) m.sendCommand(cmd)
} }
// Decode charge state of battery. // Decode charge state of battery.
func (mk *mk2Ser) stateDecode(frame []byte) { func (m *mk2Ser) stateDecode(frame []byte) {
mk.info.ChargeState = mk.applyScale(getSigned(frame[1:3]), 13) m.info.ChargeState = m.applyScale(getSigned(frame[1:3]), 13)
mk.updateReport() m.updateReport()
} }
// Decode the LED state frame. // Decode the LED state frame.
func (mk *mk2Ser) ledDecode(frame []byte) { func (m *mk2Ser) ledDecode(frame []byte) {
mk.info.LEDs = getLEDs(frame[0], frame[1]) m.info.LEDs = getLEDs(frame[0], frame[1])
// Send charge state request // Send charge state request
cmd := make([]byte, 4) cmd := make([]byte, 4)
cmd[0] = 0x57 //W cmd[0] = 0x57 //W
cmd[1] = 0x30 cmd[1] = 0x30
cmd[2] = 13 cmd[2] = 13
mk.sendCommand(cmd) m.sendCommand(cmd)
} }
// Adds active LEDs to list. // Adds active LEDs to list.
@@ -322,7 +308,7 @@ func getLEDs(ledsOn, ledsBlink byte) map[Led]LEDstate {
} }
// Adds header and trailing crc for frame to send. // Adds header and trailing crc for frame to send.
func (mk2 *mk2Ser) sendCommand(data []byte) { func (m *mk2Ser) sendCommand(data []byte) {
l := len(data) l := len(data)
dataOut := make([]byte, l+3) dataOut := make([]byte, l+3)
dataOut[0] = byte(l + 1) dataOut[0] = byte(l + 1)
@@ -334,9 +320,9 @@ func (mk2 *mk2Ser) sendCommand(data []byte) {
} }
dataOut[l+2] = cr dataOut[l+2] = cr
_, err := mk2.p.Write(dataOut) _, err := m.p.Write(dataOut)
if err != nil { if err != nil {
mk2.addError(fmt.Errorf("Write error: %v", err)) m.addError(fmt.Errorf("Write error: %v", err))
} }
} }
@@ -346,8 +332,5 @@ func checkChecksum(l, t byte, d []byte) bool {
for i := 0; i < len(d); i++ { for i := 0; i < len(d); i++ {
cr = (cr + uint16(d[i])) % 256 cr = (cr + uint16(d[i])) % 256
} }
if cr == 0 { return cr == 0
return true
}
return false
} }

View File

@@ -73,7 +73,6 @@ type Mk2Info struct {
} }
type Mk2If interface { type Mk2If interface {
GetMk2Info() *Mk2Info
C() chan *Mk2Info C() chan *Mk2Info
Close() Close()
} }

71
mk2if/mockmk2.go Normal file
View File

@@ -0,0 +1,71 @@
package mk2if
import (
"fmt"
"time"
)
type mock struct {
c chan *Mk2Info
}
func NewMk2Mock() Mk2If {
tmp := &mock{
c: make(chan *Mk2Info, 1),
}
go tmp.genMockValues()
return tmp
}
func genBaseLeds(state LEDstate) map[Led]LEDstate {
return map[Led]LEDstate{
LedMain: state,
LedAbsorption: state,
LedBulk: state,
LedFloat: state,
LedInverter: state,
LedOverload: state,
LedLowBattery: state,
LedTemperature: state,
}
}
func (m *mock) C() chan *Mk2Info {
return m.c
}
func (m *mock) Close() {
}
func (m *mock) genMockValues() {
mult := 1.0
ledState := LedOff
for {
input := &Mk2Info{
OutCurrent: 2.0 * mult,
InCurrent: 2.3 * mult,
OutVoltage: 230.0 * mult,
InVoltage: 230.1 * mult,
BatVoltage: 25 * mult,
BatCurrent: -10 * mult,
InFrequency: 50 * mult,
OutFrequency: 50 * mult,
ChargeState: 1 * mult,
Errors: nil,
Timestamp: time.Now(),
Valid: true,
LEDs: genBaseLeds(ledState),
}
ledState = (ledState + 1) % 3
mult = mult - 0.1
if mult < 0 {
mult = 1.0
}
fmt.Printf("Sending\n")
m.c <- input
time.Sleep(1 * time.Second)
}
}

View File

@@ -33,8 +33,9 @@ package webgui
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/hpdvanwyk/invertergui/mk2if"
"net/http" "net/http"
"github.com/hpdvanwyk/invertergui/mk2if"
) )
type muninData struct { type muninData struct {
@@ -46,7 +47,7 @@ func (w *WebGui) ServeMuninHTTP(rw http.ResponseWriter, r *http.Request) {
muninDat := <-w.muninRespChan muninDat := <-w.muninRespChan
if muninDat.timesUpdated == 0 { if muninDat.timesUpdated == 0 {
rw.WriteHeader(500) rw.WriteHeader(500)
rw.Write([]byte("No data to return.\n")) _, _ = rw.Write([]byte("No data to return.\n"))
return return
} }
calcMuninAverages(&muninDat) calcMuninAverages(&muninDat)
@@ -75,7 +76,7 @@ func (w *WebGui) ServeMuninHTTP(rw http.ResponseWriter, r *http.Request) {
fmt.Fprintf(outputBuf, "freqin.value %s\n", tmpInput.InFreq) fmt.Fprintf(outputBuf, "freqin.value %s\n", tmpInput.InFreq)
fmt.Fprintf(outputBuf, "freqout.value %s\n", tmpInput.OutFreq) fmt.Fprintf(outputBuf, "freqout.value %s\n", tmpInput.OutFreq)
_, err := rw.Write([]byte(outputBuf.String())) _, err := rw.Write(outputBuf.Bytes())
if err != nil { if err != nil {
fmt.Printf("%v\n", err) fmt.Printf("%v\n", err)
} }
@@ -171,7 +172,7 @@ freqout.label Out frequency (Hz)
//Munin only samples once every 5 minutes so averages have to be calculated for some values. //Munin only samples once every 5 minutes so averages have to be calculated for some values.
func calcMuninValues(muninDat *muninData, newStatus *mk2if.Mk2Info) { func calcMuninValues(muninDat *muninData, newStatus *mk2if.Mk2Info) {
muninDat.timesUpdated += 1 muninDat.timesUpdated++
muninVal := &muninDat.status muninVal := &muninDat.status
muninVal.OutCurrent += newStatus.OutCurrent muninVal.OutCurrent += newStatus.OutCurrent
muninVal.InCurrent += newStatus.InCurrent muninVal.InCurrent += newStatus.InCurrent

View File

@@ -32,6 +32,7 @@ package webgui
import ( import (
"fmt" "fmt"
"log"
"net/http" "net/http"
"sync" "sync"
"time" "time"
@@ -49,7 +50,6 @@ const (
) )
type WebGui struct { type WebGui struct {
respChan chan *mk2if.Mk2Info
stopChan chan struct{} stopChan chan struct{}
muninRespChan chan muninData muninRespChan chan muninData
@@ -178,14 +178,16 @@ func (w *WebGui) Stop() {
func (w *WebGui) dataPoll() { func (w *WebGui) dataPoll() {
pollChan := w.poller.C() pollChan := w.poller.C()
var muninValues muninData var muninValues muninData
s := &mk2if.Mk2Info{}
for { for {
select { select {
case s = <-pollChan: case s := <-pollChan:
if s.Valid { if s.Valid {
calcMuninValues(&muninValues, s) calcMuninValues(&muninValues, s)
w.pu.updatePrometheus(s) w.pu.updatePrometheus(s)
w.hub.Broadcast(buildTemplateInput(s)) err := w.hub.Broadcast(buildTemplateInput(s))
if err != nil {
log.Printf("Could not send update to clients: %v", err)
}
} }
case w.muninRespChan <- muninValues: case w.muninRespChan <- muninValues:
zeroMuninValues(&muninValues) zeroMuninValues(&muninValues)

View File

@@ -32,10 +32,11 @@ package webgui
import ( import (
"fmt" "fmt"
"github.com/hpdvanwyk/invertergui/mk2if"
"reflect" "reflect"
"testing" "testing"
"time" "time"
"github.com/hpdvanwyk/invertergui/mk2if"
) )
func TestWebGui(t *testing.T) { func TestWebGui(t *testing.T) {
@@ -61,27 +62,28 @@ var templateInputTests = []templateTest{
InFrequency: 50, InFrequency: 50,
OutFrequency: 50, OutFrequency: 50,
ChargeState: 1, ChargeState: 1,
LedListOn: []int{mk2if.LED_MAIN, mk2if.LED_FLOAT}, LEDs: map[mk2if.Led]mk2if.LEDstate{mk2if.LedMain: mk2if.LedOn},
Errors: nil, Errors: nil,
Timestamp: fakenow, Timestamp: fakenow,
}, },
output: &templateInput{ output: &templateInput{
Error: nil, Error: nil,
Date: fakenow.Format(time.RFC1123Z), Date: fakenow.Format(time.RFC1123Z),
OutCurrent: "2.000", OutCurrent: "2.00",
OutVoltage: "230.000", OutVoltage: "230.00",
OutPower: "460.000", OutPower: "460.00",
InCurrent: "2.300", InCurrent: "2.30",
InVoltage: "230.100", InVoltage: "230.10",
InPower: "529.230", InPower: "529.23",
InMinOut: "69.230", InMinOut: "69.23",
BatVoltage: "25.000", BatVoltage: "25.00",
BatCurrent: "-10.000", BatCurrent: "-10.00",
BatPower: "-250.000", BatPower: "-250.00",
InFreq: "50.000", InFreq: "50.00",
OutFreq: "50.000", OutFreq: "50.00",
BatCharge: "100.000", BatCharge: "100.00",
Leds: []string{"Mains", "Float"}}, LedMap: map[string]string{"led_mains": "dot-green"},
},
}, },
} }

View File

@@ -17,9 +17,6 @@ const (
// Send pings to peer with this period. Must be less than pongWait. // Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10 pingPeriod = (pongWait * 9) / 10
// Maximum message size allowed from peer.
maxMessageSize = 512
) )
var upgrader = websocket.Upgrader{ var upgrader = websocket.Upgrader{
@@ -52,10 +49,10 @@ func (c *Client) writePump() {
for { for {
select { select {
case message, ok := <-c.send: case message, ok := <-c.send:
c.conn.SetWriteDeadline(time.Now().Add(writeWait)) _ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if !ok { if !ok {
// The hub closed the channel. // The hub closed the channel.
c.conn.WriteMessage(websocket.CloseMessage, []byte{}) _ = c.conn.WriteMessage(websocket.CloseMessage, []byte{})
return return
} }
@@ -63,13 +60,13 @@ func (c *Client) writePump() {
if err != nil { if err != nil {
return return
} }
w.Write(message) _, _ = w.Write(message)
if err := w.Close(); err != nil { if err := w.Close(); err != nil {
return return
} }
case <-ticker.C: case <-ticker.C:
c.conn.SetWriteDeadline(time.Now().Add(writeWait)) _ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return return
} }

View File

@@ -36,7 +36,7 @@ func (h *Hub) Broadcast(message interface{}) error {
if err != nil { if err != nil {
return err return err
} }
h.broadcast <- []byte(payload) h.broadcast <- payload
return nil return nil
} }