Metadata-Version: 2.4
Name: pysolarcloud
Version: 0.5.0
Summary: A library to interact with Sungrow's iSolarCloud API
Author-email: Tore Green <bugjam@e-dreams.dk>
License: MIT
Project-URL: Homepage, https://github.com/bugjam/pysolarcloud
Project-URL: Issues, https://github.com/bugjam/pysolarcloud/issues
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Framework :: AsyncIO
Classifier: Topic :: Home Automation
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: aiohttp
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-asyncio; extra == "dev"
Dynamic: license-file
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python

# pysolarcloud

A Python package to interact with the [iSolarCloud API](https://developer-api.isolarcloud.com/) by Sungrow.

The package supports the following functionality:
* OAuth2 authentication
* Getting a list plants
* Getting details of a plant
* Getting devices of a plant
* Getting "real-time" data of a plant (Data is updated every 5 minutes according to Sungrow's documentation)
* Getting historical data
* Getting and updating grid control settings

## Quirks
The iSolarCloud API is quite new and not very mature. Some tips:
* The authorisation flow is based on OAuth2 but doesn't work exactly as you would expect
* The authorisation page is a single-page application. To have the OAuth2 `state` parameter returned, it must be placed in the URL query before the `#`; `Auth.auth_url(..., state=...)` handles this layout.
* User is asked to approve the authorisation if the flow is invoked again, e.g. in case the tokens have expired - unlike many OAuth2 implementations who will perform a "silent" authorisation if the user has already approved the access.
* The API documentation lists a lot of data points which do not seem to be returned from my inverter, it probably varies between models.
* There are different iSolarCloud servers for different regions, see the `pysolarcloud.Server` enum
* API endpoints accept a language code but respond with Chinese text when when English is requested

# Usage

## Installation

```
pip install pysolarcloud
```

## Register your app
1. Create an account in the [iSolarCloud Developer Portal](https://developer-api.isolarcloud.com/)
2. Create an app in the developer portal
   * Answer "Yes" to authorize with OAuth2.0
   * Enter a Redirect URL for your app (this can be changed later)
3. Wait for approval by Sungrow
4. Find the needed configuration details in the developer portal. You will need:
   * Appkey
   * Secret Key
   * Application Id (This is shown as a query parameter within the Authorize URL in the developer portal)

## Example

```python
from pysolarcloud import Auth, Server
from pysolarcloud.plants import Plants

app_key = "your app key"
secret_key = "your secret key"
app_id = "your app id"
redirect_uri = "your redirect uri"
state = "an opaque value used to resume your flow"

auth = Auth(Server.Europe, app_key, secret_key, app_id)
url = auth.auth_url(redirect_uri, state=state)
```
1. Redirect user to `url`
2. User selects plant(s) and grants authorisation
3. iSolarCloud will redirect the user to `redirect_uri` with query parameters `code` and `state`
```python
await auth.async_authorize(code, redirect_uri)
plants_api = Plants(auth)
plant_list = await plants_api.async_get_plants()
if plant_list:
   print(f"{len(plant_list)} plants found:")
   for plant in plant_list:
         print(f"Plant ID: {plant["ps_id"]}, Name: {plant["ps_name"]}")
else:
   print("No plants found.")
   return

print("\nFetching detailed information for each plant...\n")
plant_ids = [str(plant["ps_id"]) for plant in plant_list]
plant_details = await plants_api.async_get_plant_details(plant_ids)
for plant in plant_details:
   print(f"Details for Plant ID {plant["ps_id"]}: {plant}")

print("\nFetching real-time data for each plant...\n")
real_time_data = await plants_api.async_get_realtime_data(plant_ids)
for plant_id, data in real_time_data.items():
   # Print only the data points where value is not None
   data_values = {k: v for k, v in data.items() if v and v.get("value") is not None}
   print(f"Real-time data for Plant ID {plant_id}: {data_values}")
```

The `Auth` class keeps the access between calls and refreshes it when needed. If you prefer to manage this state yourself, you can create your own subclass of `AbstractAuth`.

## Grid Control

The `Control` class enables retrieving and updating grid control settings. Parameters and value sets are documented in the iSolarCloud Developer portal.

### Example

```python
from pysolarcloud.control import Control
devices = await plants_api.async_get_plant_devices(plant_id, device_types=[DeviceType.ENERGY_STORAGE_SYSTEM])
device_uuid = devices[0]["uuid"]
control_api = Control(auth)
# Fetch current config
current_settings = await control_api.async_read_parameters(device_uuid)
print(current_settings)
# Make an update
await control_api.async_update_parameters(device_uuid, { "charge_discharge_command": "Charge" })
```

# Contributions
Ideas or contributions are welcome. I am not afiliated with Sungrow, I'm just another user of the API. My main use case will be a HomeAssistant integration based on this package.

Enjoy!
