Added golangci linting and improved what was required
This commit is contained in:
committed by
ncthompson
parent
206159cdea
commit
2456f45836
29
.golangci.yml
Normal file
29
.golangci.yml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
run:
|
||||||
|
deadline: 10m
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable-all: false
|
||||||
|
enable:
|
||||||
|
# Enabled by default in golangci-lint v1.13.2
|
||||||
|
- deadcode
|
||||||
|
- errcheck
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- typecheck
|
||||||
|
- varcheck
|
||||||
|
# Disabled by default in golangci-lint v1.13.2
|
||||||
|
- dupl
|
||||||
|
- goconst
|
||||||
|
- gofmt
|
||||||
|
- golint
|
||||||
|
- unconvert
|
||||||
|
# The following result in high memory usage (>1GB)
|
||||||
|
# https://github.com/golangci/golangci-lint/issues/337
|
||||||
|
- staticcheck
|
||||||
|
- structcheck
|
||||||
|
- gosimple
|
||||||
|
- unused
|
||||||
|
|
||||||
|
issues:
|
||||||
|
max-per-linter: 0
|
||||||
|
max-same-issues: 0
|
||||||
6
Makefile
6
Makefile
@@ -29,8 +29,7 @@
|
|||||||
.PHONY: test install gofmt docker statik
|
.PHONY: test install gofmt docker statik
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build ./cmd/invertergui/
|
go build ./cmd/...
|
||||||
go build ./cmd/invertercli/
|
|
||||||
|
|
||||||
all: build gofmt test
|
all: build gofmt test
|
||||||
|
|
||||||
@@ -51,3 +50,6 @@ docker:
|
|||||||
|
|
||||||
statik:
|
statik:
|
||||||
statik -p=frontend -src=./frontend/root
|
statik -p=frontend -src=./frontend/root
|
||||||
|
|
||||||
|
lint:
|
||||||
|
golangci-lint run
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/hpdvanwyk/invertergui/frontend"
|
"github.com/hpdvanwyk/invertergui/frontend"
|
||||||
|
"github.com/hpdvanwyk/invertergui/mk2if"
|
||||||
"github.com/hpdvanwyk/invertergui/webgui"
|
"github.com/hpdvanwyk/invertergui/webgui"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
@@ -13,7 +14,7 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
addr := flag.String("addr", ":8080", "TCP address to listen on.")
|
addr := flag.String("addr", ":8080", "TCP address to listen on.")
|
||||||
mk2 := NewMk2Mock()
|
mk2 := mk2if.NewMk2Mock()
|
||||||
gui := webgui.NewWebGui(mk2)
|
gui := webgui.NewWebGui(mk2)
|
||||||
|
|
||||||
http.Handle("/", frontend.NewStatic())
|
http.Handle("/", frontend.NewStatic())
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hpdvanwyk/invertergui/mk2if"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mock struct {
|
|
||||||
c chan *mk2if.Mk2Info
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMk2Mock() mk2if.Mk2If {
|
|
||||||
tmp := &mock{
|
|
||||||
c: make(chan *mk2if.Mk2Info, 1),
|
|
||||||
}
|
|
||||||
go tmp.genMockValues()
|
|
||||||
return tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
func genBaseLeds(state mk2if.LEDstate) map[mk2if.Led]mk2if.LEDstate {
|
|
||||||
return map[mk2if.Led]mk2if.LEDstate{
|
|
||||||
mk2if.LedMain: state,
|
|
||||||
mk2if.LedAbsorption: state,
|
|
||||||
mk2if.LedBulk: state,
|
|
||||||
mk2if.LedFloat: state,
|
|
||||||
mk2if.LedInverter: state,
|
|
||||||
mk2if.LedOverload: state,
|
|
||||||
mk2if.LedLowBattery: state,
|
|
||||||
mk2if.LedTemperature: state,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mock) GetMk2Info() *mk2if.Mk2Info {
|
|
||||||
return &mk2if.Mk2Info{
|
|
||||||
OutCurrent: 2.0,
|
|
||||||
InCurrent: 2.3,
|
|
||||||
OutVoltage: 230.0,
|
|
||||||
InVoltage: 230.1,
|
|
||||||
BatVoltage: 25,
|
|
||||||
BatCurrent: -10,
|
|
||||||
InFrequency: 50,
|
|
||||||
OutFrequency: 50,
|
|
||||||
ChargeState: 1,
|
|
||||||
Errors: nil,
|
|
||||||
Timestamp: time.Now(),
|
|
||||||
LEDs: genBaseLeds(mk2if.LedOff),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mock) C() chan *mk2if.Mk2Info {
|
|
||||||
return m.c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mock) Close() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mock) genMockValues() {
|
|
||||||
mult := 1.0
|
|
||||||
ledState := mk2if.LedOff
|
|
||||||
for {
|
|
||||||
input := &mk2if.Mk2Info{
|
|
||||||
OutCurrent: 2.0 * mult,
|
|
||||||
InCurrent: 2.3 * mult,
|
|
||||||
OutVoltage: 230.0 * mult,
|
|
||||||
InVoltage: 230.1 * mult,
|
|
||||||
BatVoltage: 25 * mult,
|
|
||||||
BatCurrent: -10 * mult,
|
|
||||||
InFrequency: 50 * mult,
|
|
||||||
OutFrequency: 50 * mult,
|
|
||||||
ChargeState: 1 * mult,
|
|
||||||
Errors: nil,
|
|
||||||
Timestamp: time.Now(),
|
|
||||||
Valid: true,
|
|
||||||
LEDs: genBaseLeds(ledState),
|
|
||||||
}
|
|
||||||
|
|
||||||
ledState = (ledState + 1) % 3
|
|
||||||
|
|
||||||
mult = mult - 0.1
|
|
||||||
if mult < 0 {
|
|
||||||
mult = 1.0
|
|
||||||
}
|
|
||||||
fmt.Printf("Sending\n")
|
|
||||||
m.c <- input
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,14 +3,15 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/hpdvanwyk/invertergui/mk2if"
|
|
||||||
"github.com/mikepb/go-serial"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/hpdvanwyk/invertergui/mk2if"
|
||||||
|
"github.com/mikepb/go-serial"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Basic CLI to serve as example lib usage
|
// Basic CLI to serve as example lib usage
|
||||||
@@ -46,10 +47,11 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer p.Close()
|
defer p.Close()
|
||||||
mk2, err := mk2if.NewMk2Connection(p)
|
mk2, err := mk2if.NewMk2Connection(p)
|
||||||
defer mk2.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
defer mk2.Close()
|
||||||
|
|
||||||
c := mk2.C()
|
c := mk2.C()
|
||||||
sigterm := make(chan os.Signal, 1)
|
sigterm := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigterm, syscall.SIGTERM, os.Interrupt)
|
signal.Notify(sigterm, syscall.SIGTERM, os.Interrupt)
|
||||||
|
|||||||
@@ -76,10 +76,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer p.Close()
|
defer p.Close()
|
||||||
mk2, err := mk2if.NewMk2Connection(p)
|
mk2, err := mk2if.NewMk2Connection(p)
|
||||||
defer mk2.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
defer mk2.Close()
|
||||||
|
|
||||||
gui := webgui.NewWebGui(mk2)
|
gui := webgui.NewWebGui(mk2)
|
||||||
|
|
||||||
|
|||||||
@@ -7,20 +7,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type static struct {
|
|
||||||
http.FileSystem
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStatic() http.Handler {
|
func NewStatic() http.Handler {
|
||||||
statikFs, err := fs.New()
|
statikFs, err := fs.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return &static{
|
return http.FileServer(statikFs)
|
||||||
FileSystem: statikFs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *static) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.FileServer(s).ServeHTTP(w, r)
|
|
||||||
}
|
}
|
||||||
|
|||||||
211
mk2if/mk2.go
211
mk2if/mk2.go
@@ -16,70 +16,67 @@ type scaling struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type mk2Ser struct {
|
type mk2Ser struct {
|
||||||
info *Mk2Info
|
info *Mk2Info
|
||||||
report *Mk2Info
|
p io.ReadWriter
|
||||||
p io.ReadWriter
|
scales []scaling
|
||||||
sc []scaling
|
scaleCount int
|
||||||
scN int
|
run chan struct{}
|
||||||
run chan struct{}
|
frameLock bool
|
||||||
locked bool
|
infochan chan *Mk2Info
|
||||||
sync.RWMutex
|
wg sync.WaitGroup
|
||||||
infochan chan *Mk2Info
|
|
||||||
wg sync.WaitGroup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMk2Connection(dev io.ReadWriter) (Mk2If, error) {
|
func NewMk2Connection(dev io.ReadWriter) (Mk2If, error) {
|
||||||
mk2 := &mk2Ser{}
|
mk2 := &mk2Ser{}
|
||||||
mk2.p = dev
|
mk2.p = dev
|
||||||
mk2.info = &Mk2Info{}
|
mk2.info = &Mk2Info{}
|
||||||
mk2.report = &Mk2Info{}
|
mk2.scaleCount = 0
|
||||||
mk2.scN = 0
|
mk2.frameLock = false
|
||||||
mk2.locked = false
|
mk2.scales = make([]scaling, 0, 14)
|
||||||
mk2.sc = make([]scaling, 0)
|
|
||||||
mk2.setTarget()
|
mk2.setTarget()
|
||||||
mk2.run = make(chan struct{})
|
mk2.run = make(chan struct{})
|
||||||
mk2.infochan = make(chan *Mk2Info)
|
mk2.infochan = make(chan *Mk2Info)
|
||||||
mk2.wg.Add(1)
|
mk2.wg.Add(1)
|
||||||
go mk2.frameLock()
|
go mk2.frameLocker()
|
||||||
return mk2, nil
|
return mk2, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locks to incoming frame.
|
// Locks to incoming frame.
|
||||||
func (mk2 *mk2Ser) frameLock() {
|
func (m *mk2Ser) frameLocker() {
|
||||||
|
|
||||||
frame := make([]byte, 256)
|
frame := make([]byte, 256)
|
||||||
var size byte
|
var size byte
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-mk2.run:
|
case <-m.run:
|
||||||
mk2.wg.Done()
|
m.wg.Done()
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
if mk2.locked {
|
if m.frameLock {
|
||||||
size = mk2.readByte()
|
size = m.readByte()
|
||||||
l, err := io.ReadFull(mk2.p, frame[0:int(size)+1])
|
l, err := io.ReadFull(m.p, frame[0:int(size)+1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mk2.addError(fmt.Errorf("Read Error: %v", err))
|
m.addError(fmt.Errorf("Read Error: %v", err))
|
||||||
mk2.locked = false
|
m.frameLock = false
|
||||||
} else if l != int(size)+1 {
|
} else if l != int(size)+1 {
|
||||||
mk2.addError(errors.New("Read Length Error"))
|
m.addError(errors.New("Read Length Error"))
|
||||||
mk2.locked = false
|
m.frameLock = false
|
||||||
} else {
|
} else {
|
||||||
mk2.handleFrame(size, frame[0:int(size+1)])
|
m.handleFrame(size, frame[0:int(size+1)])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tmp := mk2.readByte()
|
tmp := m.readByte()
|
||||||
if tmp == 0xff || tmp == 0x20 {
|
if tmp == 0xff || tmp == 0x20 {
|
||||||
l, err := io.ReadFull(mk2.p, frame[0:int(size)])
|
l, err := io.ReadFull(m.p, frame[0:int(size)])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mk2.addError(fmt.Errorf("Read Error: %v", err))
|
m.addError(fmt.Errorf("Read Error: %v", err))
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
} else if l != int(size) {
|
} else if l != int(size) {
|
||||||
mk2.addError(errors.New("Read Length Error"))
|
m.addError(errors.New("Read Length Error"))
|
||||||
} else {
|
} else {
|
||||||
if checkChecksum(size, tmp, frame[0:int(size)]) {
|
if checkChecksum(size, tmp, frame[0:int(size)]) {
|
||||||
mk2.locked = true
|
m.frameLock = true
|
||||||
log.Printf("Locked")
|
log.Printf("Locked")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,109 +87,98 @@ func (mk2 *mk2Ser) frameLock() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Close Mk2
|
// Close Mk2
|
||||||
func (mk2 *mk2Ser) Close() {
|
func (m *mk2Ser) Close() {
|
||||||
close(mk2.run)
|
close(m.run)
|
||||||
mk2.wg.Wait()
|
m.wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns last known state with all reported errors since previous poll.
|
func (m *mk2Ser) C() chan *Mk2Info {
|
||||||
// Mk2Info.Valid will be false if no polling has completed.
|
return m.infochan
|
||||||
func (mk2 *mk2Ser) GetMk2Info() *Mk2Info {
|
|
||||||
mk2.RLock()
|
|
||||||
defer mk2.RUnlock()
|
|
||||||
return mk2.report
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mk2 *mk2Ser) C() chan *Mk2Info {
|
func (m *mk2Ser) readByte() byte {
|
||||||
return mk2.infochan
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mk2 *mk2Ser) readByte() byte {
|
|
||||||
buffer := make([]byte, 1)
|
buffer := make([]byte, 1)
|
||||||
_, err := io.ReadFull(mk2.p, buffer)
|
_, err := io.ReadFull(m.p, buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mk2.addError(fmt.Errorf("Read error: %v", err))
|
m.addError(fmt.Errorf("Read error: %v", err))
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return buffer[0]
|
return buffer[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds error to error slice.
|
// Adds error to error slice.
|
||||||
func (mk2 *mk2Ser) addError(err error) {
|
func (m *mk2Ser) addError(err error) {
|
||||||
if mk2.info.Errors == nil {
|
if m.info.Errors == nil {
|
||||||
mk2.info.Errors = make([]error, 0)
|
m.info.Errors = make([]error, 0)
|
||||||
}
|
}
|
||||||
mk2.info.Errors = append(mk2.info.Errors, err)
|
m.info.Errors = append(m.info.Errors, err)
|
||||||
mk2.info.Valid = false
|
m.info.Valid = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates report.
|
// Updates report.
|
||||||
func (mk2 *mk2Ser) updateReport() {
|
func (m *mk2Ser) updateReport() {
|
||||||
mk2.Lock()
|
m.info.Timestamp = time.Now()
|
||||||
defer mk2.Unlock()
|
|
||||||
mk2.info.Timestamp = time.Now()
|
|
||||||
mk2.report = mk2.info
|
|
||||||
select {
|
select {
|
||||||
case mk2.infochan <- mk2.info:
|
case m.infochan <- m.info:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
mk2.info = &Mk2Info{}
|
m.info = &Mk2Info{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks for valid frame and chooses decoding.
|
// Checks for valid frame and chooses decoding.
|
||||||
func (mk2 *mk2Ser) handleFrame(l byte, frame []byte) {
|
func (m *mk2Ser) handleFrame(l byte, frame []byte) {
|
||||||
if checkChecksum(l, frame[0], frame[1:]) {
|
if checkChecksum(l, frame[0], frame[1:]) {
|
||||||
switch frame[0] {
|
switch frame[0] {
|
||||||
case 0xff:
|
case 0xff:
|
||||||
switch frame[1] {
|
switch frame[1] {
|
||||||
case 0x56: // V
|
case 0x56: // V
|
||||||
mk2.versionDecode(frame[2:])
|
m.versionDecode(frame[2:])
|
||||||
case 0x57:
|
case 0x57:
|
||||||
switch frame[2] {
|
switch frame[2] {
|
||||||
case 0x8e:
|
case 0x8e:
|
||||||
mk2.scaleDecode(frame[2:])
|
m.scaleDecode(frame[2:])
|
||||||
case 0x85:
|
case 0x85:
|
||||||
mk2.stateDecode(frame[2:])
|
m.stateDecode(frame[2:])
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x4C: // L
|
case 0x4C: // L
|
||||||
mk2.ledDecode(frame[2:])
|
m.ledDecode(frame[2:])
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x20:
|
case 0x20:
|
||||||
switch frame[5] {
|
switch frame[5] {
|
||||||
case 0x0C:
|
case 0x0C:
|
||||||
mk2.dcDecode(frame[1:])
|
m.dcDecode(frame[1:])
|
||||||
case 0x08:
|
case 0x08:
|
||||||
mk2.acDecode(frame[1:])
|
m.acDecode(frame[1:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Failed")
|
log.Printf("Invalid incoming frame checksum: %x", frame)
|
||||||
mk2.locked = false
|
m.frameLock = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the target VBus device.
|
// Set the target VBus device.
|
||||||
func (mk *mk2Ser) setTarget() {
|
func (m *mk2Ser) setTarget() {
|
||||||
cmd := make([]byte, 3)
|
cmd := make([]byte, 3)
|
||||||
cmd[0] = 0x41 // A
|
cmd[0] = 0x41 // A
|
||||||
cmd[1] = 0x01
|
cmd[1] = 0x01
|
||||||
cmd[2] = 0x00
|
cmd[2] = 0x00
|
||||||
mk.sendCommand(cmd)
|
m.sendCommand(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request the scaling factor for entry 'in'.
|
// Request the scaling factor for entry 'in'.
|
||||||
func (mk *mk2Ser) reqScaleFactor(in byte) {
|
func (m *mk2Ser) reqScaleFactor(in byte) {
|
||||||
cmd := make([]byte, 4)
|
cmd := make([]byte, 4)
|
||||||
cmd[0] = 0x57 // W
|
cmd[0] = 0x57 // W
|
||||||
cmd[1] = 0x36
|
cmd[1] = 0x36
|
||||||
cmd[2] = in
|
cmd[2] = in
|
||||||
mk.sendCommand(cmd)
|
m.sendCommand(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the scale factor frame.
|
// Decode the scale factor frame.
|
||||||
func (mk *mk2Ser) scaleDecode(frame []byte) {
|
func (m *mk2Ser) scaleDecode(frame []byte) {
|
||||||
scl := uint16(frame[2])<<8 + uint16(frame[1])
|
scl := uint16(frame[2])<<8 + uint16(frame[1])
|
||||||
ofs := int16(uint16(frame[5])<<8 + uint16(frame[4]))
|
ofs := int16(uint16(frame[5])<<8 + uint16(frame[4]))
|
||||||
|
|
||||||
@@ -203,11 +189,11 @@ func (mk *mk2Ser) scaleDecode(frame []byte) {
|
|||||||
} else {
|
} else {
|
||||||
tmp.scale = math.Abs(float64(scl))
|
tmp.scale = math.Abs(float64(scl))
|
||||||
}
|
}
|
||||||
mk.sc = append(mk.sc, tmp)
|
m.scales = append(m.scales, tmp)
|
||||||
|
|
||||||
mk.scN++
|
m.scaleCount++
|
||||||
if mk.scN < 14 {
|
if m.scaleCount < 14 {
|
||||||
mk.reqScaleFactor(byte(mk.scN))
|
m.reqScaleFactor(byte(m.scaleCount))
|
||||||
} else {
|
} else {
|
||||||
log.Print("Monitoring starting.")
|
log.Print("Monitoring starting.")
|
||||||
}
|
}
|
||||||
@@ -215,28 +201,28 @@ func (mk *mk2Ser) scaleDecode(frame []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decode the version number
|
// Decode the version number
|
||||||
func (mk *mk2Ser) versionDecode(frame []byte) {
|
func (m *mk2Ser) versionDecode(frame []byte) {
|
||||||
mk.info.Version = 0
|
m.info.Version = 0
|
||||||
mk.info.Valid = true
|
m.info.Valid = true
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
mk.info.Version += uint32(frame[i]) << uint(i) * 8
|
m.info.Version += uint32(frame[i]) << uint(i) * 8
|
||||||
}
|
}
|
||||||
|
|
||||||
if mk.scN < 14 {
|
if m.scaleCount < 14 {
|
||||||
log.Print("Get scaling factors.")
|
log.Print("Get scaling factors.")
|
||||||
mk.reqScaleFactor(byte(mk.scN))
|
m.reqScaleFactor(byte(m.scaleCount))
|
||||||
} else {
|
} else {
|
||||||
// Send DC status request
|
// Send DC status request
|
||||||
cmd := make([]byte, 2)
|
cmd := make([]byte, 2)
|
||||||
cmd[0] = 0x46 //F
|
cmd[0] = 0x46 //F
|
||||||
cmd[1] = 0
|
cmd[1] = 0
|
||||||
mk.sendCommand(cmd)
|
m.sendCommand(cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply scaling to float
|
// Apply scaling to float
|
||||||
func (mk *mk2Ser) applyScale(value float64, scale int) float64 {
|
func (m *mk2Ser) applyScale(value float64, scale int) float64 {
|
||||||
return mk.sc[scale].scale * (value + mk.sc[scale].offset)
|
return m.scales[scale].scale * (value + m.scales[scale].offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert bytes->int16->float
|
// Convert bytes->int16->float
|
||||||
@@ -250,57 +236,57 @@ func getUnsigned(data []byte) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decodes DC frame.
|
// Decodes DC frame.
|
||||||
func (mk *mk2Ser) dcDecode(frame []byte) {
|
func (m *mk2Ser) dcDecode(frame []byte) {
|
||||||
mk.info.BatVoltage = mk.applyScale(getSigned(frame[5:7]), 4)
|
m.info.BatVoltage = m.applyScale(getSigned(frame[5:7]), 4)
|
||||||
|
|
||||||
usedC := mk.applyScale(getUnsigned(frame[7:10]), 5)
|
usedC := m.applyScale(getUnsigned(frame[7:10]), 5)
|
||||||
chargeC := mk.applyScale(getUnsigned(frame[10:13]), 5)
|
chargeC := m.applyScale(getUnsigned(frame[10:13]), 5)
|
||||||
mk.info.BatCurrent = usedC - chargeC
|
m.info.BatCurrent = usedC - chargeC
|
||||||
|
|
||||||
mk.info.OutFrequency = 10 / (mk.applyScale(float64(frame[13]), 7))
|
m.info.OutFrequency = 10 / (m.applyScale(float64(frame[13]), 7))
|
||||||
|
|
||||||
// Send L1 status request
|
// Send L1 status request
|
||||||
cmd := make([]byte, 2)
|
cmd := make([]byte, 2)
|
||||||
cmd[0] = 0x46 //F
|
cmd[0] = 0x46 //F
|
||||||
cmd[1] = 1
|
cmd[1] = 1
|
||||||
mk.sendCommand(cmd)
|
m.sendCommand(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decodes AC frame.
|
// Decodes AC frame.
|
||||||
func (mk *mk2Ser) acDecode(frame []byte) {
|
func (m *mk2Ser) acDecode(frame []byte) {
|
||||||
mk.info.InVoltage = mk.applyScale(getSigned(frame[5:7]), 0)
|
m.info.InVoltage = m.applyScale(getSigned(frame[5:7]), 0)
|
||||||
mk.info.InCurrent = mk.applyScale(getSigned(frame[7:9]), 1)
|
m.info.InCurrent = m.applyScale(getSigned(frame[7:9]), 1)
|
||||||
mk.info.OutVoltage = mk.applyScale(getSigned(frame[9:11]), 2)
|
m.info.OutVoltage = m.applyScale(getSigned(frame[9:11]), 2)
|
||||||
mk.info.OutCurrent = mk.applyScale(getSigned(frame[11:13]), 3)
|
m.info.OutCurrent = m.applyScale(getSigned(frame[11:13]), 3)
|
||||||
|
|
||||||
if frame[13] == 0xff {
|
if frame[13] == 0xff {
|
||||||
mk.info.InFrequency = 0
|
m.info.InFrequency = 0
|
||||||
} else {
|
} else {
|
||||||
mk.info.InFrequency = 10 / (mk.applyScale(float64(frame[13]), 8))
|
m.info.InFrequency = 10 / (m.applyScale(float64(frame[13]), 8))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send status request
|
// Send status request
|
||||||
cmd := make([]byte, 1)
|
cmd := make([]byte, 1)
|
||||||
cmd[0] = 0x4C //F
|
cmd[0] = 0x4C //F
|
||||||
mk.sendCommand(cmd)
|
m.sendCommand(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode charge state of battery.
|
// Decode charge state of battery.
|
||||||
func (mk *mk2Ser) stateDecode(frame []byte) {
|
func (m *mk2Ser) stateDecode(frame []byte) {
|
||||||
mk.info.ChargeState = mk.applyScale(getSigned(frame[1:3]), 13)
|
m.info.ChargeState = m.applyScale(getSigned(frame[1:3]), 13)
|
||||||
mk.updateReport()
|
m.updateReport()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the LED state frame.
|
// Decode the LED state frame.
|
||||||
func (mk *mk2Ser) ledDecode(frame []byte) {
|
func (m *mk2Ser) ledDecode(frame []byte) {
|
||||||
|
|
||||||
mk.info.LEDs = getLEDs(frame[0], frame[1])
|
m.info.LEDs = getLEDs(frame[0], frame[1])
|
||||||
// Send charge state request
|
// Send charge state request
|
||||||
cmd := make([]byte, 4)
|
cmd := make([]byte, 4)
|
||||||
cmd[0] = 0x57 //W
|
cmd[0] = 0x57 //W
|
||||||
cmd[1] = 0x30
|
cmd[1] = 0x30
|
||||||
cmd[2] = 13
|
cmd[2] = 13
|
||||||
mk.sendCommand(cmd)
|
m.sendCommand(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds active LEDs to list.
|
// Adds active LEDs to list.
|
||||||
@@ -322,7 +308,7 @@ func getLEDs(ledsOn, ledsBlink byte) map[Led]LEDstate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adds header and trailing crc for frame to send.
|
// Adds header and trailing crc for frame to send.
|
||||||
func (mk2 *mk2Ser) sendCommand(data []byte) {
|
func (m *mk2Ser) sendCommand(data []byte) {
|
||||||
l := len(data)
|
l := len(data)
|
||||||
dataOut := make([]byte, l+3)
|
dataOut := make([]byte, l+3)
|
||||||
dataOut[0] = byte(l + 1)
|
dataOut[0] = byte(l + 1)
|
||||||
@@ -334,9 +320,9 @@ func (mk2 *mk2Ser) sendCommand(data []byte) {
|
|||||||
}
|
}
|
||||||
dataOut[l+2] = cr
|
dataOut[l+2] = cr
|
||||||
|
|
||||||
_, err := mk2.p.Write(dataOut)
|
_, err := m.p.Write(dataOut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mk2.addError(fmt.Errorf("Write error: %v", err))
|
m.addError(fmt.Errorf("Write error: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,8 +332,5 @@ func checkChecksum(l, t byte, d []byte) bool {
|
|||||||
for i := 0; i < len(d); i++ {
|
for i := 0; i < len(d); i++ {
|
||||||
cr = (cr + uint16(d[i])) % 256
|
cr = (cr + uint16(d[i])) % 256
|
||||||
}
|
}
|
||||||
if cr == 0 {
|
return cr == 0
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ type Mk2Info struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Mk2If interface {
|
type Mk2If interface {
|
||||||
GetMk2Info() *Mk2Info
|
|
||||||
C() chan *Mk2Info
|
C() chan *Mk2Info
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|||||||
71
mk2if/mockmk2.go
Normal file
71
mk2if/mockmk2.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package mk2if
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mock struct {
|
||||||
|
c chan *Mk2Info
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMk2Mock() Mk2If {
|
||||||
|
tmp := &mock{
|
||||||
|
c: make(chan *Mk2Info, 1),
|
||||||
|
}
|
||||||
|
go tmp.genMockValues()
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
func genBaseLeds(state LEDstate) map[Led]LEDstate {
|
||||||
|
return map[Led]LEDstate{
|
||||||
|
LedMain: state,
|
||||||
|
LedAbsorption: state,
|
||||||
|
LedBulk: state,
|
||||||
|
LedFloat: state,
|
||||||
|
LedInverter: state,
|
||||||
|
LedOverload: state,
|
||||||
|
LedLowBattery: state,
|
||||||
|
LedTemperature: state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mock) C() chan *Mk2Info {
|
||||||
|
return m.c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mock) Close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mock) genMockValues() {
|
||||||
|
mult := 1.0
|
||||||
|
ledState := LedOff
|
||||||
|
for {
|
||||||
|
input := &Mk2Info{
|
||||||
|
OutCurrent: 2.0 * mult,
|
||||||
|
InCurrent: 2.3 * mult,
|
||||||
|
OutVoltage: 230.0 * mult,
|
||||||
|
InVoltage: 230.1 * mult,
|
||||||
|
BatVoltage: 25 * mult,
|
||||||
|
BatCurrent: -10 * mult,
|
||||||
|
InFrequency: 50 * mult,
|
||||||
|
OutFrequency: 50 * mult,
|
||||||
|
ChargeState: 1 * mult,
|
||||||
|
Errors: nil,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Valid: true,
|
||||||
|
LEDs: genBaseLeds(ledState),
|
||||||
|
}
|
||||||
|
|
||||||
|
ledState = (ledState + 1) % 3
|
||||||
|
|
||||||
|
mult = mult - 0.1
|
||||||
|
if mult < 0 {
|
||||||
|
mult = 1.0
|
||||||
|
}
|
||||||
|
fmt.Printf("Sending\n")
|
||||||
|
m.c <- input
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,8 +33,9 @@ package webgui
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/hpdvanwyk/invertergui/mk2if"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/hpdvanwyk/invertergui/mk2if"
|
||||||
)
|
)
|
||||||
|
|
||||||
type muninData struct {
|
type muninData struct {
|
||||||
@@ -46,7 +47,7 @@ func (w *WebGui) ServeMuninHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||||||
muninDat := <-w.muninRespChan
|
muninDat := <-w.muninRespChan
|
||||||
if muninDat.timesUpdated == 0 {
|
if muninDat.timesUpdated == 0 {
|
||||||
rw.WriteHeader(500)
|
rw.WriteHeader(500)
|
||||||
rw.Write([]byte("No data to return.\n"))
|
_, _ = rw.Write([]byte("No data to return.\n"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
calcMuninAverages(&muninDat)
|
calcMuninAverages(&muninDat)
|
||||||
@@ -75,7 +76,7 @@ func (w *WebGui) ServeMuninHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||||||
fmt.Fprintf(outputBuf, "freqin.value %s\n", tmpInput.InFreq)
|
fmt.Fprintf(outputBuf, "freqin.value %s\n", tmpInput.InFreq)
|
||||||
fmt.Fprintf(outputBuf, "freqout.value %s\n", tmpInput.OutFreq)
|
fmt.Fprintf(outputBuf, "freqout.value %s\n", tmpInput.OutFreq)
|
||||||
|
|
||||||
_, err := rw.Write([]byte(outputBuf.String()))
|
_, err := rw.Write(outputBuf.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%v\n", err)
|
fmt.Printf("%v\n", err)
|
||||||
}
|
}
|
||||||
@@ -171,7 +172,7 @@ freqout.label Out frequency (Hz)
|
|||||||
|
|
||||||
//Munin only samples once every 5 minutes so averages have to be calculated for some values.
|
//Munin only samples once every 5 minutes so averages have to be calculated for some values.
|
||||||
func calcMuninValues(muninDat *muninData, newStatus *mk2if.Mk2Info) {
|
func calcMuninValues(muninDat *muninData, newStatus *mk2if.Mk2Info) {
|
||||||
muninDat.timesUpdated += 1
|
muninDat.timesUpdated++
|
||||||
muninVal := &muninDat.status
|
muninVal := &muninDat.status
|
||||||
muninVal.OutCurrent += newStatus.OutCurrent
|
muninVal.OutCurrent += newStatus.OutCurrent
|
||||||
muninVal.InCurrent += newStatus.InCurrent
|
muninVal.InCurrent += newStatus.InCurrent
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ package webgui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -49,7 +50,6 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type WebGui struct {
|
type WebGui struct {
|
||||||
respChan chan *mk2if.Mk2Info
|
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
|
|
||||||
muninRespChan chan muninData
|
muninRespChan chan muninData
|
||||||
@@ -178,14 +178,16 @@ func (w *WebGui) Stop() {
|
|||||||
func (w *WebGui) dataPoll() {
|
func (w *WebGui) dataPoll() {
|
||||||
pollChan := w.poller.C()
|
pollChan := w.poller.C()
|
||||||
var muninValues muninData
|
var muninValues muninData
|
||||||
s := &mk2if.Mk2Info{}
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case s = <-pollChan:
|
case s := <-pollChan:
|
||||||
if s.Valid {
|
if s.Valid {
|
||||||
calcMuninValues(&muninValues, s)
|
calcMuninValues(&muninValues, s)
|
||||||
w.pu.updatePrometheus(s)
|
w.pu.updatePrometheus(s)
|
||||||
w.hub.Broadcast(buildTemplateInput(s))
|
err := w.hub.Broadcast(buildTemplateInput(s))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not send update to clients: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case w.muninRespChan <- muninValues:
|
case w.muninRespChan <- muninValues:
|
||||||
zeroMuninValues(&muninValues)
|
zeroMuninValues(&muninValues)
|
||||||
|
|||||||
@@ -32,10 +32,11 @@ package webgui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/hpdvanwyk/invertergui/mk2if"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hpdvanwyk/invertergui/mk2if"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWebGui(t *testing.T) {
|
func TestWebGui(t *testing.T) {
|
||||||
@@ -61,27 +62,28 @@ var templateInputTests = []templateTest{
|
|||||||
InFrequency: 50,
|
InFrequency: 50,
|
||||||
OutFrequency: 50,
|
OutFrequency: 50,
|
||||||
ChargeState: 1,
|
ChargeState: 1,
|
||||||
LedListOn: []int{mk2if.LED_MAIN, mk2if.LED_FLOAT},
|
LEDs: map[mk2if.Led]mk2if.LEDstate{mk2if.LedMain: mk2if.LedOn},
|
||||||
Errors: nil,
|
Errors: nil,
|
||||||
Timestamp: fakenow,
|
Timestamp: fakenow,
|
||||||
},
|
},
|
||||||
output: &templateInput{
|
output: &templateInput{
|
||||||
Error: nil,
|
Error: nil,
|
||||||
Date: fakenow.Format(time.RFC1123Z),
|
Date: fakenow.Format(time.RFC1123Z),
|
||||||
OutCurrent: "2.000",
|
OutCurrent: "2.00",
|
||||||
OutVoltage: "230.000",
|
OutVoltage: "230.00",
|
||||||
OutPower: "460.000",
|
OutPower: "460.00",
|
||||||
InCurrent: "2.300",
|
InCurrent: "2.30",
|
||||||
InVoltage: "230.100",
|
InVoltage: "230.10",
|
||||||
InPower: "529.230",
|
InPower: "529.23",
|
||||||
InMinOut: "69.230",
|
InMinOut: "69.23",
|
||||||
BatVoltage: "25.000",
|
BatVoltage: "25.00",
|
||||||
BatCurrent: "-10.000",
|
BatCurrent: "-10.00",
|
||||||
BatPower: "-250.000",
|
BatPower: "-250.00",
|
||||||
InFreq: "50.000",
|
InFreq: "50.00",
|
||||||
OutFreq: "50.000",
|
OutFreq: "50.00",
|
||||||
BatCharge: "100.000",
|
BatCharge: "100.00",
|
||||||
Leds: []string{"Mains", "Float"}},
|
LedMap: map[string]string{"led_mains": "dot-green"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ const (
|
|||||||
|
|
||||||
// Send pings to peer with this period. Must be less than pongWait.
|
// Send pings to peer with this period. Must be less than pongWait.
|
||||||
pingPeriod = (pongWait * 9) / 10
|
pingPeriod = (pongWait * 9) / 10
|
||||||
|
|
||||||
// Maximum message size allowed from peer.
|
|
||||||
maxMessageSize = 512
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
var upgrader = websocket.Upgrader{
|
||||||
@@ -52,10 +49,10 @@ func (c *Client) writePump() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case message, ok := <-c.send:
|
case message, ok := <-c.send:
|
||||||
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
_ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
if !ok {
|
if !ok {
|
||||||
// The hub closed the channel.
|
// The hub closed the channel.
|
||||||
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
_ = c.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,13 +60,13 @@ func (c *Client) writePump() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Write(message)
|
_, _ = w.Write(message)
|
||||||
|
|
||||||
if err := w.Close(); err != nil {
|
if err := w.Close(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
_ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func (h *Hub) Broadcast(message interface{}) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
h.broadcast <- []byte(payload)
|
h.broadcast <- payload
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user