summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoger Gonzalez <roger@rogs.me>2023-08-16 22:48:11 -0300
committerRoger Gonzalez <roger@rogs.me>2023-08-16 22:48:11 -0300
commitb8e1cb50d75b225fd07d9f4869921a152337c520 (patch)
tree1cb43116e259ea6c0020f052ab3114e24e54de39
Initial commit
-rw-r--r--.env.example2
-rwxr-xr-x.flake82
-rw-r--r--.gitignore181
-rwxr-xr-x.pre-commit-config.yaml25
-rwxr-xr-xpyproject.toml40
-rwxr-xr-xscript.py151
6 files changed, 401 insertions, 0 deletions
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..37d6873
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,2 @@
+DEVICE_ID=12345
+AUTHORIZATION=authorization_code
diff --git a/.flake8 b/.flake8
new file mode 100755
index 0000000..eb31e88
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+max-line-length = 121
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0f2e980
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,181 @@
+.DS_Store
+.idea
+*.log
+tmp/
+
+# Created by https://www.toptal.com/developers/gitignore/api/python
+# Edit at https://www.toptal.com/developers/gitignore?templates=python
+
+### Python ###
+# 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/
+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/
+cover/
+
+# 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
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .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
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__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/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+### Python Patch ###
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+poetry.toml
+
+# ruff
+.ruff_cache/
+
+# LSP config files
+pyrightconfig.json
+
+# End of https://www.toptal.com/developers/gitignore/api/python
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100755
index 0000000..6304ab9
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,25 @@
+exclude: ^(.bzr|\.direnv|\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.pants\.d|\.svn|\.tox|\.venv|_build|buck-out|build|dist|node_modules|venv|\.idea|dockerdata|static|.*\b(migrations)\b.*)
+repos:
+- repo: https://github.com/pycqa/isort
+ rev: 5.12.0
+ hooks:
+ - id: isort
+- repo: https://github.com/ambv/black
+ rev: 22.3.0
+ hooks:
+ - id: black
+ language_version: python3
+- repo: https://github.com/pycqa/flake8
+ rev: 3.9.2
+ hooks:
+ - id: flake8
+ additional_dependencies:
+ - flake8-bugbear==20.1.4
+ - flake8-builtins==1.5.3
+ - flake8-comprehensions==3.2.3
+ - flake8-tidy-imports==4.1.0
+ - flake8-eradicate==1.1.0
+ - flake8-print==4.0.0
+ - flake8-return==1.1.2
+ - flake8-use-fstring==1.1
+ - git+https://github.com/derrix060/flake8-expression-complexity.git
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100755
index 0000000..37ff8e2
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,40 @@
+[tool.black]
+line-length = 121
+target-version = ['py36', 'py37', 'py38']
+include = '\.pyi?$'
+# regex below includes default list from isort, for parity
+exclude = '''
+/(
+ \.bzr
+ | \.direnv
+ | \.eggs
+ | \.git
+ | \.hg
+ | \.mypy_cache
+ | \.nox
+ | \.pants\.d
+ | \.svn
+ | \.tox
+ | \.venv
+ | _build
+ | buck-out
+ | build
+ | dist
+ | node_modules
+ | venv
+ | \.idea
+ | dockerdata
+ | static
+)/
+'''
+
+[tool.isort]
+# these are black-compatible settings
+multi_line_output = 3
+include_trailing_comma = true
+force_grid_wrap = 0
+use_parentheses = true
+ensure_newline_before_comments = true
+line_length = 121
+skip = "dockerdata,.idea,static"
+filter_files = true
diff --git a/script.py b/script.py
new file mode 100755
index 0000000..96464b6
--- /dev/null
+++ b/script.py
@@ -0,0 +1,151 @@
+import os
+from datetime import datetime, timedelta
+from time import sleep
+from typing import Optional
+
+import requests
+from dotenv import load_dotenv
+
+load_dotenv()
+
+DEVICE_ID = os.environ.get("DEVICE_ID")
+AUTHORIZATION = os.environ.get("AUTHORIZATION")
+
+
+def make_request(method: str, url: str, data: Optional[dict] = None) -> requests.Response:
+ """
+ Make request
+
+ :param method: Method
+ :param url: URL
+ :param data: Data
+ :return: Response
+ """
+
+ headers = {
+ "Authorization": f"Bearer {AUTHORIZATION}",
+ "X-Client-Type": "Android",
+ "User-Agent": "okhttp/3.8.1",
+ "Content-Type": "application/json; charset=utf-8",
+ "Connection": "Keep-Alive",
+ "User-Agent": "okhttp/3.8.1",
+ }
+
+ if method == "GET":
+ return requests.get(url, headers=headers)
+
+ if method == "POST":
+ return requests.post(url, headers=headers, json=data)
+
+ return None
+
+
+def generate_ute_info(
+ device_id: str, cost_per_kwh: float, date_start: Optional[str] = None, date_end: Optional[str] = None
+) -> dict:
+ """
+ Generate UTE info from device id and date range
+
+ :param device_id: Device id
+ :param authorization: Authorization token
+ :param cost_per_kwh: Cost per kwh
+ :param date_start: Date start
+ :param date_end: Date end
+ :return: UTE info
+ """
+
+ if date_start is None:
+ yesterday = datetime.now() - timedelta(days=1)
+ date_start = yesterday.strftime("%Y-%m-%d")
+
+ if date_end is None:
+ yesterday = datetime.now() - timedelta(days=1)
+ date_end = yesterday.strftime("%Y-%m-%d")
+
+ url = f"https://rocme.ute.com.uy/api/v2/device/{device_id}/curvefromtodate/D/{date_start}/{date_end}"
+
+ response = make_request("GET", url).json()
+
+ active_energy = {"total": {"sum_in_kwh": 0}}
+
+ for item in response["data"]:
+ if item["magnitudeVO"] == "IMPORT_ACTIVE_ENERGY":
+ date = datetime.strptime(item["date"], "%Y-%m-%dT%H:%M:%S%z")
+ day_in_week = date.strftime("%A")
+ value = round(float(item["value"]), 3)
+
+ active_energy[date.strftime("%d/%m/%Y")] = {
+ "kwh": value,
+ "aproximated_cost_in_uyu": round(value * cost_per_kwh, 3),
+ "day_in_week": day_in_week,
+ }
+ active_energy["total"]["sum_in_kwh"] = active_energy["total"]["sum_in_kwh"] + value
+
+ active_energy["total"]["aproximated_cost_in_uyu"] = round(active_energy["total"]["sum_in_kwh"] * average_price, 3)
+ active_energy["total"]["daily_average_cost"] = round(
+ active_energy["total"]["aproximated_cost_in_uyu"] / (len(active_energy) - 1), 3
+ )
+ return active_energy
+
+
+def get_current_usage(device_id: str) -> dict:
+ """
+ Get current usage
+
+ :param device_id: Device id
+ :param authorization: Authorization token
+ :raises Exception: Error getting reading request
+ :return: Current usage
+ """
+ reading_request_url = "https://rocme.ute.com.uy/api/v1/device/readingRequest"
+ reading_url = f"https://rocme.ute.com.uy/api/v1/device/{device_id}/lastReading/30"
+
+ data = {"AccountServicePointId": device_id}
+
+ reading_request = make_request("POST", reading_request_url, data)
+
+ if reading_request.status_code != 200:
+ raise Exception("Error getting reading request")
+
+ response = make_request("GET", reading_url).json()
+
+ while not response["success"]:
+ sleep(5)
+ response = make_request("GET", reading_url).json()
+
+ readings = response["data"]["readings"]
+
+ for reading in readings:
+ reading_type = reading["tipoLecturaMGMI"]
+ if reading_type == "I1":
+ i1 = float(reading["valor"])
+ elif reading_type == "I2":
+ i2 = float(reading["valor"])
+ elif reading_type == "I3":
+ i3 = float(reading["valor"])
+ elif reading_type == "V1":
+ v1 = float(reading["valor"])
+ elif reading_type == "V2":
+ v2 = float(reading["valor"])
+ elif reading_type == "V3":
+ v3 = float(reading["valor"])
+
+ power_1_in_watts = v1 * i1
+ power_2_in_watts = v2 * i2
+ power_3_in_watts = v3 * i3
+
+ power_in_watts = round(power_1_in_watts + power_2_in_watts + power_3_in_watts, 3)
+
+ return_dict = {**response}
+ return_dict["data"]["power_in_watts"] = power_in_watts
+
+ return return_dict
+
+
+if __name__ == "__main__":
+ date_start = "2023-08-01"
+ date_end = "2023-08-31"
+ average_price = (10.680 * 0.1667) + (2.223 * 0.2917) + (4.875 * 0.5416) # Each price * percentage of time on the day
+
+ ute_historic_usage = generate_ute_info(DEVICE_ID, average_price, date_start, date_end)
+ ute_current_usage = get_current_usage(DEVICE_ID)