Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,6 @@ dmypy.json

# Pyre type checker
.pyre/

.vscode/
.idea/
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Makefile ;)

dist: clean
python -m build
twine check dist/*

push:
twine upload dist/*

all: dist push

clean:
find . -type f -name *.pyc -delete
find . -type d -name __pycache__ -delete
rm -rf build
rm -rf dist
rm -rf sonnenbatterie/*.egg-info
rm -rf sonnenbatterie2/*.egg-info
rm -rf timeofuse/*.egg-info

.phony: dist
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,63 @@
# python_sonnenbatterie

Python module to access the REST API of a [Sonnenbatterie](https://sonnenusa.com/en/products/).

This modules contains two distinct sets of APIs:
- the original sonnen API (v1)
- the new v2 API

The major difference between those two is the method to authenticate. With API v1 you need
to login using a username and a password. The newer API requires a token that can be found
in the web UI of your Sonnenbatterie.
Also, the v2 API provides a unified view across the different Sonnenbatterie models wheres
the v1 API may provide more details in specific setups.

When using the v1 API you'll autmatically get access to the v2 API by using the `sb2`
attribute of the v1 client object.

## Installation

### Using `pip`

``` bash
pip3 install sonnenbatterie
```

### Manual installation
[Download the archive from pypi.org](https://pypi.org/project/sonnenbatterie/#files) and unpack where needed ;)

## Usage

``` python
from sonnenbatterie import sonnenbatterie

sb_host = '192.168.1.2'
sb_user = 'User'
sb_pass = 'Password'

# Init class, establish connection
sb = sonnenbatterie(sb_host, sb_user, sb_pass)

print(sb.get_status()) # retrieve general information
print(sb.get_powermeters()) # retrieive power meter details
print(sb.get_batterysystem()) # retrieve battery system data
print(sb.get_inverter()) # retrieve inverter status
print(sb.get_systemdata()) # retrieve system data
print(sb.get_battery()) # get battery information

# API v2
# can either be access directly, see below, or
# via sb.sb2 (gets initialiazed automatically when creating a V1 object)

from sonnebatterie2 import SonnenBatterieV2
sb_token = 'SeCrEtToKeN' # retrieve via Web UI of SonnenBatterie

sb2 = SonnenBatterieV2(sb_host, sb_token)
print(sb2.get_configurations()) # retrieve configuration overview
print(sb2.get_battery_module_data()) # get battery module data
print(sb2.get_inverter_data()) # retrieve inverter data
print(sb2.get_latest_data()) # get latest date from sonnenbatterie
print(sb2_get_powermeter_data()) # get data from power meters
print(sb2.get_status_data()) # get overall status information
print(sb2.get_io_data()) # get io status
```
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[build-system]
requires = [
"setuptools>=42",
"wheel",
]
build-backend = "setuptools.build_meta"
29 changes: 29 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[metadata]
name = sonnenbatterie
version = 0.3.0
author = Jan Weltmeyer
description = "Access Sonnenbatterie REST API"
long_description = file: README.md
long_description_content_type = text/markdown
license = GPLv3
url = https://github.com/weltmeyer/python_sonnenbatterie
project_urls =
Issue Tracker = https://github.com/weltmeyer/python_sonnenbatterie/issues
classifiers =
Development Status :: 4 - Beta
Programming Language :: Python :: 3.8
Operating System :: OS Independent
Environment :: Console
License :: OSI Approved :: GNU General Public License v3 (GPLv3)

[options]
package_dir =
= .
packages = find:
include_package_data = True
python_requires = >= 3.8
install_requires =
requests

[options.packages.find]
where = .
25 changes: 2 additions & 23 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,4 @@
import pathlib
from setuptools import setup
import setuptools

with open("README.md", "r") as fh:
long_description = fh.read()

setup(
name="sonnenbatterie", # Replace with your own username
version="0.2.7",
author="Jan Weltmeyer",
author_email="author@example.com",
description="Access Sonnenbatterie REST API",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/weltmeyer/python_sonnenbatterie",
packages=["sonnenbatterie"],
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
"Development Status :: 3 - Alpha",
],
python_requires='>=3.6',
install_requires=["requests"],
)
setuptools.setup()
15 changes: 8 additions & 7 deletions sonnenbatterie/const.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
from datetime import time
#from requests import Timeout


DEFAULT_BATTERY_LOGIN_TIMEOUT=120
DEFAULT_CONNECT_TO_BATTERY_TIMEOUT=60
DEFAULT_READ_FROM_BATTERY_TIMEOUT=60
Expand All @@ -12,8 +8,13 @@
SONNEN_OPERATING_MODE_AUTOMATIC_SELF_CONSUMPTION_NAME="Automatic - Self-Consumption"
SONNEN_OPERATING_MODE_BATTERY_MODULE_EXTENSION_30_PERCENT_NAME="Battery-Module-Extension (30%)"
SONNEN_OPERATING_MODE_TIME_OF_USE_NAME="Time-Of-Use"
SONNEN_OPERATING_MODE_NAMES_TO_OPERATING_MODES = {SONNEN_OPERATING_MODE_MANUAL_NAME :"1", SONNEN_OPERATING_MODE_AUTOMATIC_SELF_CONSUMPTION_NAME:"2", SONNEN_OPERATING_MODE_BATTERY_MODULE_EXTENSION_30_PERCENT_NAME:"6", SONNEN_OPERATING_MODE_TIME_OF_USE_NAME:"10"}
SONNEN_OPERATING_MODES_TO_OPERATING_MODE_NAMES= {v:k for k,v in SONNEN_OPERATING_MODE_NAMES_TO_OPERATING_MODES.items()}
SONNEN_OPERATING_MODE_NAMES_TO_OPERATING_MODES = {
SONNEN_OPERATING_MODE_MANUAL_NAME :"1",
SONNEN_OPERATING_MODE_AUTOMATIC_SELF_CONSUMPTION_NAME:"2",
SONNEN_OPERATING_MODE_BATTERY_MODULE_EXTENSION_30_PERCENT_NAME:"6",
SONNEN_OPERATING_MODE_TIME_OF_USE_NAME:"10"
}
SONNEN_OPERATING_MODES_TO_OPERATING_MODE_NAMES = {v:k for k,v in SONNEN_OPERATING_MODE_NAMES_TO_OPERATING_MODES.items()}

SONNEN_OPEATING_MODES=[SONNEN_OPERATING_MODE_MANUAL_NAME, SONNEN_OPERATING_MODE_AUTOMATIC_SELF_CONSUMPTION_NAME, SONNEN_OPERATING_MODE_BATTERY_MODULE_EXTENSION_30_PERCENT_NAME,SONNEN_OPERATING_MODE_TIME_OF_USE_NAME ]

Expand All @@ -32,4 +33,4 @@
SONNEN_API_PATH_BATTERY="battery"

SONNEN_CHARGE_PATH="charge"
SONNEN_DISCHARGE_PATH="discharge"
SONNEN_DISCHARGE_PATH="discharge"
74 changes: 25 additions & 49 deletions sonnenbatterie/sonnenbatterie.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import requests
import sys
sys.path.append("..")
import hashlib
import json
# pylint: disable=unused-wildcard-import
import requests

from sonnenbatterie2 import SonnenBatterieV2
from .const import *
# pylint: enable=unused-wildcard-import
from .timeofuse import timeofuseschedule


class sonnenbatterie:
def __init__(self,username,password,ipaddress):
Expand All @@ -20,6 +21,8 @@ def __init__(self,username,password,ipaddress):

self._login()

self.sb2 = SonnenBatterieV2(ip_address=self.ipaddress, api_token=self.token)


def _login(self):
password_sha512 = hashlib.sha512(self.password.encode('utf-8')).hexdigest()
Expand Down Expand Up @@ -105,24 +108,9 @@ def _post(self, what, isretry=False):
# looking at the status.state_battery_inout value
# irritatingly there is no mechanism in the API to do a single set to you have to work out if
# the direction of the flow and then call the appropriate API
def set_manual_flowrate(self, direction, rate, isretry=False):
path=self.setpoint+direction+"/"+str(rate)
response = self._post(path)
return (response.status_code == 201)

def set_discharge(self, rate):
return self.set_manual_flowrate(SONNEN_DISCHARGE_PATH, rate)


def set_charge(self, rate):
return self.set_manual_flowrate(SONNEN_CHARGE_PATH, rate)

# more general purpose endpoints
def set_configuration(self, name, value):
# All configurations names and values are hendled as strings, so force that
payload = {str(name): str(value)}
return self._put(SONNEN_API_PATH_CONFIGURATIONS, payload)

# API v1 calls
def get_powermeter(self):
return self._get(SONNEN_API_PATH_POWER_METER)

Expand All @@ -140,15 +128,19 @@ def get_status(self):

def get_battery(self):
return self._get(SONNEN_API_PATH_BATTERY)


# API v2 calls
def set_configuration(self, name, value):
return self.sb2.set_config_item(name, value)

def get_latest_data(self):
return self._get(SONNEN_API_PATH_LATEST_DATA)
return self.sb2.get_latest_data()

def get_configurations(self):
return self._get(SONNEN_API_PATH_CONFIGURATIONS)
return self.sb2.get_configurations()

def get_configuration(self, name):
return self._get(SONNEN_API_PATH_CONFIGURATIONS+"/"+name).get(name)
return self.sb2.get_config_item(name)


# these have special handling in some form, for example converting a mode as a number into a string
Expand All @@ -159,8 +151,8 @@ def get_operating_mode(self):
return self.get_configuration(SONNEN_CONFIGURATION_OPERATING_MODE)

def get_operating_mode_name(self):
operating_mode_num = self.get_operating_mode()
return SONNEN_OPERATING_MODES_TO_OPERATING_MODE_NAMES.get(operating_mode_num)
operating_mode = self.get_operating_mode()
return SONNEN_OPERATING_MODES_TO_OPERATING_MODE_NAMES.get(operating_mode[SONNEN_CONFIGURATION_OPERATING_MODE])

def set_operating_mode(self, operating_mode):
return self.set_configuration(SONNEN_CONFIGURATION_OPERATING_MODE, operating_mode)
Expand All @@ -174,37 +166,21 @@ def get_battery_reserve(self):
def set_battery_reserve(self, reserve=5):
reserve = int(reserve)
if (reserve < 0) or (reserve > 100):
raise Exception("Reserve must be between 0 and 100, you specified "+reserve)
raise Exception(f"Reserve must be between 0 and 100, you specified {reserve}")
return self.set_configuration(SONNEN_CONFIGURATION_BACKUP_RESERVE, reserve)

# set the reserve to the current battery level adjusted by the offset if provided
# (a positive offset means that the reserve will be set to more than the current level
# a negative offser means less than the current level)
# If the new reserve is less than the minimum reserve then use the minimum reserve
# the reserve will be tested to ensure it's >= 0 or <= 100
def set_battery_reserve_relative_to_currentCharge(self, offset=0, minimum_reserve=0):
def set_battery_reserve_relative_to_current_charge(self, offset=0, minimum_reserve=0):
current_level = self.get_current_charge_level()
target_level = current_level +offset
if (target_level < minimum_reserve):
if target_level < minimum_reserve:
target_level = minimum_reserve
if (target_level < 0) :
if target_level < 0:
target_level = 0
elif (target_level > 100):
elif target_level > 100:
target_level = 100
return self.set_battery_reserve(target_level)

def get_time_of_use_schedule_as_string(self):
return self.get_configuration(SONNEN_CONFIGURATION_TOU_SCHEDULE)

def get_time_of_use_schedule_as_json_objects(self):
return json.loads(self.get_configuration(SONNEN_CONFIGURATION_TOU_SCHEDULE))

def get_time_of_use_schedule_as_schedule(self)-> timeofuseschedule:
current_schedule = self.get_time_of_use_schedule_as_json_objects()
return timeofuseschedule.build_from_json(current_schedule)

# In this case the schedule is a array representation of an array of dictionary formatted time of use entries, each entry has a start time and stop time and a threshold_p_max (max grid power for the entire building including charging)
def set_time_of_use_schedule_from_json_objects(self, schedule):
return self.set_configuration(SONNEN_CONFIGURATION_TOU_SCHEDULE, json.dumps(schedule))


1 change: 1 addition & 0 deletions sonnenbatterie2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .sonnenbatterie2 import SonnenBatterieV2
Loading
Loading