Skip to content

Commit

Permalink
Initial version.
Browse files Browse the repository at this point in the history
  • Loading branch information
gonzalop committed Sep 17, 2023
0 parents commit 08039e1
Show file tree
Hide file tree
Showing 33 changed files with 3,556 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build/
release/
test/
wombatt
17 changes: 17 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# docker build -t wombatt --build-arg=ARCH=arm64 --build-arg=OS=lianux --build-arg=TARGETPLATFORM=arm64 .
ARG BUILD_IMAGE=golang:1.21
ARG BASE=alpine
FROM --platform=$BUILDPLATFORM ${BUILD_IMAGE} AS build

COPY . /go/src

ARG TARGETPLATFORM
RUN ARCH=$(echo $TARGETPLATFORM |cut -f2 -d/);OS=$(echo $TARGETPLATFORM |cut -f1 -d/); \
cd /go/src; CGO_ENABLED=0 GOOS=${OS} GOARCH=${ARCH} go build -ldflags "-s -w"

FROM --platform=${TARGETPLATFORM} ${BASE}
COPY --from=build /go/src/wombatt /wombatt
# 20 is the dialout group that gives access to serial ports.
RUN echo "wombatt:x:1001:20::/:/bin/nologin" >> /etc/passwd
USER wombatt:dialout
ENTRYPOINT ["/wombatt"]
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright © 2023 Gonzalo Paniagua Javier <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
120 changes: 120 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
MODULE = $(shell $(GO) list -m)
DATE ?= $(shell date +%FT%T%z)
VERSION ?= $(shell git describe --tags --always --dirty --match=v* 2> /dev/null || \
cat .version 2> /dev/null || echo v0)
PKG =
PKGS = $(or $(PKG),$(shell $(GO) list ./...))
BINARY = wombatt

GO = go
TIMEOUT = 15
V = 0
Q = $(if $(filter 1,$V),,@)
M = $(shell if [ "$$(tput colors 2> /dev/null || echo 0)" -ge 8 ]; then printf "\033[34;1m▶\033[0m"; else printf "▶"; fi)

GENERATED = # List of generated files

GOIMPORTS = $(shell which goimports)
GOCOV = $(shell which gocov)
GOCOVXML=$(shell which gocov-xml)
GOTESTSUM=$(shell which gotestsum)
GOLANGCILINT=$(shell which golangci-lint)

.SUFFIXES:
.PHONY: all
#all: fmt golangci-lint-run $(GENERATED) | $(basename $(MODULE)) ; $(info $(M) building executable…) @ ## Build program binary
all: fmt golangci-lint-run $(GENERATED) | wombatt ; $(info $(M) building executable…) @ ## Build program binary

.PHONY: wombatt
wombatt: $(shell find -name \*.go)
$Q CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} $(GO) build \
-tags release \
-ldflags '-s -w -X $(MODULE)/cmd.Version=$(VERSION) -X $(MODULE)/cmd.BuildDate=$(DATE)' \
-o $(BINARY) main.go
# Tools

