Skip to content

Commit

Permalink
Merge branch 'master' into generation-daily-filter
Browse files Browse the repository at this point in the history
# Conflicts:
#	go.mod
#	main.go
  • Loading branch information
kubaceg committed Nov 5, 2024
2 parents fcd77e1 + 4c9737f commit 7d7cc02
Show file tree
Hide file tree
Showing 21 changed files with 474 additions and 166 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: 1.21

- name: Build
run: go build -v ./...
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
name: release

on:
release:
types: [created]
Expand All @@ -8,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: wangyoucao577/go-release-action@v1.34
- uses: wangyoucao577/go-release-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: linux
Expand All @@ -21,10 +23,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: wangyoucao577/go-release-action@v1.34
- uses: wangyoucao577/go-release-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: linux
goarch: arm
compress_assets: OFF
md5sum: FALSE
md5sum: FALSE
8 changes: 4 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/sofar
sofar_g3_lsw3_logger_reader
/bin
config.yaml
sofar
sofar-x86
sofar-arm
.idea
.vscode
.idea
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
all: build-arm build-x86

build-arm:
env GOOS=linux GOARCH=arm GOARM=5 go build -o sofar-arm
env GOOS=linux GOARCH=arm GOARM=5 go build -o bin/sofar-arm

build:
go build -o sofar
go build -o bin/sofar-x86

test:
go test -v ./...
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Sofar g3 LSW-3 logger reader

