Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use_flake
48 changes: 48 additions & 0 deletions .github/workflows/poetry-build-test-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Build, Test Release

on:
push:
branches: [ "main", "master" ]
tags:
- "v*"
pull_request:
branches: [ "main", "master" ]

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v2
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Install dependencies
run: |
uv lock
- name: Build
run: |
uv build
- name: Test
run: |
uv run pytest
- name: Test Nix build
run: |
nix build .
- name: Lint with flake8
continue-on-error: true
run: |
# stop the build if there are Python syntax errors or undefined names
uv run flake8 ./src --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
uv run flake8 ./src --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Publish distribution 📦 to PyPI
if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release'
run: |
uv publish --token ${{ secrets.PYPI_TOKEN }}
30 changes: 0 additions & 30 deletions .github/workflows/pypi-release.yml

This file was deleted.

39 changes: 0 additions & 39 deletions .github/workflows/python-app.yml

This file was deleted.

8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.direnv
dist

__pycache__
*.egg-info
.idea

result
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,29 @@ Container:

This lib depends on `pyserial` and the awesome `construct` lib.


## How to run demos

TCP demo:

```bash
uv run python ./demos/test-tcp.py 192.168.1.7 10
```

Serial:

```bash
socat -v pty,link=/tmp/serial,waitslave tcp:192.168.1.7:23,forever
# in another terminal
uv run python ./demos/test-serial.py /tmp/serial 1
```

## How to run mongodb collector

```bash
uv run poller 192.168.1.7 --mongo-url mongodb://mongodb.local:27017 --interval 1000
```

# Hardware wiring
The pylontech modules talk using the RS485 line protocol.
## Pylontech side
Expand Down Expand Up @@ -102,4 +125,5 @@ If you are using US2000 and US3000 batteries, then the main battery must be a US

## Using Pylontech LV Hub with multible battery banks

If the LV hub is used the address of the RS485 devices is depending on the battery bank. To read values the specific device address is needed. To scan for devices on a bank you can use the `scan_for_batteries` function. The max range is 0 to 255.
If the LV hub is used the address of the RS485 devices is depending on the battery bank. To read values the specific device address is needed. To scan for devices on a bank you can use the `scan_for_batteries` function. The max range is 0 to 255.

44 changes: 44 additions & 0 deletions demos/test-serial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from time import sleep

from pylontech import *

if __name__ == '__main__':
iters = 0

import sys
from rich import print_json
import json

# socat -v pty,link=/tmp/serial,waitslave tcp:192.168.10.237:23,forever
if len(sys.argv) < 2:
print("Usage: python test-tcp.py <serialdev> <iterations>")
exit(1)

host = sys.argv[1]
iterations = sys.argv[2]

cont = lambda iter: iter < 1
if iterations == "inf":
cont = lambda iter: True
if iterations != "inf":
cont = lambda iter: iter < int(iterations)

p = Pylontech(SerialDeviceTransport(serial_port=host, baudrate=115200))
bats = p.scan_for_batteries(2, 10)
print("Battery stack:")
print_json(json.dumps(to_json_serializable(bats)))

cc = 0

try:
for b in p.poll_parameters(bats.range()):
cc += 1
if not cont(cc):
break
print("System state:")
print_json(json.dumps(b))
sleep(0.5)
except (KeyboardInterrupt, SystemExit):
exit(0)
except BaseException as e:
raise e
48 changes: 48 additions & 0 deletions demos/test-tcp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from time import sleep

from pylontech import *


if __name__ == '__main__':
"""
Direct TCP connections to devices like Waveshare RS485 to ETH, are 20-50 times faster than
serial port emulation through socat. Turn "RFC2217" option on.
"""
iters = 0

import sys
from rich import print_json
import json

if len(sys.argv) < 2:
print("Usage: python test-tcp.py <telnet host> <iterations>")
exit(1)

host = sys.argv[1]
iterations = sys.argv[2]

cont = lambda iter: iter < 1
if iterations == "inf":
cont = lambda iter: True
if iterations != "inf":
cont = lambda iter: iter < int(iterations)

p = Pylontech(ExscriptTelnetTransport(host=host, port=23))
bats = p.scan_for_batteries(2, 10)
print("Battery stack:")
print_json(json.dumps(to_json_serializable(bats)))

cc = 0

try:
for b in p.poll_parameters(bats.range()):
cc += 1
if not cont(cc):
break
print("System state:")
print_json(json.dumps(b))
sleep(0.5)
except (KeyboardInterrupt, SystemExit):
exit(0)
except BaseException as e:
raise e
133 changes: 133 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading