Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a72d24cdd | ||
|
|
92daf9191b | ||
|
|
fd49891632 | ||
|
|
e74b0518e9 | ||
|
|
0b324458f0 | ||
|
|
8b0b4f64f1 | ||
|
|
47e73a4eff | ||
|
|
d02de285d9 | ||
|
|
9236d6fa86 | ||
|
|
acdaa019cb | ||
|
|
01ce2da533 |
28
.travis.yml
Normal file
28
.travis.yml
Normal 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)
|
||||||
14
Makefile
14
Makefile
@@ -26,21 +26,13 @@
|
|||||||
#OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
#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.
|
#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
|
.DEFAULT_GOAL = invertergui
|
||||||
|
|
||||||
guimock:
|
|
||||||
go build ./cmd/guimock/
|
|
||||||
|
|
||||||
invertergui:
|
invertergui:
|
||||||
go build ./cmd/invertergui/
|
go build ./cmd/invertergui/
|
||||||
|
|
||||||
invertercli:
|
|
||||||
go build ./cmd/invertercli/
|
|
||||||
|
|
||||||
build: guimock invertergui invertercli
|
|
||||||
|
|
||||||
all: build gofmt test
|
all: build gofmt test
|
||||||
|
|
||||||
gofmt:
|
gofmt:
|
||||||
@@ -62,4 +54,4 @@ lint:
|
|||||||
golangci-lint run
|
golangci-lint run
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm ./guimock ./invertercli ./invertergui
|
rm ./invertergui
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# Inverter GUI
|
# Inverter GUI
|
||||||
|
|
||||||
|
[](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 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.
|
The [`diebietse/invertergui`](https://hub.docker.com/r/diebietse/invertergui) docker image is a build of this repository.
|
||||||
|
|||||||
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -36,28 +36,67 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/diebietse/invertergui/frontend"
|
"github.com/diebietse/invertergui/mk2core"
|
||||||
"github.com/diebietse/invertergui/mk2if"
|
"github.com/diebietse/invertergui/mk2driver"
|
||||||
"github.com/diebietse/invertergui/webgui"
|
"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/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/tarm/serial"
|
"github.com/tarm/serial"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
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.")
|
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.")
|
ip := flag.String("ip", "localhost:8139", "IP to connect when using tcp connection.")
|
||||||
dev := flag.String("dev", "/dev/ttyUSB0", "TTY device to use.")
|
dev := flag.String("dev", "/dev/ttyUSB0", "TTY device to use.")
|
||||||
|
cliEnable := flag.Bool("cli", false, "Enable CLI output")
|
||||||
flag.Parse()
|
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 p io.ReadWriteCloser
|
||||||
var err error
|
var err error
|
||||||
var tcpAddr *net.TCPAddr
|
var tcpAddr *net.TCPAddr
|
||||||
|
|
||||||
if *tcp {
|
switch source {
|
||||||
tcpAddr, err = net.ResolveTCPAddr("tcp", *ip)
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -65,26 +104,17 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
} else {
|
case "mock":
|
||||||
serialConfig := &serial.Config{Name: *dev, Baud: 2400}
|
return mk2driver.NewMk2Mock()
|
||||||
p, err = serial.OpenPort(serialConfig)
|
default:
|
||||||
|
log.Printf("Invalid source selection: %v\nUse \"serial\", \"tcp\" or \"mock\"", source)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
mk2, err := mk2driver.NewMk2Connection(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
defer p.Close()
|
|
||||||
mk2, err := mk2if.NewMk2Connection(p)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer mk2.Close()
|
|
||||||
|
|
||||||
gui := webgui.NewWebGui(mk2)
|
return 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))
|
|
||||||
}
|
}
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -4,6 +4,7 @@ require (
|
|||||||
github.com/gorilla/websocket v1.4.0
|
github.com/gorilla/websocket v1.4.0
|
||||||
github.com/prometheus/client_golang v0.9.2
|
github.com/prometheus/client_golang v0.9.2
|
||||||
github.com/rakyll/statik v0.1.5
|
github.com/rakyll/statik v0.1.5
|
||||||
|
github.com/stretchr/testify v1.3.0
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313 // indirect
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
7
go.sum
7
go.sum
@@ -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 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
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 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
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 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
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 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
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 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
|
||||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
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=
|
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/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 h1:Ly2UjURzxnsSYS0zI50fZ+srA+Fu7EbpV5hglvJvJG0=
|
||||||
github.com/rakyll/statik v0.1.5/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
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 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
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=
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||||
"full": false,
|
"full": false,
|
||||||
"lineColor": "rgb(31, 120, 193)",
|
"lineColor": "rgb(31, 120, 193)",
|
||||||
"show": false
|
"show": true
|
||||||
},
|
},
|
||||||
"tableColumn": "",
|
"tableColumn": "",
|
||||||
"targets": [
|
"targets": [
|
||||||
@@ -176,7 +176,7 @@
|
|||||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||||
"full": false,
|
"full": false,
|
||||||
"lineColor": "rgb(31, 120, 193)",
|
"lineColor": "rgb(31, 120, 193)",
|
||||||
"show": false
|
"show": true
|
||||||
},
|
},
|
||||||
"tableColumn": "",
|
"tableColumn": "",
|
||||||
"targets": [
|
"targets": [
|
||||||
@@ -258,7 +258,7 @@
|
|||||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||||
"full": false,
|
"full": false,
|
||||||
"lineColor": "rgb(31, 120, 193)",
|
"lineColor": "rgb(31, 120, 193)",
|
||||||
"show": false
|
"show": true
|
||||||
},
|
},
|
||||||
"tableColumn": "",
|
"tableColumn": "",
|
||||||
"targets": [
|
"targets": [
|
||||||
@@ -339,7 +339,7 @@
|
|||||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||||
"full": false,
|
"full": false,
|
||||||
"lineColor": "rgb(31, 120, 193)",
|
"lineColor": "rgb(31, 120, 193)",
|
||||||
"show": false
|
"show": true
|
||||||
},
|
},
|
||||||
"tableColumn": "",
|
"tableColumn": "",
|
||||||
"targets": [
|
"targets": [
|
||||||
@@ -420,7 +420,7 @@
|
|||||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||||
"full": false,
|
"full": false,
|
||||||
"lineColor": "rgb(31, 120, 193)",
|
"lineColor": "rgb(31, 120, 193)",
|
||||||
"show": false
|
"show": true
|
||||||
},
|
},
|
||||||
"tableColumn": "",
|
"tableColumn": "",
|
||||||
"targets": [
|
"targets": [
|
||||||
@@ -501,7 +501,7 @@
|
|||||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||||
"full": false,
|
"full": false,
|
||||||
"lineColor": "rgb(31, 120, 193)",
|
"lineColor": "rgb(31, 120, 193)",
|
||||||
"show": false
|
"show": true
|
||||||
},
|
},
|
||||||
"tableColumn": "",
|
"tableColumn": "",
|
||||||
"targets": [
|
"targets": [
|
||||||
@@ -1345,5 +1345,5 @@
|
|||||||
"timezone": "",
|
"timezone": "",
|
||||||
"title": "Victron",
|
"title": "Victron",
|
||||||
"uid": "000000004",
|
"uid": "000000004",
|
||||||
"version": 1
|
"version": 2
|
||||||
}
|
}
|
||||||
57
mk2core/core.go
Normal file
57
mk2core/core.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package mk2if
|
package mk2driver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -26,7 +26,7 @@ type mk2Ser struct {
|
|||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMk2Connection(dev io.ReadWriter) (Mk2If, error) {
|
func NewMk2Connection(dev io.ReadWriter) (Mk2, error) {
|
||||||
mk2 := &mk2Ser{}
|
mk2 := &mk2Ser{}
|
||||||
mk2.p = dev
|
mk2.p = dev
|
||||||
mk2.info = &Mk2Info{}
|
mk2.info = &Mk2Info{}
|
||||||
141
mk2driver/mk2_test.go
Normal file
141
mk2driver/mk2_test.go
Normal 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")
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package mk2if
|
package mk2driver
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ type Mk2Info struct {
|
|||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mk2If interface {
|
type Mk2 interface {
|
||||||
C() chan *Mk2Info
|
C() chan *Mk2Info
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package mk2if
|
package mk2driver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -9,7 +8,7 @@ type mock struct {
|
|||||||
c chan *Mk2Info
|
c chan *Mk2Info
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMk2Mock() Mk2If {
|
func NewMk2Mock() Mk2 {
|
||||||
tmp := &mock{
|
tmp := &mock{
|
||||||
c: make(chan *Mk2Info, 1),
|
c: make(chan *Mk2Info, 1),
|
||||||
}
|
}
|
||||||
@@ -64,7 +63,6 @@ func (m *mock) genMockValues() {
|
|||||||
if mult < 0 {
|
if mult < 0 {
|
||||||
mult = 1.0
|
mult = 1.0
|
||||||
}
|
}
|
||||||
fmt.Printf("Sending\n")
|
|
||||||
m.c <- input
|
m.c <- input
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
47
plugins/cli/cli.go
Normal file
47
plugins/cli/cli.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -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.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package webgui
|
package munin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/diebietse/invertergui/mk2if"
|
"github.com/diebietse/invertergui/mk2driver"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Munin struct {
|
||||||
|
mk2driver.Mk2
|
||||||
|
muninResponse chan *muninData
|
||||||
|
}
|
||||||
|
|
||||||
type muninData struct {
|
type muninData struct {
|
||||||
status mk2if.Mk2Info
|
status *mk2driver.Mk2Info
|
||||||
timesUpdated int
|
timesUpdated int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WebGui) ServeMuninHTTP(rw http.ResponseWriter, r *http.Request) {
|
func NewMunin(mk2 mk2driver.Mk2) *Munin {
|
||||||
muninDat := <-w.muninRespChan
|
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 {
|
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)
|
||||||
|
|
||||||
status := muninDat.status
|
status := muninDat.status
|
||||||
tmpInput := buildTemplateInput(&status)
|
tmpInput := buildTemplateInput(status)
|
||||||
outputBuf := &bytes.Buffer{}
|
outputBuf := &bytes.Buffer{}
|
||||||
fmt.Fprintf(outputBuf, "multigraph in_batvolt\n")
|
fmt.Fprintf(outputBuf, "multigraph in_batvolt\n")
|
||||||
fmt.Fprintf(outputBuf, "volt.value %s\n", tmpInput.BatVoltage)
|
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) {
|
func (m *Munin) ServeMuninConfigHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
output := `multigraph in_batvolt
|
output := muninConfig
|
||||||
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)
|
|
||||||
`
|
|
||||||
|
|
||||||
_, err := rw.Write([]byte(output))
|
_, err := rw.Write([]byte(output))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%v\n", err)
|
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.
|
//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++
|
muninDat.timesUpdated++
|
||||||
muninVal := &muninDat.status
|
muninVal := muninDat.status
|
||||||
muninVal.OutCurrent += newStatus.OutCurrent
|
muninVal.OutCurrent += newStatus.OutCurrent
|
||||||
muninVal.InCurrent += newStatus.InCurrent
|
muninVal.InCurrent += newStatus.InCurrent
|
||||||
muninVal.BatCurrent += newStatus.BatCurrent
|
muninVal.BatCurrent += newStatus.BatCurrent
|
||||||
@@ -189,7 +143,7 @@ func calcMuninValues(muninDat *muninData, newStatus *mk2if.Mk2Info) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func calcMuninAverages(muninDat *muninData) {
|
func calcMuninAverages(muninDat *muninData) {
|
||||||
muninVal := &muninDat.status
|
muninVal := muninDat.status
|
||||||
muninVal.OutCurrent /= float64(muninDat.timesUpdated)
|
muninVal.OutCurrent /= float64(muninDat.timesUpdated)
|
||||||
muninVal.InCurrent /= float64(muninDat.timesUpdated)
|
muninVal.InCurrent /= float64(muninDat.timesUpdated)
|
||||||
muninVal.BatCurrent /= float64(muninDat.timesUpdated)
|
muninVal.BatCurrent /= float64(muninDat.timesUpdated)
|
||||||
@@ -201,7 +155,7 @@ func calcMuninAverages(muninDat *muninData) {
|
|||||||
|
|
||||||
func zeroMuninValues(muninDat *muninData) {
|
func zeroMuninValues(muninDat *muninData) {
|
||||||
muninDat.timesUpdated = 0
|
muninDat.timesUpdated = 0
|
||||||
muninVal := &muninDat.status
|
muninVal := muninDat.status
|
||||||
muninVal.OutCurrent = 0
|
muninVal.OutCurrent = 0
|
||||||
muninVal.InCurrent = 0
|
muninVal.InCurrent = 0
|
||||||
muninVal.BatCurrent = 0
|
muninVal.BatCurrent = 0
|
||||||
@@ -215,3 +169,50 @@ func zeroMuninValues(muninDat *muninData) {
|
|||||||
|
|
||||||
muninVal.ChargeState = 0
|
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
|
||||||
|
}
|
||||||
82
plugins/munin/muninconfig.go
Normal file
82
plugins/munin/muninconfig.go
Normal 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)
|
||||||
|
`
|
||||||
@@ -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.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package webgui
|
package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diebietse/invertergui/mk2if"
|
"github.com/diebietse/invertergui/mk2driver"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type prometheusUpdater struct {
|
type Prometheus struct {
|
||||||
|
mk2driver.Mk2
|
||||||
batteryVoltage prometheus.Gauge
|
batteryVoltage prometheus.Gauge
|
||||||
batteryCharge prometheus.Gauge
|
batteryCharge prometheus.Gauge
|
||||||
batteryCurrent prometheus.Gauge
|
batteryCurrent prometheus.Gauge
|
||||||
@@ -50,8 +51,9 @@ type prometheusUpdater struct {
|
|||||||
mainsFreqOut prometheus.Gauge
|
mainsFreqOut prometheus.Gauge
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPrometheusUpdater() *prometheusUpdater {
|
func NewPrometheus(mk2 mk2driver.Mk2) {
|
||||||
tmp := &prometheusUpdater{
|
tmp := &Prometheus{
|
||||||
|
Mk2: mk2,
|
||||||
batteryVoltage: prometheus.NewGauge(prometheus.GaugeOpts{
|
batteryVoltage: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
Name: "battery_voltage_v",
|
Name: "battery_voltage_v",
|
||||||
Help: "Voltage of the battery.",
|
Help: "Voltage of the battery.",
|
||||||
@@ -101,7 +103,8 @@ func newPrometheusUpdater() *prometheusUpdater {
|
|||||||
Help: "Mains frequency at inverter output",
|
Help: "Mains frequency at inverter output",
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
prometheus.MustRegister(tmp.batteryVoltage,
|
prometheus.MustRegister(
|
||||||
|
tmp.batteryVoltage,
|
||||||
tmp.batteryCharge,
|
tmp.batteryCharge,
|
||||||
tmp.batteryCurrent,
|
tmp.batteryCurrent,
|
||||||
tmp.batteryPower,
|
tmp.batteryPower,
|
||||||
@@ -114,21 +117,30 @@ func newPrometheusUpdater() *prometheusUpdater {
|
|||||||
tmp.mainsFreqIn,
|
tmp.mainsFreqIn,
|
||||||
tmp.mainsFreqOut,
|
tmp.mainsFreqOut,
|
||||||
)
|
)
|
||||||
return tmp
|
|
||||||
|
go tmp.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pu *prometheusUpdater) updatePrometheus(newStatus *mk2if.Mk2Info) {
|
func (p *Prometheus) run() {
|
||||||
s := newStatus
|
for e := range p.C() {
|
||||||
pu.batteryVoltage.Set(s.BatVoltage)
|
if e.Valid {
|
||||||
pu.batteryCharge.Set(newStatus.ChargeState * 100)
|
p.updatePrometheus(e)
|
||||||
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)
|
func (p *Prometheus) updatePrometheus(newStatus *mk2driver.Mk2Info) {
|
||||||
pu.mainsVoltageOut.Set(s.OutVoltage)
|
s := newStatus
|
||||||
pu.mainsPowerIn.Set(s.InVoltage * s.InCurrent)
|
p.batteryVoltage.Set(s.BatVoltage)
|
||||||
pu.mainsPowerOut.Set(s.OutVoltage * s.OutCurrent)
|
p.batteryCharge.Set(newStatus.ChargeState * 100)
|
||||||
pu.mainsFreqIn.Set(s.InFrequency)
|
p.batteryCurrent.Set(s.BatCurrent)
|
||||||
pu.mainsFreqOut.Set(s.OutFrequency)
|
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)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package frontend
|
package static
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/rakyll/statik/fs"
|
"github.com/rakyll/statik/fs"
|
||||||
@@ -7,7 +7,8 @@ import (
|
|||||||
"net/http"
|
"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()
|
statikFs, err := fs.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by statik. DO NOT EDIT.
|
// Code generated by statik. DO NOT EDIT.
|
||||||
|
|
||||||
// Package statik contains static assets.
|
// Package statik contains static assets.
|
||||||
package frontend
|
package static
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/rakyll/statik/fs"
|
"github.com/rakyll/statik/fs"
|
||||||
@@ -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.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package webgui
|
package webui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -37,7 +37,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/diebietse/invertergui/mk2if"
|
"github.com/diebietse/invertergui/mk2driver"
|
||||||
"github.com/diebietse/invertergui/websocket"
|
"github.com/diebietse/invertergui/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,24 +50,19 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type WebGui struct {
|
type WebGui struct {
|
||||||
|
mk2driver.Mk2
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
|
|
||||||
muninRespChan chan muninData
|
|
||||||
poller mk2if.Mk2If
|
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
hub *websocket.Hub
|
hub *websocket.Hub
|
||||||
|
|
||||||
pu *prometheusUpdater
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWebGui(source mk2if.Mk2If) *WebGui {
|
func NewWebGui(source mk2driver.Mk2) *WebGui {
|
||||||
w := new(WebGui)
|
w := &WebGui{
|
||||||
w.muninRespChan = make(chan muninData)
|
stopChan: make(chan struct{}),
|
||||||
w.stopChan = make(chan struct{})
|
Mk2: source,
|
||||||
w.poller = source
|
hub: websocket.NewHub(),
|
||||||
w.pu = newPrometheusUpdater()
|
}
|
||||||
w.hub = websocket.NewHub()
|
|
||||||
|
|
||||||
w.wg.Add(1)
|
w.wg.Add(1)
|
||||||
go w.dataPoll()
|
go w.dataPoll()
|
||||||
return w
|
return w
|
||||||
@@ -99,27 +94,19 @@ type templateInput struct {
|
|||||||
LedMap map[string]string `json:"led_map"`
|
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) {
|
func (w *WebGui) ServeHub(rw http.ResponseWriter, r *http.Request) {
|
||||||
w.hub.ServeHTTP(rw, r)
|
w.hub.ServeHTTP(rw, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ledName(led mk2if.Led) string {
|
func ledName(led mk2driver.Led) string {
|
||||||
name, ok := mk2if.LedNames[led]
|
name, ok := mk2driver.LedNames[led]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "Unknown led"
|
return "Unknown led"
|
||||||
}
|
}
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildTemplateInput(status *mk2if.Mk2Info) *templateInput {
|
func buildTemplateInput(status *mk2driver.Mk2Info) *templateInput {
|
||||||
outPower := status.OutVoltage * status.OutCurrent
|
outPower := status.OutVoltage * status.OutCurrent
|
||||||
inPower := status.InCurrent * status.InVoltage
|
inPower := status.InCurrent * status.InVoltage
|
||||||
|
|
||||||
@@ -145,20 +132,20 @@ func buildTemplateInput(status *mk2if.Mk2Info) *templateInput {
|
|||||||
LedMap: map[string]string{},
|
LedMap: map[string]string{},
|
||||||
}
|
}
|
||||||
for k, v := range status.LEDs {
|
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 {
|
switch v {
|
||||||
case mk2if.LedOn:
|
case mk2driver.LedOn:
|
||||||
tmpInput.LedMap[ledName(k)] = LedRed
|
tmpInput.LedMap[ledName(k)] = LedRed
|
||||||
case mk2if.LedBlink:
|
case mk2driver.LedBlink:
|
||||||
tmpInput.LedMap[ledName(k)] = BlinkRed
|
tmpInput.LedMap[ledName(k)] = BlinkRed
|
||||||
default:
|
default:
|
||||||
tmpInput.LedMap[ledName(k)] = LedOff
|
tmpInput.LedMap[ledName(k)] = LedOff
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch v {
|
switch v {
|
||||||
case mk2if.LedOn:
|
case mk2driver.LedOn:
|
||||||
tmpInput.LedMap[ledName(k)] = LedGreen
|
tmpInput.LedMap[ledName(k)] = LedGreen
|
||||||
case mk2if.LedBlink:
|
case mk2driver.LedBlink:
|
||||||
tmpInput.LedMap[ledName(k)] = BlinkGreen
|
tmpInput.LedMap[ledName(k)] = BlinkGreen
|
||||||
default:
|
default:
|
||||||
tmpInput.LedMap[ledName(k)] = LedOff
|
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
|
// dataPoll waits for data from the w.poller channel. It will send its currently stored status
|
||||||
// to respChan if anything reads from it.
|
// to respChan if anything reads from it.
|
||||||
func (w *WebGui) dataPoll() {
|
func (w *WebGui) dataPoll() {
|
||||||
pollChan := w.poller.C()
|
|
||||||
var muninValues muninData
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case s := <-pollChan:
|
case s := <-w.C():
|
||||||
if s.Valid {
|
if s.Valid {
|
||||||
calcMuninValues(&muninValues, s)
|
|
||||||
w.pu.updatePrometheus(s)
|
|
||||||
err := w.hub.Broadcast(buildTemplateInput(s))
|
err := w.hub.Broadcast(buildTemplateInput(s))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Could not send update to clients: %v", err)
|
log.Printf("Could not send update to clients: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case w.muninRespChan <- muninValues:
|
|
||||||
zeroMuninValues(&muninValues)
|
|
||||||
case <-w.stopChan:
|
case <-w.stopChan:
|
||||||
w.wg.Done()
|
w.wg.Done()
|
||||||
return
|
return
|
||||||
@@ -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.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package webgui
|
package webui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -36,23 +36,18 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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 {
|
type templateTest struct {
|
||||||
input *mk2if.Mk2Info
|
input *mk2driver.Mk2Info
|
||||||
output *templateInput
|
output *templateInput
|
||||||
}
|
}
|
||||||
|
|
||||||
var fakenow = time.Date(2017, 1, 2, 3, 4, 5, 6, time.UTC)
|
var fakenow = time.Date(2017, 1, 2, 3, 4, 5, 6, time.UTC)
|
||||||
var templateInputTests = []templateTest{
|
var templateInputTests = []templateTest{
|
||||||
{
|
{
|
||||||
input: &mk2if.Mk2Info{
|
input: &mk2driver.Mk2Info{
|
||||||
OutCurrent: 2.0,
|
OutCurrent: 2.0,
|
||||||
InCurrent: 2.3,
|
InCurrent: 2.3,
|
||||||
OutVoltage: 230.0,
|
OutVoltage: 230.0,
|
||||||
@@ -62,7 +57,7 @@ var templateInputTests = []templateTest{
|
|||||||
InFrequency: 50,
|
InFrequency: 50,
|
||||||
OutFrequency: 50,
|
OutFrequency: 50,
|
||||||
ChargeState: 1,
|
ChargeState: 1,
|
||||||
LEDs: map[mk2if.Led]mk2if.LEDstate{mk2if.LedMain: mk2if.LedOn},
|
LEDs: map[mk2driver.Led]mk2driver.LEDstate{mk2driver.LedMain: mk2driver.LedOn},
|
||||||
Errors: nil,
|
Errors: nil,
|
||||||
Timestamp: fakenow,
|
Timestamp: fakenow,
|
||||||
},
|
},
|
||||||
Reference in New Issue
Block a user