Skip to content

Commit

Permalink
Version bump to [0.3.14]
Browse files Browse the repository at this point in the history
  • Loading branch information
jrester committed Jan 11, 2022
1 parent 1776b07 commit dbf4493
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 55 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [0.3.14]

- revert changes from 0.3.11:
- meters can now be accessed using the old, direct method (e.g. `meters.solar.instant_power`)
- if a meter is not available a `MeterNotAvailableError` will be thrown
- move from `distutils.version` to `packaging.version`

## [0.3.13]

Implement `system_status` endpoint (https://github.com/jrester/tesla_powerwall/issues/31):
Expand Down
23 changes: 10 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Python Tesla Powerwall API for consuming a local endpoint. The API is by no mean

> Note: This is not an official API provided by Tesla and as such might fail at any time.
Powerwall Software versions from 1.45.0 to 1.50.1 as well as 20.40 to 21.35.0 are tested, but others will probably work too. If you encounter an error regarding a change in the API of the Powerwall because your Powerwall has a different version than listed here please open an Issue to report this change so it can be fixed.
Powerwall Software versions from 1.45.0 to 1.50.1 as well as 20.40 to 21.39.1 are tested, but others will probably work too. If you encounter an error regarding a change in the API of the Powerwall because your Powerwall has a different version than listed here please open an Issue to report this change so it can be fixed.

> For more information about versioning see [API versioning](#api-versioning).
Expand Down Expand Up @@ -272,15 +272,20 @@ from tesla_powerwall import MeterType
meters = powerwall.get_meters()
#=> <MetersAggregates ...>

# access meter, but may return None when meter is not available
meters.get_meter(MeterType.SOLAR)
#=> <Meter ...>

```
# access meter, but may raise MeterNotAvailableError when the meter is not available at your powerwall (e.g. no solar panels installed)
meters.solar
#=> <Meter ...>

Available meters are: `solar`, `site`, `load` and `battery`. If you have a generator you can also access it with the `generator` MeterType.
# get all available meters at the current powerwall
meters.meters
#=> [<MeterType.SITE: 'site'>, <MeterType.BATTERY: 'battery'>, <MeterType.LOAD: 'load'>, <MeterType.SOLAR: 'solar'>]
```

> Note: if the powerwall you are working with has no solar panels installed `get_meter(MeterType.SOLAR)` returns `None`
> With the attribute `MetersAggregates.meters` you can get the available meters in the response
Available meters are: `solar`, `site`, `load`, `battery` and `generator`. Some of those meters might not be available based on the installation and raise MeterNotAvailableError when accessed.

#### Current power supply/draw

Expand All @@ -306,14 +311,6 @@ meters.battery.is_active(precision=5)

> Note: For MeterType.LOAD `is_drawing_from` **always** returns `False` because it cannot be drawn from `load`.

`Meter.get_power` is just a convenience method which is equivalent to:

```python
from tesla_powerwall.helpers import convert_to_kw

convert_to_kw(meters.solar.instant_power, precision=1)
```

#### Energy exported/imported

Get energy exported/imported in watts with `energy_exported` and `energy_imported`. For the values in kWh use `get_energy_exported` and `get_energy_imported`:
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
name="tesla_powerwall",
author="Jrester",
author_email="[email protected]",
version='0.3.13',
version='0.3.14',
description="API for Tesla Powerwall",
long_description=long_description,
long_description_content_type="text/markdown",
Expand All @@ -17,5 +17,5 @@
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
],
install_requires=["requests>=2.22.0"],
install_requires=["requests>=2.22.0", "packaging>=20.5"],
)
3 changes: 2 additions & 1 deletion tesla_powerwall/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
MissingAttributeError,
PowerwallError,
PowerwallUnreachableError,
MeterNotAvailableError,
)
from .helpers import assert_attribute, convert_to_kw
from .responses import (
Expand All @@ -32,4 +33,4 @@
)
from .powerwall import Powerwall

VERSION = "0.3.13"
VERSION = "0.3.14"
11 changes: 11 additions & 0 deletions tesla_powerwall/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,14 @@ def __init__(self, resource, error=None, message=None):
else:
msg = "{}: {}".format(msg, error)
super().__init__(msg)


