Skip to content

Commit 54503e7

Browse files
authored
Merge branch 'fsantini:master' into master
2 parents 35e6344 + 004c380 commit 54503e7

20 files changed

Lines changed: 1170 additions & 819 deletions

.github/workflows/make_wheel.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,18 @@ jobs:
1313
runs-on: ubuntu-latest
1414
steps:
1515
- name: Prepare python
16-
uses: actions/setup-python@v4
16+
uses: actions/setup-python@v5
1717
with:
1818
python-version: '3.10'
1919

2020
- name: Install python dependencies
21-
run: pip install wheel twine
21+
run: pip install build
2222

2323
- name: Checkout repository
2424
uses: actions/checkout@v4
2525

2626
- name: Build wheel
27-
run: |
28-
python setup.py sdist bdist_wheel
29-
ls dist
30-
twine check dist/*
27+
run: python -m build
3128

3229
- name: Publish to PyPI
3330
uses: pypa/gh-action-pypi-publish@release/v1

.github/workflows/validate.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,18 @@ jobs:
1111
steps:
1212
- uses: actions/checkout@v4
1313
- name: Set up Python ${{ matrix.python-version }}
14-
uses: actions/setup-python@v4
14+
uses: actions/setup-python@v5
1515
with:
1616
python-version: ${{ matrix.python-version }}
1717
- name: Install dependencies
1818
run: |
1919
python -m pip install --upgrade pip
20-
pip install black flake8 flake8-docstrings isort
20+
pip install .[develop]
2121
- name: Run flake8
2222
run: flake8
2323
- name: Run isort
2424
run: isort ./ --check
2525
- name: Run black
2626
run: black ./ --check
2727
- name: Run test install
28-
run: pip install .
28+
run: pip install .

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.idea
2+
13
# Byte-compiled / optimized / DLL files
24
__pycache__/
35
*.py[cod]

README.md

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ This package can be installed from pip:
3030

3131
`pip install pye3dc`
3232

33-
## Local Connection
34-
35-
### Configuration
33+
## Configuration
3634

3735
There is a great variety of E3/DC implementation configurations, that can't automatically be detected. For example the `index` of the root power meter can be either `0` or `6`, depending how the system was installed. Additional power meter can have an ID of `1-4` and there might be also multiple inverter.
3836
This library assumes, that there is one inverter installed and the root power meter has an index of `6` for S10 mini and `0` for other systems.
@@ -64,7 +62,9 @@ For any other configurations, there is an optional `configuration` object that c
6462

6563
> Note: Not all options need to be configured.
6664
67-
### Usage
65+
## Usage
66+
67+
### Local Connection
6868

6969
An example script using the library is the following:
7070

@@ -75,17 +75,38 @@ TCP_IP = '192.168.1.57'
7575
USERNAME = 'test@test.com'
7676
PASS = 'MySecurePassword'
7777
KEY = 'abc123'
78-
CONFIG = {}
78+
CONFIG = {}
7979
# CONFIG = {"powermeters": [{"index": 6}]}
8080

8181
print("local connection")
82-
e3dc = E3DC(E3DC.CONNECT_LOCAL, username=USERNAME, password=PASS, ipAddress = TCP_IP, key = KEY, configuration = CONFIG)
82+
e3dc_obj = E3DC(E3DC.CONNECT_LOCAL, username=USERNAME, password=PASS, ipAddress = TCP_IP, key = KEY, configuration = CONFIG)
8383
# The following connections are performed through the RSCP interface
84-
print(e3dc.poll())
85-
print(e3dc.get_pvi_data())
84+
print(e3dc_obj.poll(keepAlive=True))
85+
print(e3dc_obj.get_pvi_data(keepAlive=True))
86+
e3dc_obj.disconnect()
87+
```
88+
89+
### Web Connection
90+
91+
An example script using the library is the following:
92+
93+
```python
94+
from e3dc import E3DC
95+
96+
USERNAME = 'test@test.com'
97+
PASS = 'MySecurePassword'
98+
SERIALNUMBER = 'S10-012345678910'
99+
CONFIG = {}
100+
101+
print("web connection")
102+
e3dc_obj = E3DC(E3DC.CONNECT_WEB, username=USERNAME, password=PASS, serialNumber = SERIALNUMBER, isPasswordMd5=False, configuration = CONFIG)
103+
# connect to the portal and poll the status. This might raise an exception in case of failed login. This operation is performed with Ajax
104+
print(e3dc_obj.poll(keepAlive=True))
105+
print(e3dc_obj.get_pvi_data(keepAlive=True))
106+
e3dc_obj.disconnect()
86107
```
87108

88-
### poll() return values
109+
## Example: poll() return values
89110

90111
Poll returns a dictionary like the following:
91112

@@ -108,7 +129,7 @@ Poll returns a dictionary like the following:
108129
}
109130
```
110131

111-
### Available methods
132+
## Available methods
112133

113134
- `poll()`
114135
- `get_system_info()`
@@ -117,47 +138,38 @@ Poll returns a dictionary like the following:
117138
- `get_idle_periods()`
118139
- `set_idle_periods()`
119140
- `get_db_data()`
141+
- `get_batteries()`
120142
- `get_battery_data()`
121143
- `get_batteries_data()`
144+
- `get_pvis()`
122145
- `get_pvi_data()`
123146
- `get_pvis_data()`
147+
- `get_powermeters()`
124148
- `get_powermeter_data()`
125149
- `get_powermeters_data()`
126150
- `get_power_settings()`
151+
- `get_wallbox_data()`
152+
- `set_battery_to_car_mode()`
127153
- `set_power_limits()`
128154
- `set_powersave()`
155+
- `set_wallbox_max_charge_current()`
156+
- `set_wallbox_schuko()`
157+
- `set_wallbox_sunmode()`
129158
- `set_weather_regulated_charge()`
159+
- `toggle_wallbox_charging()`
160+
- `toggle_wallbox_phases()`
161+
162+
- `sendWallboxRequest()`
163+
- `sendWallboxSetRequest()`
130164

131165
See the full documentation on [ReadTheDocs](https://python-e3dc.readthedocs.io/en/latest/)
132166

133-
### Note: The RSCP interface
167+
## Note: The RSCP interface
134168

135169
The communication to an E3/DC system has to be implemented via a rather complicated protocol, called by E3/DC RSCP. This protocol is binary and based on websockets. The documentation provided by E3/DC is limited and outdated. It can be found in the E3/DC download portal.
136170

137171
If keepAlive is false, the websocket connection is closed after the command. This makes sense because these requests are not meant to be made as often as the status requests, however, if keepAlive is True, the connection is left open and kept alive in the background in a separate thread.
138172

139-
## Web connection
140-
141-
### Usage
142-
143-
An example script using the library is the following:
144-
145-
```python
146-
from e3dc import E3DC
147-
148-
TCP_IP = '192.168.1.57'
149-
USERNAME = 'test@test.com'
150-
PASS = 'MySecurePassword'
151-
SERIALNUMBER = '1234567890'
152-
153-
print("web connection")
154-
e3dc = E3DC(E3DC.CONNECT_WEB, username=USERNAME, password=PASS, serialNumber = SERIALNUMBER, isPasswordMd5=False)
155-
# connect to the portal and poll the status. This might raise an exception in case of failed login. This operation is performed with Ajax
156-
print(e3dc.poll())
157-
# Poll the status of the switches using a remote RSCP connection via websockets
158-
# return value is in the format {'id': switchID, 'type': switchType, 'name': switchName, 'status': switchStatus}
159-
print(e3dc.poll_switches())
160-
```
161173

162174
## Known limitations
163175

@@ -174,6 +186,8 @@ One limitation of the package concerns the implemented RSCP methods. This projec
174186

175187
- Open an issue before making a pull request
176188
- Note the E3/DC system you tested with and implementation details
177-
- Pull request checks will enforce code styling (black, flake8, flake8-docstrings, isort)
189+
- Pull request checks will enforce code styling
190+
- Install development dependencies `pip install -U --upgrade-strategy eager .[develop]`
191+
- Run `tools/validate.sh` before creating a commit.
178192
- Make sure to support Python versions >= 3.8
179193
- Consider adding yourself to `AUTHORS`

e3dc/_RSCPEncryptDecrypt.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
from __future__ import annotations # required for python < 3.9
2+
13
import math
24

3-
from py3rijndael import RijndaelCbc, ZeroPadding
5+
from py3rijndael import RijndaelCbc, ZeroPadding # type: ignore
46

57
KEY_SIZE = 32
68
BLOCK_SIZE = 32
@@ -12,7 +14,7 @@ class ParameterError(Exception):
1214
pass
1315

1416

15-
def zeroPad_multiple(string, value):
17+
def zeroPad_multiple(string: bytes, value: int) -> bytes:
1618
"""Zero padding string."""
1719
length = len(string)
1820
if length % value == 0:
@@ -21,7 +23,7 @@ def zeroPad_multiple(string, value):
2123
return string.ljust(newL, b"\x00")
2224

2325

24-
def truncate_multiple(string, value):
26+
def truncate_multiple(string: bytes, value: int) -> bytes:
2527
"""Truncating sting."""
2628
length = len(string)
2729
if length % value == 0:
@@ -33,22 +35,21 @@ def truncate_multiple(string, value):
3335
class RSCPEncryptDecrypt:
3436
"""A class for encrypting and decrypting RSCP data."""
3537

36-
def __init__(self, key):
38+
def __init__(self, key: bytes):
3739
"""Constructor of a RSCP encryption and decryption class.
3840
3941
Args:
40-
key (str): RSCP encryption key
42+
key (bytes): RSCP encryption key
4143
"""
4244
if len(key) > KEY_SIZE:
4345
raise ParameterError("Key must be <%d bytes" % (KEY_SIZE))
44-
4546
self.key = key.ljust(KEY_SIZE, b"\xff")
4647
self.encryptIV = b"\xff" * BLOCK_SIZE
4748
self.decryptIV = b"\xff" * BLOCK_SIZE
4849
self.remainingData = b""
4950
self.oldDecrypt = b""
5051

51-
def encrypt(self, plainText):
52+
def encrypt(self, plainText: bytes) -> bytes:
5253
"""Method to encryt plain text."""
5354
encryptor = RijndaelCbc(
5455
self.key,
@@ -60,7 +61,9 @@ def encrypt(self, plainText):
6061
self.encryptIV = encText[-BLOCK_SIZE:]
6162
return encText
6263

63-
def decrypt(self, encText, previouslyProcessedData=None):
64+
def decrypt(
65+
self, encText: bytes, previouslyProcessedData: int | None = None
66+
) -> bytes:
6467
"""Method to decryt encrypted text."""
6568
if previouslyProcessedData is None:
6669
length = len(self.oldDecrypt)
@@ -92,4 +95,4 @@ def decrypt(self, encText, previouslyProcessedData=None):
9295
padding=ZeroPadding(BLOCK_SIZE),
9396
block_size=BLOCK_SIZE,
9497
)
95-
return decryptor.decrypt(toDecrypt)
98+
return decryptor.decrypt(toDecrypt) # pyright: ignore [reportUnknownMemberType]

e3dc/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Licensed under a MIT license. See LICENSE for details.
66
"""
77

8-
from ._e3dc import E3DC, AuthenticationError, PollError
8+
from ._e3dc import E3DC, AuthenticationError, NotAvailableError, PollError, SendError
99
from ._e3dc_rscp_local import CommunicationError, RSCPAuthenticationError, RSCPKeyError
1010
from ._e3dc_rscp_web import RequestTimeoutError, SocketNotReady
1111
from ._rscpLib import FrameError
@@ -25,4 +25,4 @@
2525
"FrameError",
2626
"set_rscp_debug",
2727
]
28-
__version__ = "0.8.1"
28+
__version__ = "0.9.2"

0 commit comments

Comments
 (0)