[![Go](https://github.com/kubaceg/sofar_g3_lsw3_logger_reader/actions/workflows/go.yml/badge.svg?branch=master)](https://github.com/kubaceg/sofar_g3_lsw3_logger_reader/actions/workflows/go.yml)[![release](https://github.com/kubaceg/sofar_g3_lsw3_logger_reader/actions/workflows/release.yaml/badge.svg?event=release)](https://github.com/kubaceg/sofar_g3_lsw3_logger_reader/actions/workflows/release.yaml)

Tool written in GO for reading metrics from Sofar LSW-3 and writing results into MQTT topics.
Program queries logger modbus port in infinite loop and sends data into MQTT topics (e.g. mosquito in HomeAssistant).

Expand All @@ -9,7 +12,7 @@ Program queries logger modbus port in infinite loop and sends data into MQTT top
4. Copy example config `cp config-example.yaml config.yaml`
5. Edit `config.yaml` in Your favorite editor, fill all required stuff
6. Build program `make build` or build for ARM machines e.g. raspberryPi `make build-arm`
7. Run `./sofar` or `sofar-arm`
7. Run `bin/sofar` or `bin/sofar-arm`

## Output data format
### MQTT
Expand All @@ -20,6 +23,9 @@ Data will be sent into MQTT topic with name `{mqttPrefix}/{fieldName}` where:
Full topic name for given example values is `/sensors/energy/inverter/PV_Generation_Today`.
Additional field is `All` which contains all measurements and their values marshalled into one json.

### Home Assistant
This tool can integrate with Home Assistant using MQTT protocol. If You want to configure MQTT along with [discovery](https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery) feature, just fill 'ha_discovery_prefix' and all sensors will be configured automatically in Your Home Assistant instance.

### OTLP
Data can also be sent over OTLP protocol to a gRPC or http server. Typically, this would be received by the
[OTel-Collector](https://opentelemetry.io/docs/collector/) for further export to any required platform.
Expand Down
5 changes: 3 additions & 2 deletions adapters/comms/serial/serial.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package serial

import (
"fmt"
"io"
"log"
"log/slog"
"time"

"go.bug.st/serial"
Expand Down Expand Up @@ -35,7 +36,7 @@ func New(name string, baud int, dataBits int, parityMode serial.Parity, stopBits
func (s *serialPort) Open() error {
if s.serialPort != nil {
if err := s.Close(); err != nil {
log.Printf("error during serial port connection close: %s", err)
slog.Error(fmt.Sprintf("error during serial port connection close: %s", err))
}
}

Expand Down
5 changes: 1 addition & 4 deletions adapters/comms/tcpip/tcpip.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package tcpip

import (
"bufio"
"fmt"
"net"
"time"
Expand Down Expand Up @@ -51,13 +50,11 @@ func (s *tcpIpPort) Read(buf []byte) (int, error) {
return 0, fmt.Errorf("connection is not open")
}

reader := bufio.NewReader(s.conn)

if err := s.conn.SetReadDeadline(time.Now().Add(timeout)); err != nil {
return 0, err
}

return reader.Read(buf)
return s.conn.Read(buf)
}

func (s *tcpIpPort) Write(payload []byte) (int, error) {
Expand Down
65 changes: 58 additions & 7 deletions adapters/devices/sofar/device.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,72 @@
package sofar

import "github.com/kubaceg/sofar_g3_lsw3_logger_reader/ports"
import (
"fmt"
"log/slog"
"regexp"

"github.com/kubaceg/sofar_g3_lsw3_logger_reader/ports"
)

type Logger struct {
serialNumber uint
connPort ports.CommunicationPort
serialNumber uint
connPort ports.CommunicationPort
attrWhiteList map[string]struct{}
attrBlackList []*regexp.Regexp
}

// for a set in go we use a map of keys -> empty struct
func toSet(slice []string) map[string]struct{} {
set := make(map[string]struct{}, len(slice))
v := struct{}{}
for _, s := range slice {
set[s] = v
}
return set
}

func toREs(patterns []string) []*regexp.Regexp {
res := make([]*regexp.Regexp, 0, len(patterns))
for idx, p := range patterns {
re, err := regexp.Compile(p)
if err == nil {
res = append(res, re)
} else {
slog.Warn(fmt.Sprintf("config attrBlackList item %d '%s' not a valid regexp; %v", idx, p, err))
}
}
return res
}

func NewSofarLogger(serialNumber uint, connPort ports.CommunicationPort) *Logger {
func NewSofarLogger(serialNumber uint, connPort ports.CommunicationPort, attrWhiteList []string, attrBlackList []string) *Logger {
return &Logger{
serialNumber: serialNumber,
connPort: connPort,
serialNumber: serialNumber,
connPort: connPort,
attrWhiteList: toSet(attrWhiteList),
attrBlackList: toREs(attrBlackList),
}
}

func (s *Logger) nameFilter(k string) bool {
if len(s.attrWhiteList) > 0 {
if _, ok := s.attrWhiteList[k]; ok {
return true
}
}
for _, re := range s.attrBlackList {
if re.MatchString(k) {
return false
}
}
return true
}

func (s *Logger) GetDiscoveryFields() []ports.DiscoveryField {
return getDiscoveryFields(s.nameFilter)
}

func (s *Logger) Query() (map[string]interface{}, error) {
return readData(s.connPort, s.serialNumber)
return readData(s.connPort, s.serialNumber, s.nameFilter)
}

func (s *Logger) Name() string {
Expand Down
49 changes: 49 additions & 0 deletions adapters/devices/sofar/device_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package sofar

import (
"testing"
"regexp"
)

func TestNameFilter(t *testing.T) {
// Create a Logger instance with the desired attributes
logger := &Logger{
attrWhiteList: map[string]struct{}{
"whitelisted": {},
},
attrBlackList: []*regexp.Regexp{
regexp.MustCompile("^blacklisted"),
},
}

// Test case 1: Key in the white list
result := logger.nameFilter("whitelisted")
if result != true {
t.Errorf("Expected: true, Got: %v", result)
}

// Test case 2: Key not in the white list, but not matching any black list regex
result = logger.nameFilter("notblacklisted")
if result != true {
t.Errorf("Expected: true, Got: %v", result)
}

// Test case 3: Key in the black list
result = logger.nameFilter("blacklisted-key")
if result != false {
t.Errorf("Expected: false, Got: %v", result)
}

// Test case 4: Key not in the white list and matches a black list regex
result = logger.nameFilter("blacklisted")
if result != false {
t.Errorf("Expected: false, Got: %v", result)
}

// Test case 5: No white or black list
logger = &Logger{} // Reset the logger
result = logger.nameFilter("anykey")
if result != true {
t.Errorf("Expected: true, Got: %v", result)
}
}
43 changes: 21 additions & 22 deletions adapters/devices/sofar/lsw.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package sofar
import (
"encoding/binary"
"fmt"
"log"
"log/slog"

"github.com/sigurn/crc16"

Expand Down Expand Up @@ -73,17 +73,18 @@ func (l LSWRequest) checksum(buf []byte) uint8 {
return checksum
}

func readData(connPort ports.CommunicationPort, serialNumber uint) (map[string]interface{}, error) {
func readData(connPort ports.CommunicationPort, serialNumber uint, nameFilter func(string) bool) (map[string]interface{}, error) {
result := make(map[string]interface{})

for _, rr := range allRegisterRanges {
reply, err := readRegisterRange(rr, connPort, serialNumber)
if err != nil {
return nil, err
}

for k, v := range reply {
result[k] = v
if nameFilter(k) {
result[k] = v
}
}
}
return result, nil
Expand All @@ -101,7 +102,7 @@ func readRegisterRange(rr registerRange, connPort ports.CommunicationPort, seria

defer func(connPort ports.CommunicationPort) {
if err := connPort.Close(); err != nil {
log.Printf("error during connection close: %s", err)
slog.Error(fmt.Sprintf("error during connection close: %s", err))
}
}(connPort)

Expand All @@ -111,27 +112,25 @@ func readRegisterRange(rr registerRange, connPort ports.CommunicationPort, seria
return nil, err
}

// read the result
buf := make([]byte, 2048)
n, err := connPort.Read(buf)
if err != nil {
return nil, err
}

// truncate the buffer
buf = buf[:n]
if len(buf) < 48 {
// short reply
return nil, fmt.Errorf("short reply: %d bytes", n)
// read enough bytes
buf := []byte{}
for {
b := make([]byte, 2048)
n, err := connPort.Read(b)
if n > 0 {
buf = append(buf, b[:n]...)
}
if err != nil {
return nil, err
}
if len(buf) >= 28 && len(buf) >= 28+int(buf[27]) {
break
}
}

replyBytesCount := buf[27]

modbusReply := buf[28 : 28+replyBytesCount]
modbusReply := buf[28 : 28+buf[27]]

// shove the data into the reply
reply := make(map[string]interface{})

for _, f := range rr.replyFields {
fieldOffset := (f.register - rr.start) * 2

Expand Down
Loading

0 comments on commit 7d7cc02

Please sign in to comment.