11 Commits

Author SHA1 Message Date
Nicholas Thompson
4a72d24cdd Plugin grouping to make reading the code easier 2019-03-31 19:57:55 +02:00
Nicholas Thompson
92daf9191b Moved frontend package to be part of webgui in plugin format and renamed it to static for a better description of the package 2019-03-31 19:57:55 +02:00
Nicholas Thompson
fd49891632 Add codecov to test 2019-03-31 19:57:55 +02:00
Nicholas Thompson
e74b0518e9 Remove old invalid test 2019-03-31 19:57:55 +02:00
Nicholas Thompson
0b324458f0 Added basic test to mk2driver to test know state. 2019-03-31 19:57:55 +02:00
Nicholas Thompson
8b0b4f64f1 Simplyfy the flags to specify a source 2019-03-31 19:57:55 +02:00
Nicholas Thompson
47e73a4eff Concept plugin layout 2019-03-31 19:57:55 +02:00
Nicholas Thompson
d02de285d9 Changed the naming of mk2if to mk2driver 2019-03-31 19:57:55 +02:00
Nicholas Thompson
9236d6fa86 Add travis-ci badge for master 2019-03-15 11:30:47 +02:00
Nicholas Thompson
acdaa019cb Add travis-ci file 2019-03-15 11:30:47 +02:00
Jedri Visser
01ce2da533 Show sparklines on Grafana dashboard 2019-03-14 23:02:27 +02:00
28 changed files with 591 additions and 333 deletions

28
.travis.yml Normal file
View File

@@ -0,0 +1,28 @@
sudo: false
language: go
env:
- GO111MODULE=on
go:
- 1.12.x
git:
depth: 1
install: true
notifications:
email: false
before_script:
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.15.0
- go mod vendor
script:
- golangci-lint run
- go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@@ -26,21 +26,13 @@
#OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
#OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.PHONY: test test-race vet install gofmt docker statik lint clean guimock invertergui invertercli build
.PHONY: test test-race vet install gofmt docker statik lint clean invertergui
.DEFAULT_GOAL = build
guimock:
go build ./cmd/guimock/
.DEFAULT_GOAL = invertergui
invertergui:
go build ./cmd/invertergui/
invertercli:
go build ./cmd/invertercli/
build: guimock invertergui invertercli
all: build gofmt test
gofmt:
@@ -62,4 +54,4 @@ lint:
golangci-lint run
clean:
rm ./guimock ./invertercli ./invertergui
rm ./invertergui

View File