goimports:
ifeq (, $(GOIMPORTS))
$(error "No goimport in $$PATH, please run 'make install-tools')
endif

gocov:
ifeq (, $(GOCOV))
$(error "No gocov in $$PATH, please run 'make install-tools')
endif

gocov-xml:
ifeq (, $(GOCOVXML))
$(error "No gocov-xml in $$PATH, please run 'make install-tools')
endif

gotestsum:
ifeq (, $(GOTESTSUM))
$(error "No gotestsum in $$PATH, please run 'make install-tools')
endif

golangci-lint:
ifeq (, $(GOLANGCILINT))
$(error "No golangci-lint $$PATH, please run 'make install-tools')
endif

install-tools:
test -x "$(GOIMPORTS)" || go install golang.org/x/tools/cmd/goimports@latest
test -x "$(GOCOV)" || go install github.com/axw/gocov/gocov@latest
test -x "$(GOCOVXML)" || go install github.com/AlekSi/gocov-xml@latest
test -x "$(GOTESTSUM)" || go install gotest.tools/gotestsum@latest
test -x "$(GOLANGCILINT)" || go install github.com/golangci/golangci-lint/cmd/[email protected]

# Generate

# Tests

TEST_TARGETS := test-short test-race
.PHONY: $(TEST_TARGETS) check test tests
test-short: ARGS=-short ## Run only short tests
test-race: ARGS=-race ## Run tests with race detector
$(TEST_TARGETS): NAME=$(MAKECMDGOALS:test-%=%)
$(TEST_TARGETS): test
check test tests: fmt golangci-lint-run $(GENERATED) | gotestsum ; $(info $(M) running $(NAME:%=% )tests…) @ ## Run tests
$Q mkdir -p test
$Q gotestsum --junitfile test/tests.xml -- -timeout $(TIMEOUT)s $(ARGS) $(PKGS)
.PHONY: test-bench
test-bench: $(GENERATED) ; $(info $(M) running benchmarks…) @ ## Run benchmarks
$Q gotestsum -f standard-quiet -- --timeout $(TIMEOUT)s -run=__absolutelynothing__ -bench=. $(PKGS)

COVERAGE_MODE = atomic
.PHONY: test-coverage
test-coverage: fmt golangci-lint-run $(GENERATED)
test-coverage: | gocov gocov-xml gotestsum ; $(info $(M) running coverage tests…) @ ## Run coverage tests
$Q mkdir -p test
$Q gotestsum -- \
-coverpkg=$(shell echo $(PKGS) | tr ' ' ',') \
-covermode=$(COVERAGE_MODE) \
-coverprofile=test/profile.out $(PKGS)
$Q $(GO) tool cover -html=test/profile.out -o test/coverage.html
$Q gocov convert test/profile.out | gocov-xml > test/coverage.xml
@echo -n "Code coverage: "; \
echo "scale=1;$$(sed -En 's/^<coverage line-rate="([0-9.]+)".*/\1/p' test/coverage.xml) * 100 / 1" | bc -q

.PHONY: golangci-lint-run
golangci-lint-run: | golangci-lint ; $(info $(M) running golangci-lint…) @
$Q golangci-lint run

.PHONY: fmt
fmt: | goimports ; $(info $(M) running gofmt…) @ ## Run gofmt on all source files
$Q goimports -local $(MODULE) -w $(shell $(GO) list -f '{{$$d := .Dir}}{{range $$f := .GoFiles}}{{printf "%s/%s\n" $$d $$f}}{{end}}{{range $$f := .CgoFiles}}{{printf "%s/%s\n" $$d $$f}}{{end}}{{range $$f := .TestGoFiles}}{{printf "%s/%s\n" $$d $$f}}{{end}}' $(PKGS))

# Misc

.PHONY: clean
clean: ; $(info $(M) cleaning…) @ ## Cleanup everything
@rm -rf $(PKG) test $(GENERATED) $(BINARY)

.PHONY: help
help:
@grep -hE '^[ a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-17s\033[0m %s\n", $$1, $$2}'

.PHONY: version
version:
@echo $(VERSION)
45 changes: 45 additions & 0 deletions Makefile.release
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
NAME:=wombatt
VERSION:=0.0.1
LINUX_ARCH:=amd64 arm arm64

all:
@echo Use the 'release' target to build a release

release: build tar

.PHONY: build
build:
@go version
@echo Cleaning old builds
@rm -rf build && mkdir build
@echo Building: darwin/amd64 - $(VERSION)
mkdir -p build/darwin/amd64 && GOOS=darwin GOARCH=amd64 $(MAKE) $(NAME) BINARY=build/darwin/amd64/$(NAME)
@echo Building: darwin/arm64 - $(VERSION)
mkdir -p build/darwin/arm64 && GOOS=darwin GOARCH=arm64 $(MAKE) $(NAME) BINARY=build/darwin/arm64/$(NAME)
@echo Building: windows/amd64 - $(VERSION)
mkdir -p build/windows/amd64 && GOOS=windows GOARCH=amd64 $(MAKE) $(NAME) BINARY=build/windows/amd64/$(NAME).exe
@echo Building: linux/$(LINUX_ARCH) - $(VERSION) ;\
for arch in $(LINUX_ARCH); do \
mkdir -p build/linux/$$arch && GOOS=linux GOARCH=$$arch $(MAKE) $(NAME) BINARY=build/linux/$$arch/$(NAME) ;\
done

.PHONY: tar
tar:
@echo Cleaning old releases
@rm -rf release && mkdir release
tar -zcf release/$(NAME)_$(VERSION)_darwin_amd64.tgz -C build/darwin/amd64 $(NAME)
tar -zcf release/$(NAME)_$(VERSION)_darwin_arm64.tgz -C build/darwin/arm64 $(NAME)
tar -zcf release/$(NAME)_$(VERSION)_windows_amd64.tgz -C build/windows/amd64 $(NAME).exe
for arch in $(LINUX_ARCH); do \
tar -zcf release/$(NAME)_$(VERSION)_linux_$$arch.tgz -C build/linux/$$arch $(NAME) ;\
done

.PHONY: version
version:
@echo $(VERSION)

.PHONY: clean
clean:
rm -rf release
rm -rf build
rm -f wombatt
180 changes: 180 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
![wombatt logo](https://github.com/gonzalop/wombatt/blob/main/extras/wombatt-small.jpg?raw=true)
# wombatt

wombatt is a set of tools to monitor batteries and inverters, and to send commands to inverters.
The initial version only supports EG4LLv2 batteries and any inverter using the PI30 protocol family.

## Compilation from Source

To compile wombatt, you need a working Go setup. Then check out the project and run `make` to compile the wombatt binary:

~~~
$ git clone https://github.com/gonzalop/wombatt.git
$ cd wombatt
$ make
~~~

And you'll get a `wombatt` binary.

If you want to cross-compile for linux, windows, and Mac:

~~~
$ git clone https://github.com/gonzalop/wombatt.git
$ cd wombatt
$ make -f Makefile.release release
~~~

And you'll get the different binaries under `build/` and tarfiles under `releases/`.

## Subcommands and usage
Run `wombatt <subcommand> -h` for help on any specific command.

### battery-info
`battery-info` displays battery status information.

For instance, to query the battery with ID #2:
~~~
$ ./wombatt battery-info --serial-port /dev/ttyUSB0 --battery-ids 2
Battery #2
===========
battery voltage: 52.9V
current: 2.5A
cell 1 voltage: 3.305V
cell 2 voltage: 3.306V
cell 3 voltage: 3.306V
cell 4 voltage: 3.306V
cell 5 voltage: 3.306V
cell 6 voltage: 3.307V
cell 7 voltage: 3.306V
cell 8 voltage: 3.307V
cell 9 voltage: 3.306V
cell 10 voltage: 3.307V
cell 11 voltage: 3.306V
cell 12 voltage: 3.306V
cell 13 voltage: 3.306V
cell 14 voltage: 3.307V
cell 15 voltage: 3.306V
cell 16 voltage: 3.307V
pcb temp: 31°C
max temp: 32°C
avg temp: 30°C
cap remaining: 46%
max charging current: 100A
soh: 100%
soc: 47%
status: inactive/charging
warning: 0
protection: 0
error code: 0
cycle counts: 10
full capacity: 100000mAh
temp1: 30°C
temp2: 30°C
temp3: 30°C
temp4: 30°C
temp5: 0
temp6: 0
cell num: 16
designed capacity: 100Ah
cell balance status: 0
max cell voltage: 3.307V
min cell voltage: 3.305V
mean cell voltage: 3.306V
median cell voltage: 3.306V
model: LFP-51.2V100Ah-V1.0
firmware version: Z02T04
serial: 2022-10-26
~~~

If `battery-ids` is omitted, it will scan IDs from 1 to 64.

### inverter-query
`inverter-query` sends PI30 protocol commands to inverters.

Below, an example of running a single command:
~~~
$ ./wombatt inverter-query -p /dev/ttyS1 --commands Q1
2023/09/17 11:35:13.619499 [00001 00006 00 00 07 037 039 043 038 02 00 000 0036 0000 0000 60.00 11 0 060 030 120 030 58.40 000 120 0 0000]
Device: /dev/ttyS1, Command: Q1
========================================
Time until the end of absorb charging: 1s
Time until the end of float charging: 6s
SCC flags: Not communicating
SCC PWM temperature: 37°C
Inverter temperature: 39°C
Battery temperature: 43°C
Transformer temperature: 38°C
GPIO13: 2
Fan lock status: not locked
Fan PWM speed: 36%
SCC charge power: 0W
Parallel warning: 0
Sync frequency: 60Hz
Inverter charger status: bulk stage
~~~

### monitor-inverters
`monitor-inverters` monitors inverters using PI30 protocol, MQTT publishing optional.

The command below will monitor the inverters connected to /dev/ttyS0 and
/dev/ttyS1, run the `Q1`, `QPIGS`, and `QPIRI` commands on both of them,
and `QPGS1` or `QPGS2`.

~~~
$ ./wombatt monitor-inverters -w :9000 --mqtt-broker tcp://127.0.0.1:1883 --mqtt-user youruser --mqtt-password yourpassword /dev/ttyS0,Q1:QPIGS:QPIRI:QPGS2,eg4_1 /dev/ttyS1,Q1:QPIGS:QPIRI:QPGS1,eg4_2
~~~

The information will be published to the specified MQTT maserver with prefixes `eg4_1`
and `eg4_2` depending on the inverter, along HomeAssistant autodiscovery configuration.

The same infomation is made available via web on port 9000
(http://127.0.0.1:9000/inverters/1/Q1 and so on) as text or JSON (add
`?format=json` to the URL), with the ability to request specific
fields (`?fields=<name>`).

### monitor-batteries
`monitor-batteries` monitors batteries state, MQTT publishing optional.

For instance, to monitor batteries with IDs 2 thru 6, and publish to MQTT and a local web page on port 8000, you can run:
~~~
$ ./wombatt monitor-batteries -w :8000 -p /dev/ttyUSB1 --mqtt-broker tcp://127.0.0.1:1883 --mqtt-user youruser --mqtt-password yourpassword --battery-ids 2,3,4,5,6
~~~

The default prefix for the items added to MQTT is `eg4` (i.e., `homeassistant/eg4_battery2_info/...`).

The same infomation is made available via web on port 8000
(http://127.0.0.1:9000/battery/2 and so on) as text or JSON (add
`?format=json` to the URL), with the ability to request specific
fields (`?fields=<name>`).

### modbus-read
`modbus-read` reads registers from a specified device. This is used during development.

To read 38 registers from device ID #2 starting at address 0:
~~~
$ ./wombatt modbus-read -p /dev/ttyUSB1 --id 2 --start 0 --count 38
/wombat modbus-read -p ~/dev/Batteries --id 2 --start 0 --count 38
/dev/ttyUSB1:
00000000 02 03 4c 14 af 00 f0 0c ed 0c ee 0c ed 0c ef 0c |..L.............|
00000010 ed 0c ee 0c ed 0c ee 0c ee 0c ef 0c ee 0c ee 0c |................|
00000020 ed 0c ee 0c ed 0c ee 00 1f 00 20 00 1e 00 30 00 |.......... ...0.|
00000030 64 00 64 00 30 00 01 00 00 00 00 00 00 00 00 00 |d.d.0...........|
00000040 0a 15 75 2a 00 1e 1e 1e 1e 00 00 00 10 03 e8 45 |..u*...........E|
00000050 8d |.|
~~~


### forward
`forward` read/writes between 2 ports, displaying the information exchanged in hexadecimal. This is used during development.

~~~
$ ./wombatt forward --subordinate-port /dev/MasterBattery --controller-port ~/dev/Inverter
2023/09/17 12:10:11.831759 Inverter: 8 010300130010b5c3
2023/09/17 12:10:11.875255 MasterBattery: 32 01032000660000003114ad05c8001e753072d8ea6002040000000a0000000015
2023/09/17 12:10:11.882400 MasterBattery: 5 e000004a8e
~~~

## Reporting bugs and requesting features
Please use https://github.com/gonzalop/wombatt/issues to report any bug, request new features
or support for batteries, inverters, etc.

Loading

0 comments on commit 08039e1

Please sign in to comment.