pax_global_header00006660000000000000000000000064146257135320014522gustar00rootroot0000000000000052 comment=cb8c67cbc4ce1c80df9b3e62b2d18da9ce1c23a7 rokam-sunweg-cb8c67c/000077500000000000000000000000001462571353200146425ustar00rootroot00000000000000rokam-sunweg-cb8c67c/.github/000077500000000000000000000000001462571353200162025ustar00rootroot00000000000000rokam-sunweg-cb8c67c/.github/workflows/000077500000000000000000000000001462571353200202375ustar00rootroot00000000000000rokam-sunweg-cb8c67c/.github/workflows/python-build.yml000066400000000000000000000025161462571353200234040ustar00rootroot00000000000000name: Python build on: push: branches: ["main"] pull_request: branches: ["main"] jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.11", "3.10"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 pytest pytest-cov genbadge[all] if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --output-file ./reports/flake8/flake8stats.txt - name: Test with pytest run: | pytest --cov=sunweg --cov-report html --cov-report xml --junitxml=reports/junit/junit.xml mv htmlcov reports/coverage - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 rokam-sunweg-cb8c67c/.github/workflows/python-publish.yml000066400000000000000000000054621462571353200237560ustar00rootroot00000000000000# This workflow will upload a Python Package using Twine when a release is created # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. name: Upload Python Package on: release: types: [published] permissions: contents: read jobs: quality: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 pytest pytest-cov genbadge[all] if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --output-file ./reports/flake8/flake8stats.txt - name: Test with pytest run: | pytest --cov=sunweg --cov-report html --cov-report xml --junitxml=reports/junit/junit.xml mv htmlcov reports/coverage - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 - name: Generate badges if: github.event_name != 'pull_request' run: | genbadge tests -o reports/tests.svg genbadge coverage -i coverage.xml -o reports/coverage.svg genbadge flake8 -i reports/flake8/flake8stats.txt -o reports/flake8.svg - name: Publish badges report to badges branch uses: JamesIves/github-pages-deploy-action@v4 if: github.event_name != 'pull_request' with: branch: badges folder: reports token: ${{ secrets.SUNWEG_GITHUB_PAT }} deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v3 with: python-version: "3.10" - name: Install dependencies run: | python -m pip install --upgrade pip pip install build - name: Build package run: python -m build - name: Publish package uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} rokam-sunweg-cb8c67c/.gitignore000066400000000000000000000034201462571353200166310ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ reports/ rokam-sunweg-cb8c67c/.vscode/000077500000000000000000000000001462571353200162035ustar00rootroot00000000000000rokam-sunweg-cb8c67c/.vscode/extensions.json000066400000000000000000000002271462571353200212760ustar00rootroot00000000000000{ "recommendations": [ "esbenp.prettier-vscode", "ms-python.python", "github.vscode-pull-request-github", "charliermarsh.ruff" ] } rokam-sunweg-cb8c67c/.vscode/launch.json000066400000000000000000000007311462571353200203510ustar00rootroot00000000000000{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Python Debugger: Current File", "type": "debugpy", "request": "launch", "program": "${file}", "console": "integratedTerminal" } ] }rokam-sunweg-cb8c67c/.vscode/settings.json000066400000000000000000000024241462571353200207400ustar00rootroot00000000000000{ "editor.formatOnPaste": false, "editor.formatOnSave": true, "editor.formatOnType": true, "python.formatting.provider": "black", "python.testing.pytestArgs": [ "tests" ], "python.testing.pytestEnabled": true, "python.linting.mypyEnabled": true, "cSpell.ignoreWords": [ "Acumulada", "Acumulado", "Atualizacao", "Corrente", "Gerada", "Hoje", "Inversor", "Potencia", "SUNWEG", "Tensao", "alarme", "autenticacao", "carbono", "descricao", "economia", "energia", "energiaacumuladanumber", "fatorpotencia", "franqueado", "frequencia", "getpaineloperacao", "idusina", "integrador", "inversores", "mppt", "mppts", "nomemppt", "planos", "potenciaativa", "procurar", "reduz", "rtype", "senha", "situacao", "stringmppt", "temperatura", "tensaoca", "usinas", "usuario", "rokam" ], "python.linting.mypyArgs": [ "--follow-imports=silent", "--ignore-missing-imports", "--show-column-numbers", "--no-pretty", "--install-types" ], "python.testing.unittestEnabled": false, "python.linting.flake8Enabled": true, "python.linting.flake8Args": [ "--max-complexity=10", "--max-line-length=127" ], } rokam-sunweg-cb8c67c/.vscode/tasks.json000066400000000000000000000010431462571353200202210ustar00rootroot00000000000000{ "version": "2.0.0", "tasks": [ { "label": "Code Coverage", "detail": "Generate code coverage report.", "type": "shell", "command": "pytest ./tests/ --cov=sunweg --cov-report term-missing --durations-min=1 --durations=0", "group": { "kind": "test", "isDefault": true }, "presentation": { "reveal": "always", "panel": "new" }, "problemMatcher": [] } ] }rokam-sunweg-cb8c67c/LICENSE000066400000000000000000000020731462571353200156510ustar00rootroot00000000000000MIT License Copyright (c) 2023 Lucas MindĂȘllo de Andrade Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rokam-sunweg-cb8c67c/README.md000066400000000000000000000021061462571353200161200ustar00rootroot00000000000000# SunWeg [![Python build](https://github.com/rokam/sunweg/actions/workflows/python-build.yml/badge.svg)](https://github.com/rokam/sunweg/actions/workflows/python-build.yml) ![Python tests](https://raw.githubusercontent.com/rokam/sunweg/badges/tests.svg) ![Python coverage](https://raw.githubusercontent.com/rokam/sunweg/badges/coverage.svg) ![Python fake8](https://raw.githubusercontent.com/rokam/sunweg/badges/flake8.svg) Python lib for WEG solar energy platform, [SunWEG.net](https://sunweg.net/) ## Usage ``` python from sunweg.api import APIHelper api = APIHelper('username','password') plants = api.listPlants() for plant in plants: print(plant) for inverter in plant.inverters: print(inverter) for phase in inverter.phases: print(phase) for mppt in inverter.mppts: print(mppt) for string in mppt.strings: print(string) ``` ## Documentation Check the [DOCs](https://github.com/rokam/sunweg/blob/main/docs/index.md) for API documentation. ## Contribute Feel free to send issues and pull requests. rokam-sunweg-cb8c67c/docs/000077500000000000000000000000001462571353200155725ustar00rootroot00000000000000rokam-sunweg-cb8c67c/docs/index.md000066400000000000000000000611741462571353200172340ustar00rootroot00000000000000# Table of Contents * [sunweg](#sunweg) * [sunweg.device](#sunweg.device) * [Phase](#sunweg.device.Phase) * [\_\_init\_\_](#sunweg.device.Phase.__init__) * [name](#sunweg.device.Phase.name) * [voltage](#sunweg.device.Phase.voltage) * [amperage](#sunweg.device.Phase.amperage) * [status\_voltage](#sunweg.device.Phase.status_voltage) * [status\_amperage](#sunweg.device.Phase.status_amperage) * [\_\_str\_\_](#sunweg.device.Phase.__str__) * [String](#sunweg.device.String) * [\_\_init\_\_](#sunweg.device.String.__init__) * [name](#sunweg.device.String.name) * [voltage](#sunweg.device.String.voltage) * [amperage](#sunweg.device.String.amperage) * [status](#sunweg.device.String.status) * [\_\_str\_\_](#sunweg.device.String.__str__) * [MPPT](#sunweg.device.MPPT) * [\_\_init\_\_](#sunweg.device.MPPT.__init__) * [name](#sunweg.device.MPPT.name) * [strings](#sunweg.device.MPPT.strings) * [\_\_str\_\_](#sunweg.device.MPPT.__str__) * [Inverter](#sunweg.device.Inverter) * [\_\_init\_\_](#sunweg.device.Inverter.__init__) * [id](#sunweg.device.Inverter.id) * [name](#sunweg.device.Inverter.name) * [sn](#sunweg.device.Inverter.sn) * [status](#sunweg.device.Inverter.status) * [temperature](#sunweg.device.Inverter.temperature) * [today\_energy](#sunweg.device.Inverter.today_energy) * [today\_energy](#sunweg.device.Inverter.today_energy) * [today\_energy\_metric](#sunweg.device.Inverter.today_energy_metric) * [today\_energy\_metric](#sunweg.device.Inverter.today_energy_metric) * [total\_energy](#sunweg.device.Inverter.total_energy) * [total\_energy](#sunweg.device.Inverter.total_energy) * [total\_energy\_metric](#sunweg.device.Inverter.total_energy_metric) * [total\_energy\_metric](#sunweg.device.Inverter.total_energy_metric) * [power\_factor](#sunweg.device.Inverter.power_factor) * [power\_factor](#sunweg.device.Inverter.power_factor) * [frequency](#sunweg.device.Inverter.frequency) * [frequency](#sunweg.device.Inverter.frequency) * [power](#sunweg.device.Inverter.power) * [power](#sunweg.device.Inverter.power) * [power\_metric](#sunweg.device.Inverter.power_metric) * [power\_metric](#sunweg.device.Inverter.power_metric) * [is\_complete](#sunweg.device.Inverter.is_complete) * [phases](#sunweg.device.Inverter.phases) * [mppts](#sunweg.device.Inverter.mppts) * [\_\_str\_\_](#sunweg.device.Inverter.__str__) * [sunweg.const](#sunweg.const) * [SUNWEG\_URL](#sunweg.const.SUNWEG_URL) * [SUNWEG\_LOGIN\_PATH](#sunweg.const.SUNWEG_LOGIN_PATH) * [SUNWEG\_PLANT\_LIST\_PATH](#sunweg.const.SUNWEG_PLANT_LIST_PATH) * [SUNWEG\_PLANT\_DETAIL\_PATH](#sunweg.const.SUNWEG_PLANT_DETAIL_PATH) * [SUNWEG\_INVERTER\_DETAIL\_PATH](#sunweg.const.SUNWEG_INVERTER_DETAIL_PATH) * [SUNWEG\_MONTH\_STATS\_PATH](#sunweg.const.SUNWEG_MONTH_STATS_PATH) * [sunweg.api](#sunweg.api) * [SunWegApiError](#sunweg.api.SunWegApiError) * [LoginError](#sunweg.api.LoginError) * [convert\_situation\_status](#sunweg.api.convert_situation_status) * [separate\_value\_metric](#sunweg.api.separate_value_metric) * [APIHelper](#sunweg.api.APIHelper) * [\_\_init\_\_](#sunweg.api.APIHelper.__init__) * [authenticate](#sunweg.api.APIHelper.authenticate) * [listPlants](#sunweg.api.APIHelper.listPlants) * [plant](#sunweg.api.APIHelper.plant) * [inverter](#sunweg.api.APIHelper.inverter) * [complete\_inverter](#sunweg.api.APIHelper.complete_inverter) * [month\_stats\_production](#sunweg.api.APIHelper.month_stats_production) * [month\_stats\_production\_by\_id](#sunweg.api.APIHelper.month_stats_production_by_id) * [sunweg.plant](#sunweg.plant) * [Plant](#sunweg.plant.Plant) * [\_\_init\_\_](#sunweg.plant.Plant.__init__) * [id](#sunweg.plant.Plant.id) * [name](#sunweg.plant.Plant.name) * [total\_power](#sunweg.plant.Plant.total_power) * [kwh\_per\_kwp](#sunweg.plant.Plant.kwh_per_kwp) * [performance\_rate](#sunweg.plant.Plant.performance_rate) * [saving](#sunweg.plant.Plant.saving) * [today\_energy](#sunweg.plant.Plant.today_energy) * [today\_energy\_metric](#sunweg.plant.Plant.today_energy_metric) * [total\_energy](#sunweg.plant.Plant.total_energy) * [total\_carbon\_saving](#sunweg.plant.Plant.total_carbon_saving) * [last\_update](#sunweg.plant.Plant.last_update) * [inverters](#sunweg.plant.Plant.inverters) * [\_\_str\_\_](#sunweg.plant.Plant.__str__) * [sunweg.util](#sunweg.util) * [Status](#sunweg.util.Status) * [ProductionStats](#sunweg.util.ProductionStats) * [\_\_init\_\_](#sunweg.util.ProductionStats.__init__) * [date](#sunweg.util.ProductionStats.date) * [production](#sunweg.util.ProductionStats.production) * [prognostic](#sunweg.util.ProductionStats.prognostic) * [\_\_str\_\_](#sunweg.util.ProductionStats.__str__) # sunweg Sunweg API library. # sunweg.device Sunweg API devices. ## Phase Objects ```python class Phase() ``` Phase details. #### \_\_init\_\_ ```python def __init__(name: str, voltage: float, amperage: float, status_voltage: Status, status_amperage: Status) -> None ``` Initialize Phase. **Arguments**: - `name` (`str`): phase name - `voltage` (`float`): phase AC voltage in V - `amperage` (`float`): phase AC amperage in A - `status_voltage` (`Status`): phase AC voltage status - `status_amperage` (`Status`): phase AC amperage status #### name ```python @property def name() -> str ``` Get phase name. **Returns**: `str`: phase name #### voltage ```python @property def voltage() -> float ``` Get phase AC voltage in V. **Returns**: `float`: phase AC voltage in V #### amperage ```python @property def amperage() -> float ``` Get phase AC amperage in A. **Returns**: `float`: phase AC amperage in A #### status\_voltage ```python @property def status_voltage() -> Status ``` Get phase AC voltage status. **Returns**: `Status`: phase AC voltage status #### status\_amperage ```python @property def status_amperage() -> Status ``` Get phase AC amperage status. **Returns**: `Status`: phase AC amperage status #### \_\_str\_\_ ```python def __str__() -> str ``` Cast Phase to str. ## String Objects ```python class String() ``` String details. #### \_\_init\_\_ ```python def __init__(name: str, voltage: float, amperage: float, status: Status) -> None ``` Initialize String. **Arguments**: - `name` (`str`): string name - `voltage` (`float`): string DC voltage in V - `amperage` (`float`): string DC amperage in A - `status` (`Status`): string status #### name ```python @property def name() -> str ``` Get string name. **Returns**: `str`: string name #### voltage ```python @property def voltage() -> float ``` Get string DC voltage in V. **Returns**: `float`: string DC voltage in V #### amperage ```python @property def amperage() -> float ``` Get string DC amperage in A. **Returns**: `float`: string DC amperage in A #### status ```python @property def status() -> Status ``` Get string status. **Returns**: `Status`: string status #### \_\_str\_\_ ```python def __str__() -> str ``` Cast String to str. ## MPPT Objects ```python class MPPT() ``` MPPT details. #### \_\_init\_\_ ```python def __init__(name: str) -> None ``` Initialize MPPT. **Arguments**: - `name` (`srt`): MPPT name #### name ```python @property def name() -> str ``` Get MPPT name. **Returns**: `str`: MPPT name #### strings ```python @property def strings() -> list[String] ``` Get list of MPPT's String. **Returns**: `list[String]`: list of Strings #### \_\_str\_\_ ```python def __str__() -> str ``` Cast MPPT to str. ## Inverter Objects ```python class Inverter() ``` Inverter device. #### \_\_init\_\_ ```python def __init__(id: int, name: str, sn: str, status: Status, temperature: int, total_energy: float = 0, total_energy_metric: str = "", today_energy: float = 0, today_energy_metric: str = "", power_factor: float = 0, frequency: float = 0, power: float = 0, power_metric: str = "") -> None ``` Initialize Inverter. **Arguments**: - `id` (`int`): inverter id - `name` (`str`): inverter name - `sn` (`str`): inverter serial number - `status` (`Status`): inverter status - `temperature` (`int`): inverter temperature - `total_energy` (`float`): total generated energy - `total_energy_metric` (`str`): total generated energy metric - `today_energy` (`float`): total generated energy today - `today_energy_metric` (`str`): total generated energy today metric - `power_factor` (`float`): inverter power factor - `frequency` (`float`): inverter output frequency in Hz - `power` (`str`): inverter output power - `power` (`str`): inverter output power metric #### id ```python @property def id() -> int ``` Get inverter id. **Returns**: `int`: inverter id #### name ```python @property def name() -> str ``` Get inverter name. **Returns**: `str`: inverter name #### sn ```python @property def sn() -> str ``` Get inverter serial number. **Returns**: `str`: inverter serial number #### status ```python @property def status() -> Status ``` Get inverter status. **Returns**: `Status`: inverter status #### temperature ```python @property def temperature() -> int ``` Get inverter temperature. **Returns**: `int`: inverter temperature #### today\_energy ```python @property def today_energy() -> float ``` Get inverter today generated energy. **Returns**: `float`: inverter today generated energy #### today\_energy ```python @today_energy.setter def today_energy(value: float) -> None ``` Set inverter today generated energy. **Arguments**: - `value` (`float`): inverter today generated energy #### today\_energy\_metric ```python @property def today_energy_metric() -> str ``` Get inverter today generated energy metric. **Returns**: `str`: inverter today generated energy metric #### today\_energy\_metric ```python @today_energy_metric.setter def today_energy_metric(value: str) -> None ``` Set inverter today generated energy metric. **Arguments**: - `value` (`str`): inverter today generated energy metric #### total\_energy ```python @property def total_energy() -> float ``` Get inverter total generated energy. **Returns**: `float`: inverter total generated energy #### total\_energy ```python @total_energy.setter def total_energy(value: float) -> None ``` Set inverter total generated energy. **Arguments**: - `value` (`float`): inverter total generated energy #### total\_energy\_metric ```python @property def total_energy_metric() -> str ``` Get inverter total generated energy metric. **Returns**: `str`: inverter total generated energy metric #### total\_energy\_metric ```python @total_energy_metric.setter def total_energy_metric(value: str) -> None ``` Set inverter total generated energy metric. **Arguments**: - `value` (`str`): inverter total generated energy metric #### power\_factor ```python @property def power_factor() -> float ``` Get inverter power factor. **Returns**: `float`: inverter power factor #### power\_factor ```python @power_factor.setter def power_factor(value: float) -> None ``` Set inverter power factor. **Arguments**: - `value` (`float`): inverter power factor #### frequency ```python @property def frequency() -> float ``` Get inverter frequency in Hz. **Returns**: `float`: inverter frequency in HZ #### frequency ```python @frequency.setter def frequency(value: float) -> None ``` Set inverter frequency in Hz. **Arguments**: - `value` (`float`): inverter frequency in Hz #### power ```python @property def power() -> float ``` Get inverter output power. **Returns**: `float`: inverter output power #### power ```python @power.setter def power(value: float) -> None ``` Set inverter output power. **Arguments**: - `value` (`float`): inverter output power #### power\_metric ```python @property def power_metric() -> str ``` Get inverter output power metric. **Returns**: `str`: inverter output power metric #### power\_metric ```python @power_metric.setter def power_metric(value: str) -> None ``` Set inverter output power metric. **Arguments**: - `value` (`float`): inverter output power metric #### is\_complete ```python @property def is_complete() -> bool ``` Is inverter data complete. **Returns**: `bool`: True when inverter data is complete #### phases ```python @property def phases() -> list[Phase] ``` Get list of inverter's phases. **Returns**: `list[Phase]`: list of phases #### mppts ```python @property def mppts() -> list[MPPT] ``` Get list of inverter's MPPTs. **Returns**: `list[MPPT]`: list of MPPTs #### \_\_str\_\_ ```python def __str__() -> str ``` Cast Inverter to str. # sunweg.const Sunweg API constants. #### SUNWEG\_URL SunWEG API URL #### SUNWEG\_LOGIN\_PATH SunWEG API login path #### SUNWEG\_PLANT\_LIST\_PATH SunWEG API list plants path #### SUNWEG\_PLANT\_DETAIL\_PATH SunWEG API plant details path #### SUNWEG\_INVERTER\_DETAIL\_PATH SunWEG API inverter details path #### SUNWEG\_MONTH\_STATS\_PATH SunWEG API month history path # sunweg.api API Helper. ## SunWegApiError Objects ```python class SunWegApiError(RuntimeError) ``` API Error. ## LoginError Objects ```python class LoginError(SunWegApiError) ``` Login Error. #### convert\_situation\_status ```python def convert_situation_status(situation: int) -> Status ``` Convert situation to status. **Arguments**: - `situation` (`int`): situation **Returns**: `Status`: equivalent status #### separate\_value\_metric ```python def separate_value_metric(value_with_metric: str | None, default_metric: str = "") -> tuple[float, str] ``` Separate the value from the metric. **Arguments**: - `value_with_metric` (`str | None`): value with metric separated by space - `default_metric` (`str`): metric that should be returned if `value_with_metric` is None **Returns**: `tuple[float, str]`: tuple with value and metric ## APIHelper Objects ```python class APIHelper() ``` Class to call sunweg.net api. #### \_\_init\_\_ ```python def __init__(username: str, password: str) -> None ``` Initialize APIHelper for SunWEG platform. **Arguments**: - `username` (`str`): username for authentication - `password` (`str`): password for authentication #### authenticate ```python def authenticate() -> bool ``` Authenticate with provided username and password. **Returns**: `bool`: True on authentication success #### listPlants ```python def listPlants(retry=True) -> list[Plant] ``` Retrieve the list of plants with incomplete inverter information. You may want to call `complete_inverter()` to complete the Inverter information. **Arguments**: - `retry` (`bool`): reauthenticate if token expired and retry **Returns**: `list[Plant]`: list of Plant #### plant ```python def plant(id: int, retry=True) -> Plant | None ``` Retrieve plant detail by plant id. **Arguments**: - `id` (`int`): plant id - `retry` (`bool`): reauthenticate if token expired and retry **Returns**: `Plant | None`: Plant or None if `id` not found. #### inverter ```python def inverter(id: int, retry=True) -> Inverter | None ``` Retrieve inverter detail by inverter id. **Arguments**: - `id` (`int`): inverter id - `retry` (`bool`): reauthenticate if token expired and retry **Returns**: `Inverter | None`: Inverter or None if `id` not found. #### complete\_inverter ```python def complete_inverter(inverter: Inverter, retry=True) -> None ``` Complete inverter data. **Arguments**: - `inverter` (`Inverter`): inverter object to be completed with information - `retry` (`bool`): reauthenticate if token expired and retry #### month\_stats\_production ```python def month_stats_production(year: int, month: int, plant: Plant, inverter: Inverter | None = None, retry: bool = True) -> list[ProductionStats] ``` Retrieve month energy production statistics. **Arguments**: - `year` (`int`): statistics year - `month` (`int`): statistics month - `plant` (`Plant`): statistics plant - `inverter` (`Inverter | None`): statistics inverter, None for every inverter - `retry` (`bool`): reauthenticate if token expired and retry **Returns**: `list[ProductionStats]`: list of daily energy production statistics #### month\_stats\_production\_by\_id ```python def month_stats_production_by_id(year: int, month: int, plant_id: int, inverter_id: int | None = None, retry: bool = True) -> list[ProductionStats] ``` Retrieve month energy production statistics. **Arguments**: - `year` (`int`): statistics year - `month` (`int`): statistics month - `plant_id` (`int`): id of statistics plant - `inverter_id` (`int | None`): id of statistics inverter, None for every inverter - `retry` (`bool`): reauthenticate if token expired and retry **Returns**: `list[ProductionStats]`: list of daily energy production statistics # sunweg.plant Sunweg API plant. ## Plant Objects ```python class Plant() ``` Plant details. #### \_\_init\_\_ ```python def __init__(id: int, name: str, total_power: float, kwh_per_kwp: float, performance_rate: float, saving: float, today_energy: float, today_energy_metric: str, total_energy: float, total_carbon_saving: float, last_update: datetime | None) -> None ``` Initialize Plant. **Arguments**: - `id` (`int`): plant id - `name` (`str`): plant name - `total_power` (`float`): plant total power - `kwh_per_kwp` (`float`): plant kWh/kWp - `performance_rate` (`float`): plant performance rate - `saving` (`float`): total saving in R$ - `today_energy` (`float`): today generated energy - `today_energy_metric` (`str`): today generated energy metric - `total_energy` (`float`): total generated energy in kWh - `total_carbon_saving` (`float`): total of CO2 saved - `last_update` (`datetime | None`): when the data was updated #### id ```python @property def id() -> int ``` Get plant id. **Returns**: `int`: plant id #### name ```python @property def name() -> str ``` Get plant name. **Returns**: `str`: plant name #### total\_power ```python @property def total_power() -> float ``` Get plant total power. **Returns**: `float`: plant total power #### kwh\_per\_kwp ```python @property def kwh_per_kwp() -> float ``` Get plant kWh/kWp. **Returns**: `float`: plant kWh/kWp #### performance\_rate ```python @property def performance_rate() -> float ``` Get plant performance rate. **Returns**: `float`: plant performance rate #### saving ```python @property def saving() -> float ``` Get plant saving in R$. **Returns**: `float`: plant saving in R$ #### today\_energy ```python @property def today_energy() -> float ``` Get plant today generated energy. **Returns**: `float`: plant today generated energy #### today\_energy\_metric ```python @property def today_energy_metric() -> str ``` Get plant today generated energy metric. **Returns**: `str`: plant today generated energy metric #### total\_energy ```python @property def total_energy() -> float ``` Get plant total generated energy in kWh. **Returns**: `float`: plant total generated energy in kWh #### total\_carbon\_saving ```python @property def total_carbon_saving() -> float ``` Get plant total of CO2 saved. **Returns**: `float`: plant total of CO2 saved #### last\_update ```python @property def last_update() -> datetime | None ``` Get when the plant data was updated. **Returns**: `datetime | None`: when the plant data was updated #### inverters ```python @property def inverters() -> list[Inverter] ``` Get list of plant's inverters. **Returns**: `list[Inverter]`: list of inverters #### \_\_str\_\_ ```python def __str__() -> str ``` Cast Plant to str. # sunweg.util Sunweg API util. ## Status Objects ```python class Status(Enum) ``` Status enum. ## ProductionStats Objects ```python class ProductionStats() ``` Energy production statistics #### \_\_init\_\_ ```python def __init__(date: date, production: float, prognostic: float) -> None ``` Initialize energy production statistics. **Arguments**: - `date` (`date`): statistics date - `production` (`float`): statistics production in kWh - `prognostic`: statistics expected production in kWh #### date ```python @property def date() -> date ``` Get date. #### production ```python @property def production() -> float ``` Get energy production in kWh. #### prognostic ```python @property def prognostic() -> float ``` Get expected energy production in kWh. #### \_\_str\_\_ ```python def __str__() -> str ``` Cast Phase to str. rokam-sunweg-cb8c67c/requirements.txt000066400000000000000000000000311462571353200201200ustar00rootroot00000000000000python-dateutil requests rokam-sunweg-cb8c67c/setup.py000066400000000000000000000014541462571353200163600ustar00rootroot00000000000000#!/bin/python """setup sunweg.""" import setuptools with open("README.md", "r") as fh: long_description = fh.read() requires = [ "python-dateutil", "requests", ] setuptools.setup( name="sunweg", version="3.0.1", author="rokam", author_email="lucas@mindello.com.br", description="A library to retrieve data from sunweg.net", license="MIT", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/rokam/sunweg", install_requires=requires, packages=setuptools.find_packages(exclude=["tests", "tests.*"]), python_requires=">=3.10", classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], ) rokam-sunweg-cb8c67c/sunweg/000077500000000000000000000000001462571353200161525ustar00rootroot00000000000000rokam-sunweg-cb8c67c/sunweg/__init__.py000066400000000000000000000000321462571353200202560ustar00rootroot00000000000000"""Sunweg API library.""" rokam-sunweg-cb8c67c/sunweg/api.py000066400000000000000000000352401462571353200173010ustar00rootroot00000000000000"""API Helper.""" import json from dateutil import parser from typing import Any from requests import Response, session from .const import ( SUNWEG_INVERTER_DETAIL_PATH, SUNWEG_LOGIN_PATH, SUNWEG_MONTH_STATS_PATH, SUNWEG_PLANT_DETAIL_PATH, SUNWEG_PLANT_LIST_PATH, SUNWEG_URL, ) from .device import MPPT, Inverter, Phase, String from .plant import Plant from .util import ProductionStats, Status class SunWegApiError(RuntimeError): """API Error.""" pass class LoginError(SunWegApiError): """Login Error.""" pass def convert_situation_status(situation: int) -> Status: """ Convert situation to status. :param situation: situation :type situation: int :return: equivalent status :rtype: Status """ if situation == 0: return Status.ERROR if situation == 1: return Status.OK return Status.WARN def separate_value_metric( value_with_metric: str | None, default_metric: str = "", metric_before: bool = False ) -> tuple[float, str]: """ Separate the value from the metric. :param value_with_metric: value with metric separated by space :type value_with_metric: str | None :param default_metric: metric that should be returned if `value_with_metric` is None :type default_metric: str :param metric_before: true when metric appears before the value :type metric_before: bool :return: tuple with value and metric :rtype: tuple[float, str] """ if value_with_metric is None or len(value_with_metric) == 0: return (0.0, default_metric) split = value_with_metric.split(" ") if metric_before: return ( float(split[0].replace(",", ".")) if len(split) < 2 else float(split[1].replace(",", ".")), default_metric if len(split) < 2 else split[0], ) return ( float(split[0].replace(",", ".")), default_metric if len(split) < 2 else split[1], ) class APIHelper: """Class to call sunweg.net api.""" SERVER_URI = SUNWEG_URL def __init__(self, username: str, password: str) -> None: """ Initialize APIHelper for SunWEG platform. :param username: username for authentication :param password: password for authentication :type username: str :type password: str """ self._token = None self._username = username self._password = password self.session = session() def _set_username(self, username: str) -> None: """ Set username. :param username: username for authentication :type username: str """ self._username = username username = property(None, _set_username) def _set_password(self, password: str) -> None: """ Set password. :param password: password for authentication :type password: str """ self._password = password password = property(None, _set_password) def authenticate(self) -> bool: """ Authenticate with provided username and password. :return: True on authentication success :rtype: bool """ user_data = json.dumps( {"usuario": self._username, "senha": self._password, "rememberMe": True}, default=lambda o: o.__dict__, ) result = self._post(SUNWEG_LOGIN_PATH, user_data, False) if not result["success"]: return False self._token = result["token"] return result["success"] def _headers(self): """Retrieve headers with authentication token.""" if self._token is None: return {"Content-Type": "application/json"} return {"Content-Type": "application/json", "X-Auth-Token-Update": self._token} def listPlants(self, retry=True) -> list[Plant]: """ Retrieve the list of plants with incomplete inverter information. You may want to call `complete_inverter()` to complete the Inverter information. :param retry: reauthenticate if token expired and retry :type retry: bool :return: list of Plant :rtype: list[Plant] """ try: result = self._get(SUNWEG_PLANT_LIST_PATH) ret_list = [] plantlist = ( result["nao_comissionadas"] + result["conectadas"] + result["falhas"] + result["alertas"] + result["atendimento"] ) for plant in plantlist: if (plant := self.plant(plant["id"])) is not None: ret_list.append(plant) return ret_list except LoginError: if retry: self.authenticate() return self.listPlants(False) return [] def plant(self, id: int, retry=True) -> Plant | None: """ Retrieve plant detail by plant id. :param id: plant id :type id: int :param retry: reauthenticate if token expired and retry :type retry: bool :return: Plant or None if `id` not found. :rtype: Plant | None """ try: result = self._get(SUNWEG_PLANT_DETAIL_PATH + str(id)) (today_energy, today_energy_metric) = separate_value_metric( result["energiadia"], "kWh" ) total_power = separate_value_metric(result["AcumuladoPotencia"])[0] saving = separate_value_metric(result["economia"], metric_before=True)[0] plant = Plant( id=id, name=result["usinas"]["nome"], total_power=total_power, kwh_per_kwp=float(0), performance_rate=float(0), saving=saving, today_energy=today_energy, today_energy_metric=today_energy_metric, total_energy=float(result["energiaacumuladanumber"]), total_carbon_saving=result["reduz_carbono_total_number"], last_update=parser.parse(result["ultimaAtualizacao"]) if result["ultimaAtualizacao"] is not None else None, ) plant.inverters.extend( [ Inverter( id=inv["id"], name=inv["nome"], sn=inv["esn"], status=Status(int(inv["situacao"])), temperature=inv["temperatura"], ) for inv in result["usinas"]["inversores"] ] ) return plant except LoginError: if retry: self.authenticate() return self.plant(id, False) return None def inverter(self, id: int, retry=True) -> Inverter | None: """ Retrieve inverter detail by inverter id. :param id: inverter id :type id: int :param retry: reauthenticate if token expired and retry :type retry: bool :return: Inverter or None if `id` not found. :rtype: Inverter | None """ try: result = self._get(SUNWEG_INVERTER_DETAIL_PATH + str(id)) (total_energy, total_energy_metric) = separate_value_metric( result["energiaacumulada"], "kWh" ) (today_energy, today_energy_metric) = separate_value_metric( result["energiadodia"], "kWh" ) (power, power_metric) = separate_value_metric(result["potenciaativa"], "kW") inverter = Inverter( id=id, name=result["inversor"]["nome"], sn=result["inversor"]["esn"], total_energy=total_energy, total_energy_metric=total_energy_metric, today_energy=today_energy, today_energy_metric=today_energy_metric, power_factor=float(result["fatorpotencia"].replace(",", ".")), frequency=float(result["frequencia"].replace(",", ".")), power=power, power_metric=power_metric, status=Status(int(result["statusInversor"])), temperature=result["temperatura"], ) self._populate_MPPT(result=result, inverter=inverter) return inverter except LoginError: if retry: self.authenticate() return self.inverter(id, False) return None def complete_inverter(self, inverter: Inverter, retry=True) -> None: """ Complete inverter data. :param inverter: inverter object to be completed with information :type inverter: Inverter :param retry: reauthenticate if token expired and retry :type retry: bool """ try: result = self._get(SUNWEG_INVERTER_DETAIL_PATH + str(inverter.id)) ( inverter.total_energy, inverter.total_energy_metric, ) = separate_value_metric(result["energiaacumulada"], "kWh") ( inverter.today_energy, inverter.today_energy_metric, ) = separate_value_metric(result["energiadodia"], "kWh") (inverter.power, inverter.power_metric) = separate_value_metric( result["potenciaativa"], "kW" ) inverter.power_factor = float(result["fatorpotencia"].replace(",", ".")) inverter.frequency = float(result["frequencia"].replace(",", ".")) self._populate_MPPT(result=result, inverter=inverter) except LoginError: if retry: self.authenticate() self.complete_inverter(inverter, False) def month_stats_production( self, year: int, month: int, plant: Plant, inverter: Inverter | None = None, retry: bool = True, ) -> list[ProductionStats]: """ Retrieve month energy production statistics. :param year: statistics year :type year: int :param month: statistics month :type month: int :param plant: statistics plant :type plant: Plant :param inverter: statistics inverter, None for every inverter :type inverter: Inverter | None :param retry: reauthenticate if token expired and retry :type retry: bool :return: list of daily energy production statistics :rtype: list[ProductionStats] """ return self.month_stats_production_by_id( year, month, plant.id, inverter.id if inverter is not None else None, retry ) def month_stats_production_by_id( self, year: int, month: int, plant_id: int, inverter_id: int | None = None, retry: bool = True, ) -> list[ProductionStats]: """ Retrieve month energy production statistics. :param year: statistics year :type year: int :param month: statistics month :type month: int :param plant_id: id of statistics plant :type plant_id: int :param inverter_id: id of statistics inverter, None for every inverter :type inverter_id: int | None :param retry: reauthenticate if token expired and retry :type retry: bool :return: list of daily energy production statistics :rtype: list[ProductionStats] """ inverter_str: str = str(inverter_id) if inverter_id is not None else "" try: result = self._get( SUNWEG_MONTH_STATS_PATH + f"idusina={plant_id}&idinversor={inverter_str}&date={format(month,'02')}/{year}" ) return [ ProductionStats( parser.parse(item["tempoatual"]).date(), float(item["energiapordia"]), float(item["prognostico"]), ) for item in result["graficomes"] ] except LoginError: if retry: self.authenticate() return self.month_stats_production_by_id( year, month, plant_id, inverter_id, False ) return [] def _populate_MPPT(self, result: dict, inverter: Inverter) -> None: """Populate MPPT information inside a inverter.""" for str_mppt in result["stringmppt"]: mppt = MPPT(str_mppt["nomemppt"]) for str_string in str_mppt["strings"]: string = String( str_string["nome"], float(result["inversor"]["leitura"][str_string["variaveltensao"]]), float( result["inversor"]["leitura"][str_string["variavelcorrente"]] ), convert_situation_status(int(str_string["situacao"])), ) mppt.strings.append(string) inverter.mppts.append(mppt) for phase_name in result["correnteCA"].keys(): if str(phase_name).endswith("status"): continue inverter.phases.append( Phase( phase_name, float(result["tensaoca"][phase_name].replace(",", ".")), float(result["correnteCA"][phase_name].replace(",", ".")), Status(result["tensaoca"][phase_name + "status"]), Status(result["correnteCA"][phase_name + "status"]), ) ) def _get(self, path: str, launch_exception_on_error: bool = True) -> dict: """Do a get request returning a treated response.""" res = self.session.get(self.SERVER_URI + path, headers=self._headers()) result = self._treat_response(res, launch_exception_on_error) return result def _post( self, path: str, data: Any | None, launch_exception_on_error: bool = True ) -> dict: """Do a post request returning a treated response.""" res = self.session.post( self.SERVER_URI + path, data=data, headers=self._headers() ) result = self._treat_response(res, launch_exception_on_error) return result def _treat_response( self, response: Response, launch_exception_on_error: bool = True ) -> dict: """Treat the response from requests.""" if response.status_code == 401: raise LoginError("Request failed: %s" % response) if response.status_code != 200: raise SunWegApiError("Request failed: %s" % response) result = response.json() if launch_exception_on_error and not result["success"]: raise SunWegApiError(result["message"]) return result rokam-sunweg-cb8c67c/sunweg/const.py000066400000000000000000000012621462571353200176530ustar00rootroot00000000000000"""Sunweg API constants.""" SUNWEG_URL = "https://api.sun.weg.net/v2/" """SunWEG API URL""" SUNWEG_LOGIN_PATH = "login/autenticacao" """SunWEG API login path""" SUNWEG_PLANT_LIST_PATH = ( "getpaineloperacao?procurar=&integrador=" + "&franqueado=&manutencao=&portal=&alarme=&" + "planos=%5B0,1,2,3,4%5D&status=%5B1,2,3,4,5%5D&" + "limite=100&situacao=&paginaAtual=1" ) """SunWEG API list plants path""" SUNWEG_PLANT_DETAIL_PATH = "viewresumov2?agrupado=false&id=" """SunWEG API plant details path""" SUNWEG_INVERTER_DETAIL_PATH = "inversores/view?id=" """SunWEG API inverter details path""" SUNWEG_MONTH_STATS_PATH = "usinas/graficomes?" """SunWEG API month history path""" rokam-sunweg-cb8c67c/sunweg/device.py000066400000000000000000000274201462571353200177700ustar00rootroot00000000000000"""Sunweg API devices.""" from .util import Status class Phase: """Phase details.""" def __init__( self, name: str, voltage: float, amperage: float, status_voltage: Status, status_amperage: Status, ) -> None: """ Initialize Phase. :param name: phase name :type name: str :param voltage: phase AC voltage in V :type voltage: float :param amperage: phase AC amperage in A :type amperage: float :param status_voltage: phase AC voltage status :type status_voltage: Status :param status_amperage: phase AC amperage status :type status_amperage: Status """ self._name = name self._voltage = voltage self._amperage = amperage self._status_voltage = status_voltage self._status_amperage = status_amperage @property def name(self) -> str: """ Get phase name. :return: phase name :rtype: str """ return self._name @property def voltage(self) -> float: """ Get phase AC voltage in V. :return: phase AC voltage in V :rtype: float """ return self._voltage @property def amperage(self) -> float: """ Get phase AC amperage in A. :return: phase AC amperage in A :rtype: float """ return self._amperage @property def status_voltage(self) -> Status: """ Get phase AC voltage status. :return: phase AC voltage status :rtype: Status """ return self._status_voltage @property def status_amperage(self) -> Status: """ Get phase AC amperage status. :return: phase AC amperage status :rtype: Status """ return self._status_amperage def __str__(self) -> str: """Cast Phase to str.""" return str(self.__class__) + ": " + str(self.__dict__) class String: """String details.""" def __init__( self, name: str, voltage: float, amperage: float, status: Status ) -> None: """ Initialize String. :param name: string name :type name: str :param voltage: string DC voltage in V :type voltage: float :param amperage: string DC amperage in A :type amperage: float :param status: string status :type status: Status """ self._name = name self._voltage = voltage self._amperage = amperage self._status = status @property def name(self) -> str: """ Get string name. :return: string name :rtype: str """ return self._name @property def voltage(self) -> float: """ Get string DC voltage in V. :return: string DC voltage in V :rtype: float """ return self._voltage @property def amperage(self) -> float: """ Get string DC amperage in A. :return: string DC amperage in A :rtype: float """ return self._amperage @property def status(self) -> Status: """ Get string status. :return: string status :rtype: Status """ return self._status def __str__(self) -> str: """Cast String to str.""" return str(self.__class__) + ": " + str(self.__dict__) class MPPT: """MPPT details.""" def __init__(self, name: str) -> None: """ Initialize MPPT. :param name: MPPT name :type name: srt """ self._name = name self._strings: list[String] = [] @property def name(self) -> str: """ Get MPPT name. :return: MPPT name :rtype: str """ return self._name @property def strings(self) -> list[String]: """ Get list of MPPT's String. :return: list of Strings :rtype: list[String] """ return self._strings def __str__(self) -> str: """Cast MPPT to str.""" return str(self.__class__) + ": " + str(self.__dict__) class Inverter: """Inverter device.""" def __init__( self, id: int, name: str, sn: str, status: Status, temperature: int, total_energy: float = 0, total_energy_metric: str = "", today_energy: float = 0, today_energy_metric: str = "", power_factor: float = 0, frequency: float = 0, power: float = 0, power_metric: str = "", ) -> None: """ Initialize Inverter. :param id: inverter id :type id: int :param name: inverter name :type name: str :param sn: inverter serial number :type sn: str :param status: inverter status :type status: Status :param temperature: inverter temperature :type temperature: int :param total_energy: total generated energy :type total_energy: float :param total_energy_metric: total generated energy metric :type total_energy_metric: str :param today_energy: total generated energy today :type today_energy: float :param today_energy_metric: total generated energy today metric :type today_energy_metric: str :param power_factor: inverter power factor :type power_factor: float :param frequency: inverter output frequency in Hz :type frequency: float :param power: inverter output power :type power: float :param power: inverter output power metric :type power: str """ self._id = id self._name = name self._sn = sn self._total_energy = total_energy self._total_energy_metric = total_energy_metric self._today_energy = today_energy self._today_energy_metric = today_energy_metric self._power_factor = power_factor self._frequency = frequency self._power = power self._power_metric = power_metric self._status = status self._temperature = temperature self._phases: list[Phase] = [] self._mppts: list[MPPT] = [] @property def id(self) -> int: """ Get inverter id. :return: inverter id :rtype: int """ return self._id @property def name(self) -> str: """ Get inverter name. :return: inverter name :rtype: str """ return self._name @property def sn(self) -> str: """ Get inverter serial number. :return: inverter serial number :rtype: str """ return self._sn @property def status(self) -> Status: """ Get inverter status. :return: inverter status :rtype: Status """ return self._status @property def temperature(self) -> int: """ Get inverter temperature. :return: inverter temperature :rtype: int """ return self._temperature @property def today_energy(self) -> float: """ Get inverter today generated energy. :return: inverter today generated energy :rtype: float """ return self._today_energy @today_energy.setter def today_energy(self, value: float) -> None: """ Set inverter today generated energy. :param value: inverter today generated energy :type value: float """ self._today_energy = value @property def today_energy_metric(self) -> str: """ Get inverter today generated energy metric. :return: inverter today generated energy metric :rtype: str """ return self._today_energy_metric @today_energy_metric.setter def today_energy_metric(self, value: str) -> None: """ Set inverter today generated energy metric. :param value: inverter today generated energy metric :type value: str """ self._today_energy_metric = value @property def total_energy(self) -> float: """ Get inverter total generated energy. :return: inverter total generated energy :rtype: float """ return self._total_energy @total_energy.setter def total_energy(self, value: float) -> None: """ Set inverter total generated energy. :param value: inverter total generated energy :type value: float """ self._total_energy = value @property def total_energy_metric(self) -> str: """ Get inverter total generated energy metric. :return: inverter total generated energy metric :rtype: str """ return self._total_energy_metric @total_energy_metric.setter def total_energy_metric(self, value: str) -> None: """ Set inverter total generated energy metric. :param value: inverter total generated energy metric :type value: str """ self._total_energy_metric = value @property def power_factor(self) -> float: """ Get inverter power factor. :return: inverter power factor :rtype: float """ return self._power_factor @power_factor.setter def power_factor(self, value: float) -> None: """ Set inverter power factor. :param value: inverter power factor :type value: float """ self._power_factor = value @property def frequency(self) -> float: """ Get inverter frequency in Hz. :return: inverter frequency in HZ :rtype: float """ return self._frequency @frequency.setter def frequency(self, value: float) -> None: """ Set inverter frequency in Hz. :param value: inverter frequency in Hz :type value: float """ self._frequency = value @property def power(self) -> float: """ Get inverter output power. :return: inverter output power :rtype: float """ return self._power @power.setter def power(self, value: float) -> None: """ Set inverter output power. :param value: inverter output power :type value: float """ self._power = value @property def power_metric(self) -> str: """ Get inverter output power metric. :return: inverter output power metric :rtype: str """ return self._power_metric @power_metric.setter def power_metric(self, value: str) -> None: """ Set inverter output power metric. :param value: inverter output power metric :type value: float """ self._power_metric = value @property def is_complete(self) -> bool: """ Is inverter data complete. :return: True when inverter data is complete :rtype: bool """ return ( self._today_energy != 0 or self._total_energy != 0 or self._power_factor != 0 or self._frequency != 0 or self._power != 0 ) @property def phases(self) -> list[Phase]: """ Get list of inverter's phases. :return: list of phases :rtype: list[Phase] """ return self._phases @property def mppts(self) -> list[MPPT]: """ Get list of inverter's MPPTs. :return: list of MPPTs :rtype: list[MPPT] """ return self._mppts def __str__(self) -> str: """Cast Inverter to str.""" return str(self.__class__) + ": " + str(self.__dict__) rokam-sunweg-cb8c67c/sunweg/plant.py000066400000000000000000000116421462571353200176460ustar00rootroot00000000000000"""Sunweg API plant.""" from datetime import datetime import warnings from .device import Inverter class Plant: """Plant details.""" def __init__( self, id: int, name: str, total_power: float, kwh_per_kwp: float, performance_rate: float, saving: float, today_energy: float, today_energy_metric: str, total_energy: float, total_carbon_saving: float, last_update: datetime | None, ) -> None: """ Initialize Plant. :param id: plant id :type id: int :param name: plant name :type name: str :param total_power: plant total power :type total_power: float :param kwh_per_kwp: plant kWh/kWp :type kwh_per_kwp: float :param performance_rate: plant performance rate :type performance_rate: float :param saving: total saving in R$ :type saving: float :param today_energy: today generated energy :type today_energy: float :param today_energy_metric: today generated energy metric :type today_energy_metric: str :param total_energy: total generated energy in kWh :type total_energy: float :param total_carbon_saving: total of CO2 saved :type total_carbon_saving: float :param last_update: when the data was updated :type last_update: datetime | None """ self._id = id self._name = name self._total_power = total_power self._kwh_per_kwp = kwh_per_kwp self._performance_rate = performance_rate self._saving = saving self._today_energy = today_energy self._today_energy_metric = today_energy_metric self._total_energy = total_energy self._total_carbon_saving = total_carbon_saving self._last_update = last_update self._inverters: list[Inverter] = [] @property def id(self) -> int: """ Get plant id. :return: plant id :rtype: int """ return self._id @property def name(self) -> str: """ Get plant name. :return: plant name :rtype: str """ return self._name @property def total_power(self) -> float: """ Get plant total power. :return: plant total power :rtype: float """ return self._total_power @property def kwh_per_kwp(self) -> float: """ Deprecated as API v2 doesn't return it anymore. Get plant kWh/kWp. :return: plant kWh/kWp :rtype: float """ warnings.warn( "The 'kwh_per_kwp' property is deprecated and will return 0.", DeprecationWarning, stacklevel=2, ) return self._kwh_per_kwp @property def performance_rate(self) -> float: """ Deprecated as API v2 doesn't return it anymore. Get plant performance rate. :return: plant performance rate :rtype: float """ warnings.warn( "The 'performance_rate' property is deprecated and will return 0.", DeprecationWarning, stacklevel=2, ) return self._performance_rate @property def saving(self) -> float: """ Get plant saving in R$. :return: plant saving in R$ :rtype: float """ return self._saving @property def today_energy(self) -> float: """ Get plant today generated energy. :return: plant today generated energy :rtype: float """ return self._today_energy @property def today_energy_metric(self) -> str: """ Get plant today generated energy metric. :return: plant today generated energy metric :rtype: str """ return self._today_energy_metric @property def total_energy(self) -> float: """ Get plant total generated energy in kWh. :return: plant total generated energy in kWh :rtype: float """ return self._total_energy @property def total_carbon_saving(self) -> float: """ Get plant total of CO2 saved. :return: plant total of CO2 saved :rtype: float """ return self._total_carbon_saving @property def last_update(self) -> datetime | None: """ Get when the plant data was updated. :return: when the plant data was updated :rtype: datetime | None """ return self._last_update @property def inverters(self) -> list[Inverter]: """ Get list of plant's inverters. :return: list of inverters :rtype: list[Inverter] """ return self._inverters def __str__(self) -> str: """Cast Plant to str.""" return str(self.__class__) + ": " + str(self.__dict__) rokam-sunweg-cb8c67c/sunweg/util.py000066400000000000000000000022271462571353200175040ustar00rootroot00000000000000"""Sunweg API util.""" from datetime import date from enum import Enum class Status(Enum): """Status enum.""" OK = 0 WARN = 2 ERROR = 1 class ProductionStats: """Energy production statistics""" def __init__(self, date: date, production: float, prognostic: float) -> None: """ Initialize energy production statistics. :param date: statistics date :type date: date :param production: statistics production in kWh :type production: float :param prognostic: statistics expected production in kWh """ self._date = date self._production = production self._prognostic = prognostic @property def date(self) -> date: """Get date.""" return self._date @property def production(self) -> float: """Get energy production in kWh.""" return self._production @property def prognostic(self) -> float: """Get expected energy production in kWh.""" return self._prognostic def __str__(self) -> str: """Cast Phase to str.""" return str(self.__class__) + ": " + str(self.__dict__) rokam-sunweg-cb8c67c/tests/000077500000000000000000000000001462571353200160045ustar00rootroot00000000000000rokam-sunweg-cb8c67c/tests/__init__.py000066400000000000000000000000231462571353200201100ustar00rootroot00000000000000"""Test sunweg.""" rokam-sunweg-cb8c67c/tests/bandit.yaml000066400000000000000000000003401462571353200201260ustar00rootroot00000000000000# https://bandit.readthedocs.io/en/latest/config.html tests: - B103 - B108 - B306 - B307 - B313 - B314 - B315 - B316 - B317 - B318 - B319 - B320 - B325 - B601 - B602 - B604 - B608 - B609 rokam-sunweg-cb8c67c/tests/common.py000066400000000000000000000017551462571353200176560ustar00rootroot00000000000000"""Test sunweg common.""" from datetime import datetime from sunweg.device import Inverter, MPPT, Phase, String from sunweg.plant import Plant from sunweg.util import Status PLANT_MOCK = Plant( id=1, name="Plant", total_power=29.2, kwh_per_kwp=0.1, performance_rate=0, saving=0, today_energy=123.1, today_energy_metric="kWh", total_energy=321.1, total_carbon_saving=12.1, last_update=datetime.now(), ) INVERTER_MOCK = Inverter( id=1, name="Inverter", sn="1234ABC", total_energy=321.1, total_energy_metric="kWh", today_energy=123.1, today_energy_metric="kWh", power_factor=0.2, frequency=60, power=29, power_metric="kW", status=Status.OK, temperature=70, ) MPPT_MOCK = MPPT("MPPT") STRING_MOCK = String(name="String", voltage=523.1, amperage=12.1, status=Status.OK) PHASE_MOCK = Phase( name="Phase", voltage=230.1, amperage=3.1, status_voltage=Status.OK, status_amperage=Status.OK, ) rokam-sunweg-cb8c67c/tests/responses/000077500000000000000000000000001462571353200200255ustar00rootroot00000000000000rokam-sunweg-cb8c67c/tests/responses/auth_fail_response.json000066400000000000000000000000641462571353200245720ustar00rootroot00000000000000{ "success": false, "message": "Error message" }rokam-sunweg-cb8c67c/tests/responses/auth_success_response.json000066400000000000000000000006011462571353200253240ustar00rootroot00000000000000{"success":true,"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2NzcyNjQzODEsImlzcyI6Imh0dHA6XC9cL2llcy5nb3YiLCJleHAiOjE2Nzc4NjkxODEsImhvc3QiOiIxOTEuMzMuMTg1LjEyOSIsImRhdGEiOnsidXN1YXJpb2lkIjo2NTQzLCJpZHBlcmZpbCI6NiwiaWRyZWxhY2lvbmFkbyI6MTAxOCwiaWRmcmFucXVlYWRvIjpudWxsLCJhbGVydGFzIjp0cnVlLCJjb250cm9sZSI6dHJ1ZSwiZnJlZSI6ZmFsc2V9fQ.907NnIUaMhVl2HVzAuzPTvVdD-huHNg932Eu2zj7-0"}rokam-sunweg-cb8c67c/tests/responses/error_401_response.txt000066400000000000000000000000141462571353200242140ustar00rootroot00000000000000Unauthorizedrokam-sunweg-cb8c67c/tests/responses/error_500_response.txt000066400000000000000000000000251462571353200242160ustar00rootroot00000000000000Internal server errorrokam-sunweg-cb8c67c/tests/responses/inverter_success_response.json000066400000000000000000000032411462571353200262240ustar00rootroot00000000000000{ "success": true, "temperatura": 80, "temperaturaStatus": 0, "fatorpotencia": "0,00", "stringmppt": [ { "nomemppt": "MPPT 01", "strings": [ { "nome": "ST 01", "variaveltensao": "Upv1", "variavelcorrente": "Ipv1", "situacao": 1 }, { "nome": "ST 02", "variaveltensao": "Upv2", "variavelcorrente": "Ipv2", "situacao": 1 } ] }, { "nomemppt": "MPPT 02", "strings": [ { "nome": "ST 03", "variaveltensao": "Upv3", "variavelcorrente": "Ipv3", "situacao": 1 }, { "nome": "ST 04", "variaveltensao": "Upv4", "variavelcorrente": "Ipv4", "situacao": 1 } ] } ], "inversor": { "id": 21255, "nome": "Inverter Name", "descricao": "Inverter Description", "esn": "1234ABC", "situacao": 1, "tensaoca": 242, "temperatura": 80, "leitura": { "Upv1": 429.6000000000000227373675443232059478759765625, "Upv2": 415, "Upv3": 418, "Upv4": 525.13, "Ipv1": 3, "Ipv2": 2.1, "Ipv3": 4.2, "Ipv4": 5 } }, "correnteCA": { "faseA": "0,20", "faseB": "0,50", "faseC": "0,40", "faseAstatus": 0, "faseBstatus": 0, "faseCstatus": 0 }, "tensaoca": { "faseA": "11,10", "faseB": "11,30", "faseC": "10,20", "faseAstatus": 1, "faseBstatus": 1, "faseCstatus": 1 }, "potenciaativa": "0,00 kW", "energiadodia": "0,00 kWh", "energiaacumulada": "23,20 kWh", "frequencia": "59,85", "statusInversor": 0 }rokam-sunweg-cb8c67c/tests/responses/list_plant_success_1_response.json000066400000000000000000000001551462571353200267600ustar00rootroot00000000000000{"success":true,"conectadas":[{"id":16925}],"nao_comissionadas":[],"falhas":[],"alertas":[],"atendimento":[]}rokam-sunweg-cb8c67c/tests/responses/list_plant_success_2_response.json000066400000000000000000000001721462571353200267600ustar00rootroot00000000000000{"success":true,"conectadas":[{"id":16925},{"id":16926}],"nao_comissionadas":[],"falhas":[],"alertas":[],"atendimento":[]}rokam-sunweg-cb8c67c/tests/responses/list_plant_success_none_response.json000066400000000000000000000001411462571353200275520ustar00rootroot00000000000000{"success":true,"conectadas":[],"nao_comissionadas":[],"falhas":[],"alertas":[],"atendimento":[]}rokam-sunweg-cb8c67c/tests/responses/month_stats_fail_response.json000066400000000000000000000000641462571353200261740ustar00rootroot00000000000000{ "success": false, "message": "Error message" }rokam-sunweg-cb8c67c/tests/responses/month_stats_success_response.json000066400000000000000000000230241462571353200267320ustar00rootroot00000000000000{ "graficomes": [ { "energiapordia": 116.9, "prognostico": "111.0322580645161290322580645", "tempoatual": "Wed, 01 May 2024 00:00:00 GMT" }, { "energiapordia": 113.7, "prognostico": "111.0322580645161290322580645", "tempoatual": "Thu, 02 May 2024 00:00:00 GMT" }, { "energiapordia": 112.4, "prognostico": "111.0322580645161290322580645", "tempoatual": "Fri, 03 May 2024 00:00:00 GMT" }, { "energiapordia": 133.3, "prognostico": "111.0322580645161290322580645", "tempoatual": "Sat, 04 May 2024 00:00:00 GMT" }, { "energiapordia": 129.2, "prognostico": "111.0322580645161290322580645", "tempoatual": "Sun, 05 May 2024 00:00:00 GMT" }, { "energiapordia": 86.5, "prognostico": "111.0322580645161290322580645", "tempoatual": "Mon, 06 May 2024 00:00:00 GMT" }, { "energiapordia": 116.2, "prognostico": "111.0322580645161290322580645", "tempoatual": "Tue, 07 May 2024 00:00:00 GMT" }, { "energiapordia": 125.8, "prognostico": "111.0322580645161290322580645", "tempoatual": "Wed, 08 May 2024 00:00:00 GMT" }, { "energiapordia": 127.5, "prognostico": "111.0322580645161290322580645", "tempoatual": "Thu, 09 May 2024 00:00:00 GMT" }, { "energiapordia": 118.7, "prognostico": "111.0322580645161290322580645", "tempoatual": "Fri, 10 May 2024 00:00:00 GMT" }, { "energiapordia": 122.6, "prognostico": "111.0322580645161290322580645", "tempoatual": "Sat, 11 May 2024 00:00:00 GMT" }, { "energiapordia": 123.3, "prognostico": "111.0322580645161290322580645", "tempoatual": "Sun, 12 May 2024 00:00:00 GMT" }, { "energiapordia": 96.3, "prognostico": "111.0322580645161290322580645", "tempoatual": "Mon, 13 May 2024 00:00:00 GMT" }, { "energiapordia": 104.3, "prognostico": "111.0322580645161290322580645", "tempoatual": "Tue, 14 May 2024 00:00:00 GMT" }, { "energiapordia": 120.9, "prognostico": "111.0322580645161290322580645", "tempoatual": "Wed, 15 May 2024 00:00:00 GMT" }, { "energiapordia": 120.8, "prognostico": "111.0322580645161290322580645", "tempoatual": "Thu, 16 May 2024 00:00:00 GMT" }, { "energiapordia": 115.1, "prognostico": "111.0322580645161290322580645", "tempoatual": "Fri, 17 May 2024 00:00:00 GMT" }, { "energiapordia": 118.8, "prognostico": "111.0322580645161290322580645", "tempoatual": "Sat, 18 May 2024 00:00:00 GMT" }, { "energiapordia": 114.5, "prognostico": "111.0322580645161290322580645", "tempoatual": "Sun, 19 May 2024 00:00:00 GMT" }, { "energiapordia": 102.4, "prognostico": "111.0322580645161290322580645", "tempoatual": "Mon, 20 May 2024 00:00:00 GMT" }, { "energiapordia": 106.9, "prognostico": "111.0322580645161290322580645", "tempoatual": "Tue, 21 May 2024 00:00:00 GMT" }, { "energiapordia": 117.1, "prognostico": "111.0322580645161290322580645", "tempoatual": "Wed, 22 May 2024 00:00:00 GMT" }, { "energiapordia": 105.6, "prognostico": "111.0322580645161290322580645", "tempoatual": "Thu, 23 May 2024 00:00:00 GMT" }, { "energiapordia": 99.1, "prognostico": "111.0322580645161290322580645", "tempoatual": "Fri, 24 May 2024 00:00:00 GMT" }, { "energiapordia": 104.4, "prognostico": "111.0322580645161290322580645", "tempoatual": "Sat, 25 May 2024 00:00:00 GMT" }, { "energiapordia": 96.7, "prognostico": "111.0322580645161290322580645", "tempoatual": "Sun, 26 May 2024 00:00:00 GMT" }, { "energiapordia": 110.4, "prognostico": "111.0322580645161290322580645", "tempoatual": "Mon, 27 May 2024 00:00:00 GMT" }, { "energiapordia": 108.6, "prognostico": "111.0322580645161290322580645", "tempoatual": "Tue, 28 May 2024 00:00:00 GMT" }, { "energiapordia": 58.3, "prognostico": "111.0322580645161290322580645", "tempoatual": "2024-05-29" }, { "energiapordia": 0, "prognostico": "111.0322580645161290322580645", "tempoatual": "2024-05-30" }, { "energiapordia": 0, "prognostico": "111.0322580645161290322580645", "tempoatual": "2024-05-31" } ], "graficomescalc": [ { "energiapordia": 0, "prognostico": 0, "tempoatual": "Wed, 01 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Thu, 02 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Fri, 03 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Sat, 04 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Sun, 05 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Mon, 06 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Tue, 07 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Wed, 08 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Thu, 09 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Fri, 10 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Sat, 11 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Sun, 12 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Mon, 13 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Tue, 14 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Wed, 15 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Thu, 16 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Fri, 17 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Sat, 18 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Sun, 19 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Mon, 20 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Tue, 21 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Wed, 22 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Thu, 23 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Fri, 24 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Sat, 25 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Sun, 26 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Mon, 27 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "Tue, 28 May 2024 00:00:00 GMT" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "2024-05-29" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "2024-05-30" }, { "energiapordia": 0, "prognostico": 0, "tempoatual": "2024-05-31" } ], "success": true }rokam-sunweg-cb8c67c/tests/responses/plant_success_alt_response.json000066400000000000000000000011211462571353200263370ustar00rootroot00000000000000{ "success": true, "usinas": { "id": 16925, "nome": "Plant Name", "inversores": [ { "id": 21255, "nome": "Inverter Name", "descricao": "Inverter Description", "esn": "1234ABC", "situacao": 1, "tensaoca": 242, "temperatura": 80 } ] }, "ultimaAtualizacao": null, "AcumuladoPotencia": "25,23 kW", "energiadia": "1,23 kWh", "energiaacumuladanumber": "23.20", "taxaPerformance": 1.48, "KWHporkWp": "1,2", "PerformanceRate": 0.1, "reduz_carbono_total_number": 0.012296, "economia": "12,78" }rokam-sunweg-cb8c67c/tests/responses/plant_success_response.json000066400000000000000000000011421462571353200255020ustar00rootroot00000000000000{ "success": true, "usinas": { "id": 16925, "nome": "Plant Name", "inversores": [ { "id": 21255, "nome": "Inverter Name", "descricao": "Inverter Description", "esn": "1234ABC", "situacao": 1, "tensaoca": 242, "temperatura": 80 } ] }, "ultimaAtualizacao": "2023-02-25 08:04:22", "AcumuladoPotencia": "25,23 kW", "energiadia": "1,23 kWh", "energiaacumuladanumber": "23.20", "taxaPerformance": 1.48, "KWHporkWp": "1,2", "PerformanceRate": 0.1, "reduz_carbono_total_number": 0.012296, "economia": "12,78" }rokam-sunweg-cb8c67c/tests/test_api.py000066400000000000000000000425501462571353200201740ustar00rootroot00000000000000"""Test sunweg.api.""" from datetime import date, datetime from os import path import os from unittest import TestCase from unittest.mock import MagicMock, patch import pytest from requests import Response from sunweg.api import ( APIHelper, convert_situation_status, SunWegApiError, separate_value_metric, ) from sunweg.device import Inverter, String from sunweg.util import Status from .common import INVERTER_MOCK, PLANT_MOCK class Api_Test(TestCase): """APIHelper test case.""" responses: dict[str, Response] = {} def setUp(self) -> None: """Set tests up.""" for file in os.listdir(path.join(path.dirname(__file__), "responses")): filename = path.basename(file) with open(path.join(path.dirname(__file__), "responses", file)) as f: response = Response() if filename.startswith("error"): response.status_code = int(filename.split("_")[1]) response.reason = "".join(f.readlines()) else: response.status_code = 200 response._content = "".join(f.readlines()).encode() self.responses[filename] = response def test_convert_situation_status(self) -> None: """Test the conversion from situation to status.""" status_ok: Status = convert_situation_status(1) status_err: Status = convert_situation_status(0) status_wrn: Status = convert_situation_status(2) assert status_ok == Status.OK assert status_err == Status.ERROR assert status_wrn == Status.WARN def test_separate_value_metric_comma(self) -> None: """Test the separation from value and metric of string with comma.""" (value, metric) = separate_value_metric("0,0") assert value == 0 assert metric == "" (value, metric) = separate_value_metric("1,0", "W") assert value == 1.0 assert metric == "W" (value, metric) = separate_value_metric("0,2 kW", "W") assert value == 0.2 assert metric == "kW" def test_separate_value_metric_dot(self) -> None: """Test the separation from value and metric of string with dot.""" (value, metric) = separate_value_metric("0.0") assert value == 0 assert metric == "" (value, metric) = separate_value_metric("1.0", "W") assert value == 1.0 assert metric == "W" (value, metric) = separate_value_metric("0.2 kW", "W") assert value == 0.2 assert metric == "kW" def test_separate_value_metric_none_int(self) -> None: """Test the separation from value and metric of string with dot.""" (value, metric) = separate_value_metric(None) assert value == 0 assert metric == "" (value, metric) = separate_value_metric("1", "W") assert value == 1.0 assert metric == "W" (value, metric) = separate_value_metric("2 kW", "W") assert value == 2.0 assert metric == "kW" def test_error500(self) -> None: """Test error 500.""" with patch( "requests.Session.post", return_value=self.responses["error_500_response.txt"], ): api = APIHelper("user@acme.com", "password") with pytest.raises(SunWegApiError) as e_info: api.authenticate() assert e_info.value.__str__() == "Request failed: " def test_authenticate_success(self) -> None: """Test authentication success.""" with patch( "requests.Session.post", return_value=self.responses["auth_success_response.json"], ): api = APIHelper("user@acme.com", "password") assert api.authenticate() def test_authenticate_failed(self) -> None: """Test authentication failed.""" with patch( "requests.Session.post", return_value=self.responses["auth_fail_response.json"], ): api = APIHelper("user@acme.com", "password") assert not api.authenticate() def test_list_plants_none_success(self) -> None: """Test list plants with empty plant list.""" with patch( "requests.Session.get", return_value=self.responses["list_plant_success_none_response.json"], ), patch("sunweg.api.APIHelper.plant", return_value=PLANT_MOCK): api = APIHelper("user@acme.com", "password") assert len(api.listPlants()) == 0 def test_list_plants_1_success(self) -> None: """Test list plants with one plant in the list.""" with patch( "requests.Session.get", return_value=self.responses["list_plant_success_1_response.json"], ), patch("sunweg.api.APIHelper.plant", return_value=PLANT_MOCK): api = APIHelper("user@acme.com", "password") assert len(api.listPlants()) == 1 def test_list_plants_2_success(self) -> None: """Test list plants with two plant in the list.""" with patch( "requests.Session.get", return_value=self.responses["list_plant_success_2_response.json"], ), patch("sunweg.api.APIHelper.plant", return_value=PLANT_MOCK): api = APIHelper("user@acme.com", "password") assert len(api.listPlants()) == 2 def test_list_plants_401(self) -> None: """Test list plants with expired token.""" with patch( "requests.Session.post", return_value=self.responses["auth_success_response.json"], ), patch( "requests.Session.get", return_value=self.responses["error_401_response.txt"], ): api = APIHelper("user@acme.com", "password") assert len(api.listPlants()) == 0 def test_plant_success(self) -> None: """Test plant success.""" with patch( "requests.Session.get", return_value=self.responses["plant_success_response.json"], ), patch("sunweg.api.APIHelper.inverter", return_value=INVERTER_MOCK): api = APIHelper("user@acme.com", "password") plant = api.plant(16925) assert plant is not None assert plant.id == 16925 assert plant.name == "Plant Name" assert plant.total_power == 25.23 assert plant.last_update == datetime(2023, 2, 25, 8, 4, 22) assert plant.kwh_per_kwp == 0.0 assert plant.performance_rate == 0.0 assert plant.saving == 12.78 assert plant.today_energy == 1.23 assert plant.today_energy_metric == "kWh" assert plant.total_carbon_saving == 0.012296 assert plant.total_energy == 23.2 assert plant.__str__().startswith("") assert len(plant.inverters) == 1 for inverter in plant.inverters: assert inverter.id == 21255 assert inverter.name == "Inverter Name" assert inverter.frequency == 0 assert inverter.power == 0.0 assert inverter.power_metric == "" assert inverter.power_factor == 0.0 assert inverter.sn == "1234ABC" assert inverter.status == Status.ERROR assert inverter.temperature == 80 assert inverter.today_energy == 0.0 assert inverter.today_energy_metric == "" assert inverter.total_energy == 0.0 assert inverter.total_energy_metric == "" assert not inverter.is_complete def test_plant_success_alt(self) -> None: """Test plant success.""" with patch( "requests.Session.get", return_value=self.responses["plant_success_alt_response.json"], ), patch("sunweg.api.APIHelper.inverter", return_value=INVERTER_MOCK): api = APIHelper("user@acme.com", "password") plant = api.plant(16925) assert plant is not None assert plant.id == 16925 assert plant.name == "Plant Name" assert plant.total_power == 25.23 assert plant.last_update is None assert plant.kwh_per_kwp == 0.0 assert plant.performance_rate == 0.0 assert plant.saving == 12.78 assert plant.today_energy == 1.23 assert plant.today_energy_metric == "kWh" assert plant.total_carbon_saving == 0.012296 assert plant.total_energy == 23.2 assert plant.__str__().startswith("") assert len(plant.inverters) == 1 for inverter in plant.inverters: assert inverter.id == 21255 assert inverter.name == "Inverter Name" assert inverter.frequency == 0 assert inverter.power == 0.0 assert inverter.power_metric == "" assert inverter.power_factor == 0.0 assert inverter.sn == "1234ABC" assert inverter.status == Status.ERROR assert inverter.temperature == 80 assert inverter.today_energy == 0.0 assert inverter.today_energy_metric == "" assert inverter.total_energy == 0.0 assert inverter.total_energy_metric == "" assert not inverter.is_complete def test_plant_401(self) -> None: """Test plant with expired token.""" with patch( "requests.Session.post", return_value=self.responses["auth_success_response.json"], ), patch( "requests.Session.get", return_value=self.responses["error_401_response.txt"], ): api = APIHelper("user@acme.com", "password") assert api.plant(16925) is None def test_inverter_success(self) -> None: """Test inverter success.""" with patch( "requests.Session.get", return_value=self.responses["inverter_success_response.json"], ): api = APIHelper("user@acme.com", "password") inverter = api.inverter(21255) assert inverter is not None assert inverter.id == 21255 assert inverter.name == "Inverter Name" assert inverter.frequency == 59.85 assert inverter.power == 0.0 assert inverter.power_metric == "kW" assert inverter.power_factor == 0.0 assert inverter.sn == "1234ABC" assert inverter.status == Status.OK assert inverter.temperature == 80 assert inverter.today_energy == 0.0 assert inverter.today_energy_metric == "kWh" assert inverter.total_energy == 23.2 assert inverter.today_energy_metric == "kWh" strings: list[String] = [] for mppt in inverter.mppts: assert mppt.__str__().startswith("") assert mppt.name != "" strings.extend(mppt.strings) assert len(strings) == 4 assert len(inverter.phases) == 3 assert inverter.__str__().startswith("") for string in strings: assert string.name != "" assert string.amperage != 0 assert string.voltage != 0 assert string.status == Status.OK assert string.__str__().startswith("") for phase in inverter.phases: assert phase.name != "" assert phase.amperage != 0 assert phase.voltage != 0 assert phase.status_amperage == Status.OK assert phase.status_voltage == Status.ERROR assert phase.__str__().startswith("") def test_inverter_401(self) -> None: """Test inverter with expired token.""" with patch( "requests.Session.post", return_value=self.responses["auth_success_response.json"], ), patch( "requests.Session.get", return_value=self.responses["error_401_response.txt"], ): api = APIHelper("user@acme.com", "password") assert api.inverter(21255) is None def test_complete_inverter_success(self) -> None: """Test complete inverter success.""" with patch( "requests.Session.get", return_value=self.responses["inverter_success_response.json"], ): api = APIHelper("user@acme.com", "password") inverter = Inverter( id=12345, name="Other inverter name", sn="1234ABCD", status=Status.ERROR, temperature=70, ) api.complete_inverter(inverter) assert inverter is not None assert inverter.id == 12345 assert inverter.name == "Other inverter name" assert inverter.frequency == 59.85 assert inverter.power == 0.0 assert inverter.power_factor == 0.0 assert inverter.sn == "1234ABCD" assert inverter.status == Status.ERROR assert inverter.temperature == 70 assert inverter.today_energy == 0.0 assert inverter.total_energy == 23.2 strings: list[String] = [] for mppt in inverter.mppts: assert mppt.__str__().startswith("") assert mppt.name != "" strings.extend(mppt.strings) assert len(strings) == 4 assert len(inverter.phases) == 3 assert inverter.__str__().startswith("") for string in strings: assert string.name != "" assert string.amperage != 0 assert string.voltage != 0 assert string.status == Status.OK assert string.__str__().startswith("") for phase in inverter.phases: assert phase.name != "" assert phase.amperage != 0 assert phase.voltage != 0 assert phase.status_amperage == Status.OK assert phase.status_voltage == Status.ERROR assert phase.__str__().startswith("") def test_complete_inverter_401(self) -> None: """Test complete inverter with expired token.""" with patch( "requests.Session.post", return_value=self.responses["auth_success_response.json"], ), patch( "requests.Session.get", return_value=self.responses["error_401_response.txt"], ): api = APIHelper("user@acme.com", "password") inverter = Inverter( id=12345, name="Other inverter name", sn="1234ABCD", status=Status.ERROR, temperature=70, ) api.complete_inverter(inverter) assert not inverter.is_complete def test_setters(self) -> None: """Test API setters.""" api = APIHelper("user@acme.com", "password") assert api._username == "user@acme.com" assert api._password == "password" api.username = "user1@acme.com" api.password = "password1" assert api._username == "user1@acme.com" assert api._password == "password1" def test_month_stats_fail(self) -> None: """Test month stats with error from server.""" with patch( "requests.Session.get", return_value=self.responses["month_stats_fail_response.json"], ): api = APIHelper("user@acme.com", "password") plant = MagicMock() plant.id = 1 with pytest.raises(SunWegApiError) as e_info: api.month_stats_production(2013, 12, plant) assert e_info.value.__str__() == "Error message" def test_month_stats_401(self) -> None: """Test month stats with data from server with expired token.""" with patch( "requests.Session.post", return_value=self.responses["auth_success_response.json"], ), patch( "requests.Session.get", return_value=self.responses["error_401_response.txt"], ): api = APIHelper("user@acme.com", "password") plant = MagicMock() plant.id = 1 stats = api.month_stats_production(2023, 12, plant) assert isinstance(stats, list) assert len(stats) == 0 def test_month_stats_success(self) -> None: """Test month stats with data from server.""" with patch( "requests.Session.get", return_value=self.responses["month_stats_success_response.json"], ): api = APIHelper("user@acme.com", "password") plant = MagicMock() plant.id = 1 stats = api.month_stats_production(2023, 12, plant) assert len(stats) > 0 i: int = 1 for stat in stats: assert stat.date == date(2024, 5, i) assert isinstance(stat.production, float) assert stat.prognostic == 111.03225806451613 assert stat.__str__().startswith( "" ) i += 1