@@ -1,5 +1,7 @@
# Inverter GUI
[![Build Status](https://travis-ci.org/diebietse/invertergui.svg?branch=master)](https://travis-ci.org/diebietse/invertergui)
The invertergui allows the monitoring of a [Victron Multiplus](https://www.victronenergy.com/inverters-chargers/multiplus-12v-24v-48v-800va-3kva) via the [MK3/MK2 USB](https://www.victronenergy.com/accessories/interface-mk3-usb) or the MK2 RS232.
The [`diebietse/invertergui`](https://hub.docker.com/r/diebietse/invertergui) docker image is a build of this repository.

View File

@@ -1,29 +0,0 @@
package main
import (
"flag"
"log"
"net/http"
"github.com/diebietse/invertergui/frontend"
"github.com/diebietse/invertergui/mk2if"
"github.com/diebietse/invertergui/webgui"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
addr := flag.String("addr", ":8080", "TCP address to listen on.")
flag.Parse()
mk2 := mk2if.NewMk2Mock()
gui := webgui.NewWebGui(mk2)
http.Handle("/", frontend.NewStatic())
http.Handle("/ws", http.HandlerFunc(gui.ServeHub))
http.Handle("/munin", http.HandlerFunc(gui.ServeMuninHTTP))
http.Handle("/muninconfig", http.HandlerFunc(gui.ServeMuninConfigHTTP))
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(*addr, nil))
}

View File

@@ -1,88 +0,0 @@
package main
import (
"flag"
"fmt"
"io"
"log"
"net"
"os"
"os/signal"
"syscall"
"github.com/diebietse/invertergui/mk2if"
"github.com/tarm/serial"
)
// Basic CLI to serve as example lib usage
func main() {
//Info = log.New()
tcp := flag.Bool("tcp", false, "Use TCP instead of TTY")
ip := flag.String("ip", "localhost:8139", "IP to connect when using tcp connection.")
dev := flag.String("dev", "/dev/ttyUSB0", "TTY device to use.")
flag.Parse()
var p io.ReadWriteCloser
var err error
var tcpAddr *net.TCPAddr
if *tcp {
tcpAddr, err = net.ResolveTCPAddr("tcp", *ip)
if err != nil {
panic(err)
}
p, err = net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
panic(err)
}
} else {
serialConfig := &serial.Config{Name: *dev, Baud: 2400}
p, err = serial.OpenPort(serialConfig)
if err != nil {
panic(err)
}
}
defer p.Close()
mk2, err := mk2if.NewMk2Connection(p)
if err != nil {
panic(err)
}
defer mk2.Close()
c := mk2.C()
sigterm := make(chan os.Signal, 1)
signal.Notify(sigterm, syscall.SIGTERM, os.Interrupt)
mainloop:
for {
select {
case tmp := <-c:
if tmp.Valid {
PrintInfo(tmp)
}
case <-sigterm:
break mainloop
}
}
log.Printf("Closing connection")
}
func PrintInfo(info *mk2if.Mk2Info) {
out := fmt.Sprintf("Version: %v\n", info.Version)
out += fmt.Sprintf("Bat Volt: %.2fV Bat Cur: %.2fA \n", info.BatVoltage, info.BatCurrent)
out += fmt.Sprintf("In Volt: %.2fV In Cur: %.2fA In Freq %.2fHz\n", info.InVoltage, info.InCurrent, info.InFrequency)
out += fmt.Sprintf("Out Volt: %.2fV Out Cur: %.2fA Out Freq %.2fHz\n", info.OutVoltage, info.OutCurrent, info.OutFrequency)
out += fmt.Sprintf("In Power %.2fW Out Power %.2fW\n", info.InVoltage*info.InCurrent, info.OutVoltage*info.OutCurrent)
out += fmt.Sprintf("Charge State: %.2f%%\n", info.ChargeState*100)
out += "LEDs state:"
for k, v := range info.LEDs {
out += fmt.Sprintf(" %s %s", mk2if.LedNames[k], mk2if.StateNames[v])
}
out += "\nErrors:"
for _, v := range info.Errors {
out += " " + v.Error()
}
out += "\n"
log.Printf("System Info: \n%v", out)
}

View File

@@ -36,28 +36,67 @@ import (
"log"
"net"
"net/http"
"os"
"github.com/diebietse/invertergui/frontend"
"github.com/diebietse/invertergui/mk2if"
"github.com/diebietse/invertergui/webgui"
"github.com/diebietse/invertergui/mk2core"
"github.com/diebietse/invertergui/mk2driver"
"github.com/diebietse/invertergui/plugins/cli"
"github.com/diebietse/invertergui/plugins/munin"
"github.com/diebietse/invertergui/plugins/prometheus"
"github.com/diebietse/invertergui/plugins/webui"
"github.com/diebietse/invertergui/plugins/webui/static"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/tarm/serial"
)
func main() {
source := flag.String("source", "serial", "Set the source of data for the inverter gui. \"serial\", \"tcp\" or \"mock\"")
addr := flag.String("addr", ":8080", "TCP address to listen on.")
tcp := flag.Bool("tcp", false, "Use TCP instead of TTY")
ip := flag.String("ip", "localhost:8139", "IP to connect when using tcp connection.")
dev := flag.String("dev", "/dev/ttyUSB0", "TTY device to use.")
cliEnable := flag.Bool("cli", false, "Enable CLI output")
flag.Parse()
mk2 := getMk2Device(*source, *ip, *dev)
defer mk2.Close()
core := mk2core.NewCore(mk2)
if *cliEnable {
cli.NewCli(core.NewSubscription())
}
// Webgui
gui := webui.NewWebGui(core.NewSubscription())
http.Handle("/", static.New())
http.Handle("/ws", http.HandlerFunc(gui.ServeHub))
// Munin
mu := munin.NewMunin(core.NewSubscription())
http.Handle("/munin", http.HandlerFunc(mu.ServeMuninHTTP))
http.Handle("/muninconfig", http.HandlerFunc(mu.ServeMuninConfigHTTP))
// Prometheus
prometheus.NewPrometheus(core.NewSubscription())
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(*addr, nil))
}
func getMk2Device(source, ip, dev string) mk2driver.Mk2 {
var p io.ReadWriteCloser
var err error
var tcpAddr *net.TCPAddr
if *tcp {
tcpAddr, err = net.ResolveTCPAddr("tcp", *ip)
switch source {
case "serial":
serialConfig := &serial.Config{Name: dev, Baud: 2400}
p, err = serial.OpenPort(serialConfig)
if err != nil {
panic(err)
}
case "tcp":
tcpAddr, err = net.ResolveTCPAddr("tcp", ip)
if err != nil {
panic(err)
}
@@ -65,26 +104,17 @@ func main() {
if err != nil {
panic(err)
}
} else {
serialConfig := &serial.Config{Name: *dev, Baud: 2400}
p, err = serial.OpenPort(serialConfig)
case "mock":
return mk2driver.NewMk2Mock()
default:
log.Printf("Invalid source selection: %v\nUse \"serial\", \"tcp\" or \"mock\"", source)
os.Exit(1)
}
mk2, err := mk2driver.NewMk2Connection(p)
if err != nil {
panic(err)
}
}
defer p.Close()
mk2, err := mk2if.NewMk2Connection(p)
if err != nil {
panic(err)
}
defer mk2.Close()
gui := webgui.NewWebGui(mk2)
http.Handle("/", frontend.NewStatic())
http.Handle("/ws", http.HandlerFunc(gui.ServeHub))
http.Handle("/munin", http.HandlerFunc(gui.ServeMuninHTTP))
http.Handle("/muninconfig", http.HandlerFunc(gui.ServeMuninConfigHTTP))
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(*addr, nil))
return mk2
}

1
go.mod
View File

@@ -4,6 +4,7 @@ require (
github.com/gorilla/websocket v1.4.0
github.com/prometheus/client_golang v0.9.2
github.com/rakyll/statik v0.1.5
github.com/stretchr/testify v1.3.0
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
golang.org/x/sys v0.0.0-20190312061237-fead79001313 // indirect
)

7
go.sum
View File

@@ -1,11 +1,15 @@
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
@@ -16,6 +20,9 @@ github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nL
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rakyll/statik v0.1.5 h1:Ly2UjURzxnsSYS0zI50fZ+srA+Fu7EbpV5hglvJvJG0=
github.com/rakyll/statik v0.1.5/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

View File

@@ -94,7 +94,7 @@
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": false
"show": true
},
"tableColumn": "",
"targets": [
@@ -176,7 +176,7 @@
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": false
"show": true
},
"tableColumn": "",
"targets": [
@@ -258,7 +258,7 @@
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": false
"show": true
},
"tableColumn": "",
"targets": [
@@ -339,7 +339,7 @@
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": false
"show": true
},
"tableColumn": "",
"targets": [
@@ -420,7 +420,7 @@
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": false
"show": true
},
"tableColumn": "",
"targets": [
@@ -501,7 +501,7 @@
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": false
"show": true
},
"tableColumn": "",
"targets": [
@@ -1345,5 +1345,5 @@
"timezone": "",
"title": "Victron",
"uid": "000000004",
"version": 1
"version": 2
}