class MeterNotAvailableError(PowerwallError):
def __init__(self, meter, available_meters):
self.meter = meter
self.available_meters = available_meters
super().__init__(
"Meter {} is not available at your powerwall. Following meters are available: {} ".format(
meter.value, available_meters
)
)
16 changes: 11 additions & 5 deletions tesla_powerwall/powerwall.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Union, List
import requests
from distutils import version
from packaging import version

from .api import API
from .const import (
Expand Down Expand Up @@ -87,7 +87,9 @@ def get_charge(self) -> float:
return assert_attribute(self._api.get_system_status_soe(), "percentage", "soe")

def get_energy(self) -> int:
return assert_attribute(self._api.get_system_status(), "nominal_energy_remaining", "system_status")
return assert_attribute(
self._api.get_system_status(), "nominal_energy_remaining", "system_status"
)

def get_sitemaster(self) -> SiteMaster:
return SiteMaster(self._api.get_sitemaster())
Expand All @@ -104,10 +106,14 @@ def get_grid_status(self) -> GridStatus:
return GridStatus(status)

def get_capacity(self) -> float:
return assert_attribute(self._api.get_system_status(), "nominal_full_pack_energy", "system_status")
return assert_attribute(
self._api.get_system_status(), "nominal_full_pack_energy", "system_status"
)

def get_batteries(self) -> List[Battery]:
batteries = assert_attribute(self._api.get_system_status(), "battery_blocks", "system_status")
batteries = assert_attribute(
self._api.get_system_status(), "battery_blocks", "system_status"
)
return [Battery(battery) for battery in batteries]

def is_grid_services_active(self) -> bool:
Expand Down Expand Up @@ -177,7 +183,7 @@ def pin_version(self, vers: Union[str, version.Version]):
if isinstance(vers, version.Version):
self._pin_version = vers
else:
self._pin_version = version.LooseVersion(vers)
self._pin_version = version.Version(vers)

def get_pinned_version(self) -> version.Version:
return self._pin_version
Expand Down
17 changes: 14 additions & 3 deletions tesla_powerwall/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Roles,
)
from .helpers import assert_attribute, convert_to_kw
from .error import MeterNotAvailableError


class Response:
Expand Down Expand Up @@ -106,6 +107,16 @@ def __init__(self, response):
super().__init__(response)
self.meters = [MeterType(key) for key in response.keys()]

def __getattribute__(self, attr):
if attr.upper() in MeterType.__dict__:
m = MeterType(attr)
if m in self.meters:
return self.get_meter(m)
else:
raise MeterNotAvailableError(m, self.meters)
else:
return object.__getattribute__(self, attr)

def get_meter(self, meter: MeterType) -> Meter:
if meter in self.meters:
return Meter(meter, self.assert_attribute(meter.value))
Expand Down Expand Up @@ -276,8 +287,8 @@ def model(self):
def power_rating_watts(self):
return self.assert_attribute("power_rating_watts")

class Battery(Response):

class Battery(Response):
@property
def part_number(self):
return self.assert_attribute("PackagePartNumber")
Expand Down Expand Up @@ -310,7 +321,7 @@ def energy_remaining(self) -> int:
Returns:
int: energy in watts
"""
"""
return self.assert_attribute("nominal_energy_remaining")

