Use mk2if instead of datasource as data source.
This removes support for the python based mk2daemon.
This commit is contained in:
@@ -32,23 +32,54 @@ package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/hpdvanwyk/invertergui/datasource"
|
||||
"github.com/hpdvanwyk/invertergui/mk2if"
|
||||
"github.com/hpdvanwyk/invertergui/webgui"
|
||||
"github.com/mikepb/go-serial"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
url := flag.String("url", "http://localhost:9005", "The url of the multiplus JSON interface.")
|
||||
capacity := flag.Float64("capacity", 100, "The capacity of the batteries in the system.")
|
||||
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.")
|
||||
flag.Parse()
|
||||
|
||||
source := datasource.NewJSONSource(*url)
|
||||
poller := datasource.NewDataPoller(source, 10*time.Second)
|
||||
gui := webgui.NewWebGui(poller, *capacity)
|
||||
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 {
|
||||
options := serial.RawOptions
|
||||
options.BitRate = 2400
|
||||
options.Mode = serial.MODE_READ_WRITE
|
||||
p, err = options.Open(*dev)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
defer p.Close()
|
||||
mk2, err := mk2if.NewMk2Connection(p)
|
||||
defer mk2.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
gui := webgui.NewWebGui(mk2)
|
||||
http.Handle("/", gui)
|
||||
http.Handle("/munin", http.HandlerFunc(gui.ServeMuninHTTP))
|
||||
http.Handle("/muninconfig", http.HandlerFunc(gui.ServeMuninConfigHTTP))
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Hendrik van Wyk
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of invertergui nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
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 datasource
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DataPoller interface {
|
||||
C() chan *Status
|
||||
Stop()
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
MpStatus MultiplusStatus
|
||||
Time time.Time
|
||||
Err error
|
||||
}
|
||||
|
||||
type poller struct {
|
||||
source DataSource
|
||||
rate time.Duration
|
||||
statusChan chan *Status
|
||||
stop chan struct{}
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func NewDataPoller(source DataSource, pollRate time.Duration) DataPoller {
|
||||
this := &poller{
|
||||
source: source,
|
||||
rate: pollRate,
|
||||
statusChan: make(chan *Status),
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
this.wg.Add(1)
|
||||
go this.poll()
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *poller) C() chan *Status {
|
||||
return this.statusChan
|
||||
}
|
||||
|
||||
func (this *poller) Stop() {
|
||||
close(this.stop)
|
||||
this.wg.Wait()
|
||||
}
|
||||
|
||||
func (this *poller) poll() {
|
||||
ticker := time.NewTicker(this.rate)
|
||||
this.doPoll()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
this.doPoll()
|
||||
case <-this.stop:
|
||||
ticker.Stop()
|
||||
close(this.statusChan)
|
||||
this.wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *poller) doPoll() {
|
||||
tmp := new(Status)
|
||||
tmp.Err = this.source.GetData(&tmp.MpStatus)
|
||||
tmp.Time = time.Now()
|
||||
this.statusChan <- tmp
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Hendrik van Wyk
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of invertergui nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
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 datasource
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var sampleJSON string = `{"outCurrent": 1.19,
|
||||
"leds": [0, 0, 0, 0, 1, 0, 0, 1],
|
||||
"batVoltage": 26.63,
|
||||
"inCurrent": 1.39,
|
||||
"outVoltage": 235.3,
|
||||
"inVoltage": 235.3,
|
||||
"inFreq": 51.3,
|
||||
"batCurrent": 0.0,
|
||||
"outFreq": 735.3}`
|
||||
|
||||
func returnJson(resp http.ResponseWriter, req *http.Request) {
|
||||
resp.Write([]byte(sampleJSON))
|
||||
}
|
||||
|
||||
func TestFetchStatus(t *testing.T) {
|
||||
//setup test server
|
||||
testServer := httptest.NewServer(http.HandlerFunc(returnJson))
|
||||
|
||||
var status MultiplusStatus
|
||||
source := NewJSONSource(testServer.URL)
|
||||
err := source.GetData(&status)
|
||||
if err != nil {
|
||||
t.Errorf("Unmarshal gave: %v", err)
|
||||
}
|
||||
expected := MultiplusStatus{1.19, 1.39, 235.3, 235.3, 26.63, 0, 51.3, 735.3, []int{0, 0, 0, 0, 1, 0, 0, 1}}
|
||||
if !reflect.DeepEqual(status, expected) {
|
||||
t.Errorf("JSON string did not decode as expected.")
|
||||
}
|
||||
testServer.Close()
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Hendrik van Wyk
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of invertergui nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
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 datasource
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type DataSource interface {
|
||||
GetData(*MultiplusStatus) error
|
||||
}
|
||||
|
||||
type MultiplusStatus struct {
|
||||
OutCurrent float64 `json:"outCurrent"`
|
||||
InCurrent float64 `json:"inCurrent"`
|
||||
OutVoltage float64 `json:"outVoltage"`
|
||||
InVoltage float64 `json:"inVoltage"`
|
||||
BatVoltage float64 `json:"batVoltage"`
|
||||
BatCurrent float64 `json:"batCurrent"`
|
||||
InFreq float64 `json:"inFreq"`
|
||||
OutFreq float64 `json:"outFreq"`
|
||||
Leds []int `json:"leds"`
|
||||
}
|
||||
|
||||
type source struct {
|
||||
url string
|
||||
}
|
||||
|
||||
func NewJSONSource(url string) DataSource {
|
||||
return &source{url: url}
|
||||
}
|
||||
|
||||
func (s *source) GetData(status *MultiplusStatus) error {
|
||||
resp, err := http.Get(s.url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
err = dec.Decode(status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2015, Hendrik van Wyk
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of invertergui nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
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 datasource
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type mockDataSource struct {
|
||||
currentMock int
|
||||
shouldBreak bool
|
||||
}
|
||||
|
||||
func (this *mockDataSource) GetData(data *MultiplusStatus) error {
|
||||
if this.shouldBreak {
|
||||
return errors.New("Do not be alarmed. This is only a test.")
|
||||
}
|
||||
data.BatCurrent = float64(this.currentMock)
|
||||
this.currentMock++
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestOnePoll(t *testing.T) {
|
||||
poller := NewDataPoller(&mockDataSource{currentMock: 100}, 1*time.Millisecond)
|
||||
statChan := poller.C()
|
||||
status := <-statChan
|
||||
if status.MpStatus.BatCurrent != 100 {
|
||||
t.Errorf("Incorrect data passed from data source.")
|
||||
}
|
||||
if status.Time.IsZero() {
|
||||
t.Errorf("Time not set.")
|
||||
}
|
||||
poller.Stop()
|
||||
}
|
||||
|
||||
func TestMultiplePolls(t *testing.T) {
|
||||
poller := NewDataPoller(&mockDataSource{currentMock: 100}, 1*time.Millisecond)
|
||||
statChan := poller.C()
|
||||
expect := 100
|
||||
for i := 0; i < 100; i++ {
|
||||
status := <-statChan
|
||||
if status.MpStatus.BatCurrent != float64(expect) {
|
||||
t.Errorf("Incorrect data passed from data source.")
|
||||
}
|
||||
expect++
|
||||
if status.Time.IsZero() {
|
||||
t.Errorf("Time not set.")
|
||||
}
|
||||
}
|
||||
poller.Stop()
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
poller := NewDataPoller(&mockDataSource{shouldBreak: true}, 1*time.Millisecond)
|
||||
statChan := poller.C()
|
||||
status := <-statChan
|
||||
if status.Err == nil {
|
||||
t.Errorf("Error not correctly propagated.")
|
||||
}
|
||||
poller.Stop()
|
||||
}
|
||||
@@ -40,7 +40,11 @@ var htmlTemplate string = `<html>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{if .Error}} <p>Error encountered: {{.Error}} </p> {{end}}
|
||||
{{if .Error}} <p>Errors encountered: </p>
|
||||
{{range .Error}}
|
||||
<dt> {{.}}</dt>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<dl>
|
||||
<dt> Date: {{.Date}}</dt>
|
||||
</dl>
|
||||
@@ -55,6 +59,7 @@ var htmlTemplate string = `<html>
|
||||
<dl>
|
||||
<dt> Output Current: {{.OutCurrent}} A</dt>
|
||||
<dt> Output Voltage: {{.OutVoltage}} V</dt>
|
||||
<dt> Output Frequency: {{.OutFreq}} Hz</dt>
|
||||
<dt> Output Power: {{.OutPower}} VA</dt>
|
||||
</dl>
|
||||
<dl>
|
||||
@@ -68,7 +73,7 @@ var htmlTemplate string = `<html>
|
||||
<dt> Battery Current: {{.BatCurrent}} A</dt>
|
||||
<dt> Battery Voltage: {{.BatVoltage}} V</dt>
|
||||
<dt> Battery Power: {{.BatPower}} W</dt>
|
||||
<dt> Battery Charge: {{.BatCharge}} A h</dt>
|
||||
<dt> Battery Charge: {{.BatCharge}} %</dt>
|
||||
</dl>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
@@ -33,12 +33,12 @@ package webgui
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/hpdvanwyk/invertergui/mk2if"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type muninData struct {
|
||||
statusP statusProcessed
|
||||
status mk2if.Mk2Info
|
||||
timesUpdated int
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ func (w *WebGui) ServeMuninHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
calcMuninAverages(&muninDat)
|
||||
|
||||
statusP := &muninDat.statusP
|
||||
tmpInput := buildTemplateInput(statusP, time.Now())
|
||||
status := muninDat.status
|
||||
tmpInput := buildTemplateInput(&status)
|
||||
outputBuf := &bytes.Buffer{}
|
||||
fmt.Fprintf(outputBuf, "multigraph in_batvolt\n")
|
||||
fmt.Fprintf(outputBuf, "volt.value %s\n", tmpInput.BatVoltage)
|
||||
@@ -72,7 +72,8 @@ func (w *WebGui) ServeMuninHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(outputBuf, "powerin.value %s\n", tmpInput.InPower)
|
||||
fmt.Fprintf(outputBuf, "powerout.value %s\n", tmpInput.OutPower)
|
||||
fmt.Fprintf(outputBuf, "multigraph in_mainsfreq\n")
|
||||
fmt.Fprintf(outputBuf, "freq.value %s\n", tmpInput.InFreq)
|
||||
fmt.Fprintf(outputBuf, "freqin.value %s\n", tmpInput.InFreq)
|
||||
fmt.Fprintf(outputBuf, "freqout.value %s\n", tmpInput.OutFreq)
|
||||
|
||||
_, err := rw.Write([]byte(outputBuf.String()))
|
||||
if err != nil {
|
||||
@@ -92,12 +93,12 @@ volt.label Voltage of battery (V)
|
||||
|
||||
multigraph in_batcharge
|
||||
graph_title Battery Charge
|
||||
graph_vlabel Charge (A h)
|
||||
graph_vlabel Charge (%)
|
||||
graph_category inverter
|
||||
graph_info Battery charge
|
||||
|
||||
charge.info Estimated charge of battery
|
||||
charge.label Battery charge (A h)
|
||||
charge.label Battery charge (%)
|
||||
|
||||
multigraph in_batcurrent
|
||||
graph_title Battery Current
|
||||
@@ -156,8 +157,10 @@ graph_vlabel Frequency (Hz)
|
||||
graph_category inverter
|
||||
graph_info Mains frequency
|
||||
|
||||
freq.info Input frequency
|
||||
freq.label Input frequency (Hz)
|
||||
freqin.info In frequency
|
||||
freqin.label In frequency (Hz)
|
||||
freqout.info Out frequency
|
||||
freqout.label Out frequency (Hz)
|
||||
`
|
||||
|
||||
_, err := rw.Write([]byte(output))
|
||||
@@ -167,46 +170,47 @@ freq.label Input frequency (Hz)
|
||||
}
|
||||
|
||||
//Munin only samples once every 5 minutes so averages have to be calculated for some values.
|
||||
func calcMuninValues(muninDat *muninData, newStatus *statusProcessed) {
|
||||
func calcMuninValues(muninDat *muninData, newStatus *mk2if.Mk2Info) {
|
||||
muninDat.timesUpdated += 1
|
||||
muninVal := &muninDat.statusP
|
||||
muninVal.status.OutCurrent += newStatus.status.OutCurrent
|
||||
muninVal.status.InCurrent += newStatus.status.InCurrent
|
||||
muninVal.status.BatCurrent += newStatus.status.BatCurrent
|
||||
muninVal := &muninDat.status
|
||||
muninVal.OutCurrent += newStatus.OutCurrent
|
||||
muninVal.InCurrent += newStatus.InCurrent
|
||||
muninVal.BatCurrent += newStatus.BatCurrent
|
||||
|
||||
muninVal.status.OutVoltage += newStatus.status.OutVoltage
|
||||
muninVal.status.InVoltage += newStatus.status.InVoltage
|
||||
muninVal.status.BatVoltage += newStatus.status.BatVoltage
|
||||
muninVal.OutVoltage += newStatus.OutVoltage
|
||||
muninVal.InVoltage += newStatus.InVoltage
|
||||
muninVal.BatVoltage += newStatus.BatVoltage
|
||||
|
||||
muninVal.status.InFreq = newStatus.status.InFreq
|
||||
muninVal.InFrequency = newStatus.InFrequency
|
||||
muninVal.OutFrequency = newStatus.OutFrequency
|
||||
|
||||
muninVal.chargeLevel = newStatus.chargeLevel
|
||||
muninVal.status.Leds = newStatus.status.Leds
|
||||
muninVal.ChargeState = newStatus.ChargeState
|
||||
}
|
||||
|
||||
func calcMuninAverages(muninDat *muninData) {
|
||||
muninVal := &muninDat.statusP
|
||||
muninVal.status.OutCurrent /= float64(muninDat.timesUpdated)
|
||||
muninVal.status.InCurrent /= float64(muninDat.timesUpdated)
|
||||
muninVal.status.BatCurrent /= float64(muninDat.timesUpdated)
|
||||
muninVal := &muninDat.status
|
||||
muninVal.OutCurrent /= float64(muninDat.timesUpdated)
|
||||
muninVal.InCurrent /= float64(muninDat.timesUpdated)
|
||||
muninVal.BatCurrent /= float64(muninDat.timesUpdated)
|
||||
|
||||
muninVal.status.OutVoltage /= float64(muninDat.timesUpdated)
|
||||
muninVal.status.InVoltage /= float64(muninDat.timesUpdated)
|
||||
muninVal.status.BatVoltage /= float64(muninDat.timesUpdated)
|
||||
muninVal.OutVoltage /= float64(muninDat.timesUpdated)
|
||||
muninVal.InVoltage /= float64(muninDat.timesUpdated)
|
||||
muninVal.BatVoltage /= float64(muninDat.timesUpdated)
|
||||
}
|
||||
|
||||
func zeroMuninValues(muninDat *muninData) {
|
||||
muninDat.timesUpdated = 0
|
||||
muninVal := &muninDat.statusP
|
||||
muninVal.status.OutCurrent = 0
|
||||
muninVal.status.InCurrent = 0
|
||||
muninVal.status.BatCurrent = 0
|
||||
muninVal := &muninDat.status
|
||||
muninVal.OutCurrent = 0
|
||||
muninVal.InCurrent = 0
|
||||
muninVal.BatCurrent = 0
|
||||
|
||||
muninVal.status.OutVoltage = 0
|
||||
muninVal.status.InVoltage = 0
|
||||
muninVal.status.BatVoltage = 0
|
||||
muninVal.OutVoltage = 0
|
||||
muninVal.InVoltage = 0
|
||||
muninVal.BatVoltage = 0
|
||||
|
||||
muninVal.status.InFreq = 0
|
||||
muninVal.InFrequency = 0
|
||||
muninVal.OutFrequency = 0
|
||||
|
||||
muninVal.chargeLevel = 0
|
||||
muninVal.ChargeState = 0
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
package webgui
|
||||
|
||||
import (
|
||||
"github.com/hpdvanwyk/invertergui/mk2if"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
@@ -116,10 +117,10 @@ func newPrometheusUpdater() *prometheusUpdater {
|
||||
return tmp
|
||||
}
|
||||
|
||||
func (pu *prometheusUpdater) updatePrometheus(newStatus *statusProcessed) {
|
||||
s := newStatus.status
|
||||
func (pu *prometheusUpdater) updatePrometheus(newStatus *mk2if.Mk2Info) {
|
||||
s := newStatus
|
||||
pu.batteryVoltage.Set(s.BatVoltage)
|
||||
pu.batteryCharge.Set(newStatus.chargeLevel)
|
||||
pu.batteryCharge.Set(newStatus.ChargeState * 100)
|
||||
pu.batteryCurrent.Set(s.BatCurrent)
|
||||
pu.batteryCurrent.Set(s.BatVoltage * s.BatCurrent)
|
||||
pu.mainsCurrentIn.Set(s.InCurrent)
|
||||
@@ -128,7 +129,6 @@ func (pu *prometheusUpdater) updatePrometheus(newStatus *statusProcessed) {
|
||||
pu.mainsVoltageOut.Set(s.OutVoltage)
|
||||
pu.mainsPowerIn.Set(s.InVoltage * s.InCurrent)
|
||||
pu.mainsPowerOut.Set(s.OutVoltage * s.OutCurrent)
|
||||
pu.mainsFreqIn.Set(s.InFreq)
|
||||
pu.mainsFreqIn.Set(s.OutFreq)
|
||||
|
||||
pu.mainsFreqIn.Set(s.InFrequency)
|
||||
pu.mainsFreqOut.Set(s.OutFrequency)
|
||||
}
|
||||
|
||||
102
webgui/webgui.go
102
webgui/webgui.go
@@ -32,50 +32,28 @@ package webgui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hpdvanwyk/invertergui/datasource"
|
||||
"github.com/hpdvanwyk/invertergui/mk2if"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
Temperature = iota
|
||||
Low_battery
|
||||
Overload
|
||||
Inverter
|
||||
Float
|
||||
Bulk
|
||||
Absorption
|
||||
Mains
|
||||
)
|
||||
|
||||
var leds = map[int]string{
|
||||
0: "Temperature",
|
||||
1: "Low battery",
|
||||
2: "Overload",
|
||||
3: "Inverter",
|
||||
4: "Float",
|
||||
5: "Bulk",
|
||||
6: "Absorption",
|
||||
7: "Mains",
|
||||
}
|
||||
|
||||
type WebGui struct {
|
||||
respChan chan statusProcessed
|
||||
respChan chan *mk2if.Mk2Info
|
||||
stopChan chan struct{}
|
||||
template *template.Template
|
||||
|
||||
muninRespChan chan muninData
|
||||
poller datasource.DataPoller
|
||||
poller mk2if.Mk2If
|
||||
wg sync.WaitGroup
|
||||
|
||||
pu *prometheusUpdater
|
||||
}
|
||||
|
||||
func NewWebGui(source datasource.DataPoller, batteryCapacity float64) *WebGui {
|
||||
func NewWebGui(source mk2if.Mk2If) *WebGui {
|
||||
w := new(WebGui)
|
||||
w.respChan = make(chan statusProcessed)
|
||||
w.respChan = make(chan *mk2if.Mk2Info)
|
||||
w.muninRespChan = make(chan muninData)
|
||||
w.stopChan = make(chan struct{})
|
||||
var err error
|
||||
@@ -87,12 +65,12 @@ func NewWebGui(source datasource.DataPoller, batteryCapacity float64) *WebGui {
|
||||
w.pu = newPrometheusUpdater()
|
||||
|
||||
w.wg.Add(1)
|
||||
go w.dataPoll(batteryCapacity)
|
||||
go w.dataPoll()
|
||||
return w
|
||||
}
|
||||
|
||||
type templateInput struct {
|
||||
Error error
|
||||
Error []error
|
||||
|
||||
Date string
|
||||
|
||||
@@ -111,7 +89,8 @@ type templateInput struct {
|
||||
BatPower string
|
||||
BatCharge string
|
||||
|
||||
InFreq string
|
||||
InFreq string
|
||||
OutFreq string
|
||||
|
||||
Leds []string
|
||||
}
|
||||
@@ -119,7 +98,7 @@ type templateInput struct {
|
||||
func (w *WebGui) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
statusErr := <-w.respChan
|
||||
|
||||
tmpInput := buildTemplateInput(&statusErr, statusErr.timestamp)
|
||||
tmpInput := buildTemplateInput(statusErr)
|
||||
|
||||
err := w.template.Execute(rw, tmpInput)
|
||||
if err != nil {
|
||||
@@ -127,20 +106,28 @@ func (w *WebGui) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func buildTemplateInput(statusErr *statusProcessed, now time.Time) *templateInput {
|
||||
status := statusErr.status
|
||||
func ledName(nameInt int) string {
|
||||
name, ok := mk2if.LedNames[nameInt]
|
||||
if !ok {
|
||||
return "Unknown led"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func buildTemplateInput(status *mk2if.Mk2Info) *templateInput {
|
||||
outPower := status.OutVoltage * status.OutCurrent
|
||||
inPower := status.InCurrent * status.InVoltage
|
||||
|
||||
tmpInput := &templateInput{
|
||||
Error: statusErr.err,
|
||||
Date: now.Format(time.RFC1123Z),
|
||||
Error: status.Errors,
|
||||
Date: status.Timestamp.Format(time.RFC1123Z),
|
||||
OutCurrent: fmt.Sprintf("%.3f", status.OutCurrent),
|
||||
OutVoltage: fmt.Sprintf("%.3f", status.OutVoltage),
|
||||
OutPower: fmt.Sprintf("%.3f", outPower),
|
||||
InCurrent: fmt.Sprintf("%.3f", status.InCurrent),
|
||||
InVoltage: fmt.Sprintf("%.3f", status.InVoltage),
|
||||
InFreq: fmt.Sprintf("%.3f", status.InFreq),
|
||||
InFreq: fmt.Sprintf("%.3f", status.InFrequency),
|
||||
OutFreq: fmt.Sprintf("%.3f", status.OutFrequency),
|
||||
InPower: fmt.Sprintf("%.3f", inPower),
|
||||
|
||||
InMinOut: fmt.Sprintf("%.3f", inPower-outPower),
|
||||
@@ -148,56 +135,33 @@ func buildTemplateInput(statusErr *statusProcessed, now time.Time) *templateInpu
|
||||
BatCurrent: fmt.Sprintf("%.3f", status.BatCurrent),
|
||||
BatVoltage: fmt.Sprintf("%.3f", status.BatVoltage),
|
||||
BatPower: fmt.Sprintf("%.3f", status.BatVoltage*status.BatCurrent),
|
||||
BatCharge: fmt.Sprintf("%.3f", statusErr.chargeLevel),
|
||||
BatCharge: fmt.Sprintf("%.3f", status.ChargeState*100),
|
||||
}
|
||||
if len(status.Leds) == 8 {
|
||||
for i := 7; i >= 0; i-- {
|
||||
if status.Leds[i] == 1 {
|
||||
tmpInput.Leds = append(tmpInput.Leds, leds[i])
|
||||
}
|
||||
}
|
||||
for i := range status.LedListOn {
|
||||
tmpInput.Leds = append(tmpInput.Leds, ledName(status.LedListOn[i]))
|
||||
}
|
||||
return tmpInput
|
||||
}
|
||||
|
||||
func (w *WebGui) Stop() {
|
||||
w.poller.Stop()
|
||||
close(w.stopChan)
|
||||
w.wg.Wait()
|
||||
}
|
||||
|
||||
type statusProcessed struct {
|
||||
status datasource.MultiplusStatus
|
||||
chargeLevel float64
|
||||
err error
|
||||
timestamp time.Time
|
||||
}
|
||||
|
||||
// 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(batteryCapacity float64) {
|
||||
tracker := NewChargeTracker(batteryCapacity)
|
||||
func (w *WebGui) dataPoll() {
|
||||
pollChan := w.poller.C()
|
||||
var statusP statusProcessed
|
||||
var muninValues muninData
|
||||
s := &mk2if.Mk2Info{}
|
||||
for {
|
||||
select {
|
||||
case s := <-pollChan:
|
||||
if s.Err != nil {
|
||||
statusP.err = s.Err
|
||||
} else {
|
||||
statusP.status = s.MpStatus
|
||||
statusP.err = nil
|
||||
statusP.timestamp = s.Time
|
||||
tracker.Update(s.MpStatus.BatCurrent, s.Time)
|
||||
if s.MpStatus.Leds[Float] == 1 {
|
||||
tracker.Reset()
|
||||
}
|
||||
statusP.chargeLevel = tracker.CurrentLevel()
|
||||
calcMuninValues(&muninValues, &statusP)
|
||||
w.pu.updatePrometheus(&statusP)
|
||||
case s = <-pollChan:
|
||||
if s.Valid {
|
||||
calcMuninValues(&muninValues, s)
|
||||
w.pu.updatePrometheus(s)
|
||||
}
|
||||
case w.respChan <- statusP:
|
||||
case w.respChan <- s:
|
||||
case w.muninRespChan <- muninValues:
|
||||
zeroMuninValues(&muninValues)
|
||||
case <-w.stopChan:
|
||||
|
||||
@@ -31,57 +31,39 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
package webgui
|
||||
|
||||
import (
|
||||
"github.com/hpdvanwyk/invertergui/datasource"
|
||||
"fmt"
|
||||
"github.com/hpdvanwyk/invertergui/mk2if"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type mockSource struct {
|
||||
}
|
||||
|
||||
func NewMockSource() datasource.DataSource {
|
||||
return &mockSource{}
|
||||
}
|
||||
|
||||
func (s *mockSource) GetData(status *datasource.MultiplusStatus) error {
|
||||
status.OutCurrent = 2.0
|
||||
status.InCurrent = 2.3
|
||||
status.OutVoltage = 230.0
|
||||
status.InVoltage = 230.1
|
||||
status.BatVoltage = 25
|
||||
status.BatCurrent = -10
|
||||
status.InFreq = 50
|
||||
status.OutFreq = 50
|
||||
status.Leds = []int{0, 0, 0, 0, 1, 0, 0, 1}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestWebGui(t *testing.T) {
|
||||
t.Skip("Not yet implimented")
|
||||
//TODO figure out how to test template output.
|
||||
}
|
||||
|
||||
type templateTest struct {
|
||||
input *statusProcessed
|
||||
input *mk2if.Mk2Info
|
||||
output *templateInput
|
||||
}
|
||||
|
||||
var fakenow = time.Date(2017, 1, 2, 3, 4, 5, 6, time.UTC)
|
||||
var templateInputTests = []templateTest{
|
||||
{
|
||||
input: &statusProcessed{
|
||||
status: datasource.MultiplusStatus{
|
||||
OutCurrent: 2.0,
|
||||
InCurrent: 2.3,
|
||||
OutVoltage: 230.0,
|
||||
InVoltage: 230.1,
|
||||
BatVoltage: 25,
|
||||
BatCurrent: -10,
|
||||
InFreq: 50,
|
||||
OutFreq: 50,
|
||||
Leds: []int{0, 0, 0, 0, 1, 0, 0, 1}},
|
||||
err: nil,
|
||||
input: &mk2if.Mk2Info{
|
||||
OutCurrent: 2.0,
|
||||
InCurrent: 2.3,
|
||||
OutVoltage: 230.0,
|
||||
InVoltage: 230.1,
|
||||
BatVoltage: 25,
|
||||
BatCurrent: -10,
|
||||
InFrequency: 50,
|
||||
OutFrequency: 50,
|
||||
ChargeState: 1,
|
||||
LedListOn: []int{mk2if.LED_MAIN, mk2if.LED_FLOAT},
|
||||
Errors: nil,
|
||||
Timestamp: fakenow,
|
||||
},
|
||||
output: &templateInput{
|
||||
Error: nil,
|
||||
@@ -97,16 +79,18 @@ var templateInputTests = []templateTest{
|
||||
BatCurrent: "-10.000",
|
||||
BatPower: "-250.000",
|
||||
InFreq: "50.000",
|
||||
BatCharge: "0.000",
|
||||
OutFreq: "50.000",
|
||||
BatCharge: "100.000",
|
||||
Leds: []string{"Mains", "Float"}},
|
||||
},
|
||||
}
|
||||
|
||||
func TestTemplateInput(t *testing.T) {
|
||||
for i := range templateInputTests {
|
||||
templateInput := buildTemplateInput(templateInputTests[i].input, fakenow)
|
||||
templateInput := buildTemplateInput(templateInputTests[i].input)
|
||||
if !reflect.DeepEqual(templateInput, templateInputTests[i].output) {
|
||||
t.Errorf("buildTemplateInput not producing expected results")
|
||||
fmt.Printf("%v\n%v\n", templateInput, templateInputTests[i].output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user