57
mk2core/core.go Normal file
View File

@@ -0,0 +1,57 @@
package mk2core
import (
"github.com/diebietse/invertergui/mk2driver"
)
type Core struct {
mk2driver.Mk2
plugins map[*subscription]bool
register chan *subscription
}
func NewCore(m mk2driver.Mk2) *Core {
core := &Core{
Mk2: m,
register: make(chan *subscription, 255),
plugins: map[*subscription]bool{},
}
go core.run()
return core
}
func (c *Core) NewSubscription() mk2driver.Mk2 {
sub := &subscription{
send: make(chan *mk2driver.Mk2Info),
}
c.register <- sub
return sub
}
func (c *Core) run() {
for {
select {
case r := <-c.register:
c.plugins[r] = true
case e := <-c.C():
for plugin := range c.plugins {
select {
case plugin.send <- e:
default:
}
}
}
}
}
type subscription struct {
send chan *mk2driver.Mk2Info
}
func (s *subscription) C() chan *mk2driver.Mk2Info {
return s.send
}
func (s *subscription) Close() {
close(s.send)
}

View File

@@ -1,4 +1,4 @@
package mk2if
package mk2driver
import (
"errors"
@@ -26,7 +26,7 @@ type mk2Ser struct {
wg sync.WaitGroup
}
func NewMk2Connection(dev io.ReadWriter) (Mk2If, error) {
func NewMk2Connection(dev io.ReadWriter) (Mk2, error) {
mk2 := &mk2Ser{}
mk2.p = dev
mk2.info = &Mk2Info{}

141
mk2driver/mk2_test.go Normal file
View File

@@ -0,0 +1,141 @@
/**
write out: []byte{0x04, 0xff, 0x41, 0x01, 0x00, 0xbb, }
read byte: []byte{0x04, }
read byte: []byte{0xff, }
read unlocked: []byte{0x41, 0x01, 0x00, 0xbb, }
2019/03/17 16:24:17 Locked
write out: []byte{0x04, 0xff, 0x41, 0x01, 0x00, 0xbb, }
write out: []byte{0x05, 0xff, 0x57, 0x36, 0x00, 0x00, 0x6f, }
write out: []byte{0x05, 0xff, 0x57, 0x36, 0x01, 0x00, 0x6e, }
write out: []byte{0x05, 0xff, 0x57, 0x36, 0x02, 0x00, 0x6d, }
write out: []byte{0x05, 0xff, 0x57, 0x36, 0x03, 0x00, 0x6c, }
write out: []byte{0x05, 0xff, 0x57, 0x36, 0x04, 0x00, 0x6b, }
write out: []byte{0x05, 0xff, 0x57, 0x36, 0x05, 0x00, 0x6a, }
write out: []byte{0x05, 0xff, 0x57, 0x36, 0x06, 0x00, 0x69, }
write out: []byte{0x05, 0xff, 0x57, 0x36, 0x07, 0x00, 0x68, }
write out: []byte{0x05, 0xff, 0x57, 0x36, 0x08, 0x00, 0x67, }
write out: []byte{0x05, 0xff, 0x57, 0x36, 0x09, 0x00, 0x66, }
write out: []byte{0x05, 0xff, 0x57, 0x36, 0x0a, 0x00, 0x65, }
write out: []byte{0x05, 0xff, 0x57, 0x36, 0x0b, 0x00, 0x64, }
write out: []byte{0x05, 0xff, 0x57, 0x36, 0x0c, 0x00, 0x63, }
write out: []byte{0x05, 0xff, 0x57, 0x36, 0x0d, 0x00, 0x62, }
write out: []byte{0x03, 0xff, 0x46, 0x00, 0xb8, }
write out: []byte{0x03, 0xff, 0x46, 0x01, 0xb7, }
write out: []byte{0x02, 0xff, 0x4c, 0xb3, }
write out: []byte{0x05, 0xff, 0x57, 0x30, 0x0d, 0x00, 0x68, }
write out: []byte{0x03, 0xff, 0x46, 0x00, 0xb8, }
write out: []byte{0x03, 0xff, 0x46, 0x01, 0xb7, }
write out: []byte{0x02, 0xff, 0x4c, 0xb3, }
write out: []byte{0x05, 0xff, 0x57, 0x30, 0x0d, 0x00, 0x68, }
*/
package mk2driver_test
import (
"bytes"
"io"
"testing"
"github.com/diebietse/invertergui/mk2driver"
"github.com/stretchr/testify/assert"
)
var knownWrites = []byte{
0x04, 0xff, 0x41, 0x01, 0x00, 0xbb,
0x05, 0xff, 0x57, 0x36, 0x00, 0x00, 0x6f,
0x05, 0xff, 0x57, 0x36, 0x01, 0x00, 0x6e,
0x05, 0xff, 0x57, 0x36, 0x02, 0x00, 0x6d,
0x05, 0xff, 0x57, 0x36, 0x03, 0x00, 0x6c,
0x05, 0xff, 0x57, 0x36, 0x04, 0x00, 0x6b,
0x05, 0xff, 0x57, 0x36, 0x05, 0x00, 0x6a,
0x05, 0xff, 0x57, 0x36, 0x06, 0x00, 0x69,
0x05, 0xff, 0x57, 0x36, 0x07, 0x00, 0x68,
0x05, 0xff, 0x57, 0x36, 0x08, 0x00, 0x67,
0x05, 0xff, 0x57, 0x36, 0x09, 0x00, 0x66,
0x05, 0xff, 0x57, 0x36, 0x0a, 0x00, 0x65,
0x05, 0xff, 0x57, 0x36, 0x0b, 0x00, 0x64,
0x05, 0xff, 0x57, 0x36, 0x0c, 0x00, 0x63,
0x05, 0xff, 0x57, 0x36, 0x0d, 0x00, 0x62,
0x03, 0xff, 0x46, 0x00, 0xb8,
0x03, 0xff, 0x46, 0x01, 0xb7,
0x02, 0xff, 0x4c, 0xb3,
0x05, 0xff, 0x57, 0x30, 0x0d, 0x00, 0x68,
}
var writeBuffer = bytes.NewBuffer(nil)
type testIo struct {
io.Reader
io.Writer
}
func NewIOStub(readBuffer []byte) io.ReadWriter {
return &testIo{
Reader: bytes.NewBuffer(readBuffer),
Writer: writeBuffer,
}
}
// Test a know sequence as reference as extracted from Mk2
func TestSync(t *testing.T) {
knownReadBuffer := []byte{
//Len Cmd
0x04, 0xff, 0x41, 0x01, 0x00, 0xbb, 0x07, 0xff, 0x56, 0x96, 0x3e, 0x11, 0x00, 0x00, 0xbf,
0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a,
0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x00, 0x00, 0xa1,
0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a,
0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a,
0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a,
0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x00, 0x00, 0xa1,
0x08, 0xff, 0x57, 0x8e, 0x9c, 0x7f, 0x8f, 0x00, 0x00, 0x6a,
0x08, 0xff, 0x57, 0x8e, 0x57, 0x78, 0x8f, 0x00, 0x01, 0xb5,
0x08, 0xff, 0x57, 0x8e, 0x2f, 0x7c, 0x8f, 0x00, 0x00, 0xda,
0x08, 0xff, 0x57, 0x8e, 0x64, 0x80, 0x8f, 0x00, 0x00, 0xa1,
0x08, 0xff, 0x57, 0x8e, 0x04, 0x00, 0x8f, 0x00, 0x80, 0x01,
0x08, 0xff, 0x57, 0x8e, 0x01, 0x00, 0x8f, 0x00, 0x80, 0x04,
0x08, 0xff, 0x57, 0x8e, 0x02, 0x00, 0x8f, 0x00, 0x80, 0x03,
0x08, 0xff, 0x57, 0x8e, 0x38, 0x7f, 0x8f, 0x00, 0x00, 0xce,
0x07, 0xff, 0x56, 0x96, 0x3e, 0x11, 0x00, 0x00, 0xbf,
0x0f, 0x20, 0xf3, 0x00, 0xc8, 0x02, 0x0c, 0xa1, 0x05, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x88, 0xb2,
0x0f, 0x20, 0x01, 0x01, 0xca, 0x09, 0x08, 0xaa, 0x58, 0xab, 0x00, 0xaa, 0x58, 0x9a, 0x00, 0xc3, 0xe8,
0x06, 0xff, 0x4c, 0x03, 0x00, 0x00, 0x00, 0xac,
0x05, 0xff, 0x57, 0x85, 0xc8, 0x00, 0x58,
}
expectedLEDs := map[mk2driver.Led]mk2driver.LEDstate{
mk2driver.LedMain: mk2driver.LedOn,
mk2driver.LedAbsorption: mk2driver.LedOn,
mk2driver.LedBulk: mk2driver.LedOff,
mk2driver.LedFloat: mk2driver.LedOff,
mk2driver.LedInverter: mk2driver.LedOff,
mk2driver.LedOverload: mk2driver.LedOff,
mk2driver.LedLowBattery: mk2driver.LedOff,
mk2driver.LedTemperature: mk2driver.LedOff,
}
testIO := NewIOStub(knownReadBuffer)
mk2, err := mk2driver.NewMk2Connection(testIO)
assert.NoError(t, err, "Could not open MK2")
event := <-mk2.C()
mk2.Close()
assert.Equal(t, 0, bytes.Compare(writeBuffer.Bytes(), knownWrites), "Expected writes did not match received writes")
assert.True(t, event.Valid, "data not valid")
assert.Equal(t, uint32(2736), event.Version, "Invalid version decoded")
assert.Equal(t, 0, len(event.Errors), "Reported errors not empty")
assert.Equal(t, expectedLEDs, event.LEDs, "Reported LEDs incorrect")
epsilon := 0.00000001
assert.InEpsilon(t, 14.41, event.BatVoltage, epsilon, "BatVoltage conversion failed")
assert.InEpsilon(t, -0.4, event.BatCurrent, epsilon, "BatCurrent conversion failed")
assert.InEpsilon(t, 226.98, event.InVoltage, epsilon, "InVoltage conversion failed")
assert.InEpsilon(t, 1.71, event.InCurrent, epsilon, "InCurrent conversion failed")
assert.InEpsilon(t, 50.10256410256411, event.InFrequency, epsilon, "InFrequency conversion failed")
assert.InEpsilon(t, 226.980, event.OutVoltage, epsilon, "OutVoltage conversion failed")
assert.InEpsilon(t, 1.54, event.OutCurrent, epsilon, "OutCurrent conversion failed")
assert.InEpsilon(t, 50.025510204081634, event.OutFrequency, epsilon, "OutFrequency conversion failed")
assert.InEpsilon(t, 1, event.ChargeState, epsilon, "ChargeState conversion failed")
}

View File

@@ -1,4 +1,4 @@
package mk2if
package mk2driver
import "time"
@@ -72,7 +72,7 @@ type Mk2Info struct {
Timestamp time.Time
}
type Mk2If interface {
type Mk2 interface {
C() chan *Mk2Info
Close()
}

View File

@@ -1,7 +1,6 @@
package mk2if
package mk2driver
import (
"fmt"
"time"
)
@@ -9,7 +8,7 @@ type mock struct {
c chan *Mk2Info
}
func NewMk2Mock() Mk2If {
func NewMk2Mock() Mk2 {
tmp := &mock{
c: make(chan *Mk2Info, 1),
}
@@ -64,7 +63,6 @@ func (m *mock) genMockValues() {
if mult < 0 {
mult = 1.0
}
fmt.Printf("Sending\n")
m.c <- input
time.Sleep(1 * time.Second)
}

47
plugins/cli/cli.go Normal file
View File

@@ -0,0 +1,47 @@
package cli
import (
"fmt"
"log"
"github.com/diebietse/invertergui/mk2driver"
)
type Cli struct {
mk2driver.Mk2
}
func NewCli(mk2 mk2driver.Mk2) {
newCli := &Cli{
Mk2: mk2,
}
go newCli.run()
}
func (c *Cli) run() {
for e := range c.C() {
if e.Valid {
printInfo(e)
}
}
}
func printInfo(info *mk2driver.Mk2Info) {
out := fmt.Sprintf("Version: %v\n", info.Version)
out += fmt.Sprintf("Bat Volt: %.2fV Bat Cur: %.2fA \n", info.BatVoltage, info.BatCurrent)
out += fmt.Sprintf("In Volt: %.2fV In Cur: %.2fA In Freq %.2fHz\n", info.InVoltage, info.InCurrent, info.InFrequency)
out += fmt.Sprintf("Out Volt: %.2fV Out Cur: %.2fA Out Freq %.2fHz\n", info.OutVoltage, info.OutCurrent, info.OutFrequency)
out += fmt.Sprintf("In Power %.2fW Out Power %.2fW\n", info.InVoltage*info.InCurrent, info.OutVoltage*info.OutCurrent)
out += fmt.Sprintf("Charge State: %.2f%%\n", info.ChargeState*100)
out += "LEDs state:"
for k, v := range info.LEDs {
out += fmt.Sprintf(" %s %s", mk2driver.LedNames[k], mk2driver.StateNames[v])
}
out += "\nErrors:"
for _, v := range info.Errors {
out += " " + v.Error()
}
out += "\n"
log.Printf("System Info: \n%v", out)
}

View File

@@ -28,32 +28,49 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package webgui
package munin
import (
"bytes"
"fmt"
"net/http"
"time"
"github.com/diebietse/invertergui/mk2if"
"github.com/diebietse/invertergui/mk2driver"
)
type Munin struct {
mk2driver.Mk2
muninResponse chan *muninData
}
type muninData struct {
status mk2if.Mk2Info
status *mk2driver.Mk2Info
timesUpdated int
}
func (w *WebGui) ServeMuninHTTP(rw http.ResponseWriter, r *http.Request) {
muninDat := <-w.muninRespChan
func NewMunin(mk2 mk2driver.Mk2) *Munin {
m := &Munin{
Mk2: mk2,
muninResponse: make(chan *muninData),
}
go m.run()
return m
}
func (m *Munin) ServeMuninHTTP(rw http.ResponseWriter, r *http.Request) {
muninDat := <-m.muninResponse
if muninDat.timesUpdated == 0 {
rw.WriteHeader(500)
_, _ = rw.Write([]byte("No data to return.\n"))
return
}
calcMuninAverages(&muninDat)
calcMuninAverages(muninDat)
status := muninDat.status
tmpInput := buildTemplateInput(&status)
tmpInput := buildTemplateInput(status)
outputBuf := &bytes.Buffer{}
fmt.Fprintf(outputBuf, "multigraph in_batvolt\n")
fmt.Fprintf(outputBuf, "volt.value %s\n", tmpInput.BatVoltage)
@@ -82,98 +99,35 @@ func (w *WebGui) ServeMuninHTTP(rw http.ResponseWriter, r *http.Request) {
}
}
func (w *WebGui) ServeMuninConfigHTTP(rw http.ResponseWriter, r *http.Request) {
output := `multigraph in_batvolt
graph_title Battery Voltage
graph_vlabel Voltage (V)
graph_category inverter
graph_info Battery voltage
volt.info Voltage of battery
volt.label Voltage of battery (V)
multigraph in_batcharge
graph_title Battery Charge
graph_vlabel Charge (%)
graph_category inverter
graph_info Battery charge
charge.info Estimated charge of battery
charge.label Battery charge (%)
multigraph in_batcurrent
graph_title Battery Current
graph_vlabel Current (A)
graph_category inverter
graph_info Battery current
current.info Battery current
current.label Battery current (A)
multigraph in_batpower
graph_title Battery Power
graph_vlabel Power (W)
graph_category inverter
graph_info Battery power
power.info Battery power
power.label Battery power (W)
multigraph in_mainscurrent
graph_title Mains Current
graph_vlabel Current (A)
graph_category inverter
graph_info Mains current
currentin.info Input current
currentin.label Input current (A)
currentout.info Output current
currentout.label Output current (A)
multigraph in_mainsvoltage
graph_title Mains Voltage
graph_vlabel Voltage (V)
graph_category inverter
graph_info Mains voltage
voltagein.info Input voltage
voltagein.label Input voltage (V)
voltageout.info Output voltage
voltageout.label Output voltage (V)
multigraph in_mainspower
graph_title Mains Power
graph_vlabel Power (VA)
graph_category inverter
graph_info Mains power
powerin.info Input power
powerin.label Input power (VA)
powerout.info Output power
powerout.label Output power (VA)
multigraph in_mainsfreq
graph_title Mains frequency
graph_vlabel Frequency (Hz)
graph_category inverter
graph_info Mains frequency
freqin.info In frequency
freqin.label In frequency (Hz)
freqout.info Out frequency
freqout.label Out frequency (Hz)
`
func (m *Munin) ServeMuninConfigHTTP(rw http.ResponseWriter, r *http.Request) {
output := muninConfig
_, err := rw.Write([]byte(output))
if err != nil {
fmt.Printf("%v\n", err)
}
}
func (m *Munin) run() {
muninValues := &muninData{
status: &mk2driver.Mk2Info{},
}
for {
select {
case e := <-m.C():
if e.Valid {
calcMuninValues(muninValues, e)
}
case m.muninResponse <- muninValues:
zeroMuninValues(muninValues)
}
}
}
//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 *mk2driver.Mk2Info) {
muninDat.timesUpdated++
muninVal := &muninDat.status
muninVal := muninDat.status
muninVal.OutCurrent += newStatus.OutCurrent
muninVal.InCurrent += newStatus.InCurrent
muninVal.BatCurrent += newStatus.BatCurrent
@@ -189,7 +143,7 @@ func calcMuninValues(muninDat *muninData, newStatus *mk2if.Mk2Info) {
}
func calcMuninAverages(muninDat *muninData) {
muninVal := &muninDat.status
muninVal := muninDat.status
muninVal.OutCurrent /= float64(muninDat.timesUpdated)
muninVal.InCurrent /= float64(muninDat.timesUpdated)
muninVal.BatCurrent /= float64(muninDat.timesUpdated)
@@ -201,7 +155,7 @@ func calcMuninAverages(muninDat *muninData) {
func zeroMuninValues(muninDat *muninData) {
muninDat.timesUpdated = 0
muninVal := &muninDat.status
muninVal := muninDat.status
muninVal.OutCurrent = 0
muninVal.InCurrent = 0
muninVal.BatCurrent = 0
@@ -215,3 +169,50 @@ func zeroMuninValues(muninDat *muninData) {
muninVal.ChargeState = 0
}
type templateInput struct {
Date string `json:"date"`
OutCurrent string `json:"output_current"`
OutVoltage string `json:"output_voltage"`
OutPower string `json:"output_power"`
InCurrent string `json:"input_current"`
InVoltage string `json:"input_voltage"`
InPower string `json:"input_power"`
InMinOut string
BatVoltage string `json:"battery_voltage"`
BatCurrent string `json:"battery_current"`
BatPower string `json:"battery_power"`
BatCharge string `json:"battery_charge"`
InFreq string `json:"input_frequency"`
OutFreq string `json:"output_frequency"`
}
func buildTemplateInput(status *mk2driver.Mk2Info) *templateInput {
outPower := status.OutVoltage * status.OutCurrent
inPower := status.InCurrent * status.InVoltage
newInput := &templateInput{
Date: status.Timestamp.Format(time.RFC1123Z),
OutCurrent: fmt.Sprintf("%.2f", status.OutCurrent),
OutVoltage: fmt.Sprintf("%.2f", status.OutVoltage),
OutPower: fmt.Sprintf("%.2f", outPower),
InCurrent: fmt.Sprintf("%.2f", status.InCurrent),
InVoltage: fmt.Sprintf("%.2f", status.InVoltage),
InFreq: fmt.Sprintf("%.2f", status.InFrequency),
OutFreq: fmt.Sprintf("%.2f", status.OutFrequency),
InPower: fmt.Sprintf("%.2f", inPower),
InMinOut: fmt.Sprintf("%.2f", inPower-outPower),
BatCurrent: fmt.Sprintf("%.2f", status.BatCurrent),
BatVoltage: fmt.Sprintf("%.2f", status.BatVoltage),
BatPower: fmt.Sprintf("%.2f", status.BatVoltage*status.BatCurrent),
BatCharge: fmt.Sprintf("%.2f", status.ChargeState*100),
}
return newInput
}

View File

@@ -0,0 +1,82 @@
package munin
const muninConfig = `multigraph in_batvolt
graph_title Battery Voltage
graph_vlabel Voltage (V)
graph_category inverter
graph_info Battery voltage
volt.info Voltage of battery
volt.label Voltage of battery (V)
multigraph in_batcharge
graph_title Battery Charge
graph_vlabel Charge (%)
graph_category inverter
graph_info Battery charge
charge.info Estimated charge of battery
charge.label Battery charge (%)
multigraph in_batcurrent
graph_title Battery Current
graph_vlabel Current (A)
graph_category inverter
graph_info Battery current
current.info Battery current
current.label Battery current (A)
multigraph in_batpower
graph_title Battery Power
graph_vlabel Power (W)
graph_category inverter
graph_info Battery power
power.info Battery power
power.label Battery power (W)
multigraph in_mainscurrent
graph_title Mains Current
graph_vlabel Current (A)
graph_category inverter
graph_info Mains current
currentin.info Input current
currentin.label Input current (A)
currentout.info Output current
currentout.label Output current (A)
multigraph in_mainsvoltage
graph_title Mains Voltage
graph_vlabel Voltage (V)
graph_category inverter
graph_info Mains voltage
voltagein.info Input voltage
voltagein.label Input voltage (V)
voltageout.info Output voltage
voltageout.label Output voltage (V)
multigraph in_mainspower
graph_title Mains Power
graph_vlabel Power (VA)
graph_category inverter
graph_info Mains power
powerin.info Input power
powerin.label Input power (VA)
powerout.info Output power
powerout.label Output power (VA)
multigraph in_mainsfreq
graph_title Mains frequency
graph_vlabel Frequency (Hz)
graph_category inverter
graph_info Mains frequency
freqin.info In frequency
freqin.label In frequency (Hz)
freqout.info Out frequency
freqout.label Out frequency (Hz)
`

View File

@@ -28,14 +28,15 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package webgui
package prometheus
import (
"github.com/diebietse/invertergui/mk2if"
"github.com/diebietse/invertergui/mk2driver"
"github.com/prometheus/client_golang/prometheus"
)
type prometheusUpdater struct {
type Prometheus struct {
mk2driver.Mk2
batteryVoltage prometheus.Gauge
batteryCharge prometheus.Gauge
batteryCurrent prometheus.Gauge
@@ -50,8 +51,9 @@ type prometheusUpdater struct {
mainsFreqOut prometheus.Gauge
}
func newPrometheusUpdater() *prometheusUpdater {
tmp := &prometheusUpdater{
func NewPrometheus(mk2 mk2driver.Mk2) {
tmp := &Prometheus{
Mk2: mk2,
batteryVoltage: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "battery_voltage_v",
Help: "Voltage of the battery.",
@@ -101,7 +103,8 @@ func newPrometheusUpdater() *prometheusUpdater {
Help: "Mains frequency at inverter output",
}),
}
prometheus.MustRegister(tmp.batteryVoltage,
prometheus.MustRegister(
tmp.batteryVoltage,
tmp.batteryCharge,
tmp.batteryCurrent,
tmp.batteryPower,
@@ -114,21 +117,30 @@ func newPrometheusUpdater() *prometheusUpdater {
tmp.mainsFreqIn,
tmp.mainsFreqOut,
)
return tmp
go tmp.run()
}
func (pu *prometheusUpdater) updatePrometheus(newStatus *mk2if.Mk2Info) {
s := newStatus
pu.batteryVoltage.Set(s.BatVoltage)
pu.batteryCharge.Set(newStatus.ChargeState * 100)
pu.batteryCurrent.Set(s.BatCurrent)
pu.batteryPower.Set(s.BatVoltage * s.BatCurrent)
pu.mainsCurrentIn.Set(s.InCurrent)
pu.mainsCurrentOut.Set(s.OutCurrent)
pu.mainsVoltageIn.Set(s.InVoltage)
pu.mainsVoltageOut.Set(s.OutVoltage)
pu.mainsPowerIn.Set(s.InVoltage * s.InCurrent)
pu.mainsPowerOut.Set(s.OutVoltage * s.OutCurrent)
pu.mainsFreqIn.Set(s.InFrequency)
pu.mainsFreqOut.Set(s.OutFrequency)
func (p *Prometheus) run() {
for e := range p.C() {
if e.Valid {
p.updatePrometheus(e)
}
}
}
func (p *Prometheus) updatePrometheus(newStatus *mk2driver.Mk2Info) {
s := newStatus
p.batteryVoltage.Set(s.BatVoltage)
p.batteryCharge.Set(newStatus.ChargeState * 100)
p.batteryCurrent.Set(s.BatCurrent)
p.batteryPower.Set(s.BatVoltage * s.BatCurrent)
p.mainsCurrentIn.Set(s.InCurrent)
p.mainsCurrentOut.Set(s.OutCurrent)
p.mainsVoltageIn.Set(s.InVoltage)
p.mainsVoltageOut.Set(s.OutVoltage)
p.mainsPowerIn.Set(s.InVoltage * s.InCurrent)
p.mainsPowerOut.Set(s.OutVoltage * s.OutCurrent)
p.mainsFreqIn.Set(s.InFrequency)
p.mainsFreqOut.Set(s.OutFrequency)
}

View File

@@ -1,4 +1,4 @@
package frontend
package static
import (
"github.com/rakyll/statik/fs"
@@ -7,7 +7,8 @@ import (
"net/http"
)
func NewStatic() http.Handler {
// New exports the static part of the webgui that is served via statik
func New() http.Handler {
statikFs, err := fs.New()
if err != nil {
log.Fatal(err)

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -1,7 +1,7 @@
// Code generated by statik. DO NOT EDIT.
// Package statik contains static assets.
package frontend
package static
import (
"github.com/rakyll/statik/fs"

View File

@@ -28,7 +28,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package webgui
package webui
import (
"fmt"
@@ -37,7 +37,7 @@ import (
"sync"
"time"
"github.com/diebietse/invertergui/mk2if"
"github.com/diebietse/invertergui/mk2driver"
"github.com/diebietse/invertergui/websocket"
)
@@ -50,24 +50,19 @@ const (
)
type WebGui struct {
mk2driver.Mk2
stopChan chan struct{}
muninRespChan chan muninData
poller mk2if.Mk2If
wg sync.WaitGroup
hub *websocket.Hub
pu *prometheusUpdater
}
func NewWebGui(source mk2if.Mk2If) *WebGui {
w := new(WebGui)
w.muninRespChan = make(chan muninData)
w.stopChan = make(chan struct{})
w.poller = source
w.pu = newPrometheusUpdater()
w.hub = websocket.NewHub()
func NewWebGui(source mk2driver.Mk2) *WebGui {
w := &WebGui{
stopChan: make(chan struct{}),
Mk2: source,
hub: websocket.NewHub(),
}
w.wg.Add(1)
go w.dataPoll()
return w
@@ -99,27 +94,19 @@ type templateInput struct {
LedMap map[string]string `json:"led_map"`
}
func (w *WebGui) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
http.ServeFile(rw, r, "./frontend/index.html")
}
func (w *WebGui) ServeJS(rw http.ResponseWriter, r *http.Request) {
http.ServeFile(rw, r, "./frontend/js/controller.js")
}
func (w *WebGui) ServeHub(rw http.ResponseWriter, r *http.Request) {
w.hub.ServeHTTP(rw, r)
}
func ledName(led mk2if.Led) string {
name, ok := mk2if.LedNames[led]
func ledName(led mk2driver.Led) string {
name, ok := mk2driver.LedNames[led]
if !ok {
return "Unknown led"
}
return name
}
func buildTemplateInput(status *mk2if.Mk2Info) *templateInput {
func buildTemplateInput(status *mk2driver.Mk2Info) *templateInput {
outPower := status.OutVoltage * status.OutCurrent
inPower := status.InCurrent * status.InVoltage
@@ -145,20 +132,20 @@ func buildTemplateInput(status *mk2if.Mk2Info) *templateInput {
LedMap: map[string]string{},
}
for k, v := range status.LEDs {
if k == mk2if.LedOverload || k == mk2if.LedTemperature || k == mk2if.LedLowBattery {
if k == mk2driver.LedOverload || k == mk2driver.LedTemperature || k == mk2driver.LedLowBattery {
switch v {
case mk2if.LedOn:
case mk2driver.LedOn:
tmpInput.LedMap[ledName(k)] = LedRed
case mk2if.LedBlink:
case mk2driver.LedBlink:
tmpInput.LedMap[ledName(k)] = BlinkRed
default:
tmpInput.LedMap[ledName(k)] = LedOff
}
} else {
switch v {
case mk2if.LedOn:
case mk2driver.LedOn:
tmpInput.LedMap[ledName(k)] = LedGreen
case mk2if.LedBlink:
case mk2driver.LedBlink:
tmpInput.LedMap[ledName(k)] = BlinkGreen
default:
tmpInput.LedMap[ledName(k)] = LedOff
@@ -176,21 +163,15 @@ func (w *WebGui) Stop() {
// dataPoll waits for data from the w.poller channel. It will send its currently stored status
// to respChan if anything reads from it.
func (w *WebGui) dataPoll() {
pollChan := w.poller.C()
var muninValues muninData
for {
select {
case s := <-pollChan:
case s := <-w.C():
if s.Valid {
calcMuninValues(&muninValues, s)
w.pu.updatePrometheus(s)
err := w.hub.Broadcast(buildTemplateInput(s))
if err != nil {
log.Printf("Could not send update to clients: %v", err)
}
}
case w.muninRespChan <- muninValues:
zeroMuninValues(&muninValues)
case <-w.stopChan:
w.wg.Done()
return

View File

@@ -28,7 +28,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package webgui
package webui
import (
"fmt"
@@ -36,23 +36,18 @@ import (
"testing"
"time"
"github.com/diebietse/invertergui/mk2if"
"github.com/diebietse/invertergui/mk2driver"
)
func TestWebGui(t *testing.T) {
t.Skip("Not yet implimented")
//TODO figure out how to test template output.
}
type templateTest struct {
input *mk2if.Mk2Info
input *mk2driver.Mk2Info
output *templateInput
}
var fakenow = time.Date(2017, 1, 2, 3, 4, 5, 6, time.UTC)
var templateInputTests = []templateTest{
{
input: &mk2if.Mk2Info{
input: &mk2driver.Mk2Info{
OutCurrent: 2.0,
InCurrent: 2.3,
OutVoltage: 230.0,
@@ -62,7 +57,7 @@ var templateInputTests = []templateTest{
InFrequency: 50,
OutFrequency: 50,
ChargeState: 1,
LEDs: map[mk2if.Led]mk2if.LEDstate{mk2if.LedMain: mk2if.LedOn},
LEDs: map[mk2driver.Led]mk2driver.LEDstate{mk2driver.LedMain: mk2driver.LedOn},
Errors: nil,
Timestamp: fakenow,
},