@property
Expand All @@ -329,4 +340,4 @@ def wobble_detected(self) -> bool:
Returns:
bool: detected
"""
return self.assert_attribute("wobble_detected")
return self.assert_attribute("wobble_detected")
1 change: 1 addition & 0 deletions tests/integration/test_powerwall.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from tests.integration import POWERWALL_IP, POWERWALL_PASSWORD


class TestPowerwall(unittest.TestCase):
def setUp(self) -> None:
self.powerwall = Powerwall(POWERWALL_IP)
Expand Down
47 changes: 22 additions & 25 deletions tests/unit/test_powerwall.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import json
import os
import unittest
import datetime

from distutils import version
import requests
from packaging import version
import responses
from responses import GET, POST, Response, add

from tesla_powerwall import (
API,
AccessDeniedError,
APIError,
Meter,
MetersAggregates,
Powerwall,
PowerwallUnreachableError,
MeterNotAvailableError,
SiteMaster,
SiteInfo,
GridStatus,
DeviceType,
assert_attribute,
Expand Down Expand Up @@ -50,10 +44,10 @@ def test_get_api(self):

def test_pins_version_on_creation(self):
pw = Powerwall(ENDPOINT, pin_version="1.49.0")
self.assertEqual(pw.get_pinned_version(), version.LooseVersion("1.49.0"))
self.assertEqual(pw.get_pinned_version(), version.Version("1.49.0"))

pw = Powerwall(ENDPOINT, pin_version=version.LooseVersion("1.49.0"))
self.assertEqual(pw.get_pinned_version(), version.LooseVersion("1.49.0"))
pw = Powerwall(ENDPOINT, pin_version=version.Version("1.49.0"))
self.assertEqual(pw.get_pinned_version(), version.Version("1.49.0"))

@responses.activate
def test_get_charge(self):
Expand Down Expand Up @@ -95,17 +89,10 @@ def test_get_meters(self):
meters.meters,
[MeterType.SITE, MeterType.BATTERY, MeterType.LOAD, MeterType.SOLAR],
)
self.assertIsInstance(meters.load, Meter)
self.assertIsInstance(meters.get_meter(MeterType.LOAD), Meter)

@responses.activate
def test_meter(self):
add(
Response(
responses.GET,
url=f"{ENDPOINT}meters/aggregates",
json=METERS_AGGREGATES_RESPONSE,
)
)
with self.assertRaises(MeterNotAvailableError):
meters.generator

@responses.activate
def test_is_sending(self):
Expand Down Expand Up @@ -203,14 +190,18 @@ def test_get_backup_reserved_percentage(self):
add(
Response(responses.GET, url=f"{ENDPOINT}operation", json=OPERATION_RESPONSE)
)
self.assertEqual(self.powerwall.get_backup_reserve_percentage(), 5.000019999999999)
self.assertEqual(
self.powerwall.get_backup_reserve_percentage(), 5.000019999999999
)

@responses.activate
def test_get_operation_mode(self):
add(
Response(responses.GET, url=f"{ENDPOINT}operation", json=OPERATION_RESPONSE)
)
self.assertEqual(self.powerwall.get_operation_mode(), OperationMode.SELF_CONSUMPTION)
self.assertEqual(
self.powerwall.get_operation_mode(), OperationMode.SELF_CONSUMPTION
)

@responses.activate
def test_get_version(self):
Expand All @@ -220,14 +211,20 @@ def test_get_version(self):
@responses.activate
def test_detect_and_pin_version(self):
add(Response(responses.GET, url=f"{ENDPOINT}status", json=STATUS_RESPONSE))
vers = version.LooseVersion("1.50.1")
vers = version.Version("1.50.1")
pw = Powerwall(ENDPOINT)
self.assertEqual(pw.detect_and_pin_version(), vers)
self.assertEqual(pw._pin_version, vers)

@responses.activate
def test_system_status(self):
add(Response(responses.GET, url=f"{ENDPOINT}system_status", json=SYSTEM_STATUS_RESPONSE))
add(
Response(
responses.GET,
url=f"{ENDPOINT}system_status",
json=SYSTEM_STATUS_RESPONSE,
)
)
self.assertEqual(self.powerwall.get_capacity(), 28078)
self.assertEqual(self.powerwall.get_energy(), 14807)

Expand Down
12 changes: 6 additions & 6 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
[tox]
envlist = py39
envlist = testenv
isolated_build = True

[testenv]
setenv = PYTHONPATH = {toxinidir}
deps = unittest2
responses
commands = unit2 discover {posargs:tests/unit}
deps = responses
packaging
commands = python -m unittest discover {posargs:tests/unit}

[testenv:unit]
commands = unit2 discover tests/unit
commands = python -m unittest discover tests/unit

[testenv:integration]
passenv = POWERWALL_IP POWERWALL_PASSWORD
commands = unit2 discover tests/integration
commands = python -m unittest discover tests/integration

0 comments on commit dbf4493

Please sign in to comment.