oocone 0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
oocone-0.1/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC
2
+
3
+ Copyright (c) 2024, Simon Leiner
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
oocone-0.1/PKG-INFO ADDED
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.1
2
+ Name: oocone
3
+ Version: 0.1
4
+ Summary: Unofficial library for interacting with an enocoo energy management system
5
+ Author-Email: Simon Leiner <simon@leiner.me>
6
+ License: ISC
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Programming Language :: Python :: 3
9
+ Project-URL: Repository, https://github.com/sleiner/oocone
10
+ Project-URL: Homepage, https://github.com/sleiner/oocone
11
+ Requires-Python: >=3.11
12
+ Requires-Dist: aiohttp>=3.9.4
13
+ Requires-Dist: beautifulsoup4>=4.12.3
14
+ Description-Content-Type: text/markdown
15
+
16
+ # oocone
17
+
18
+ [![Tests](https://github.com/sleiner/oocone/workflows/Tests/badge.svg)](https://github.com/sleiner/oocone/actions?query=workflow%3Aci)
19
+ [![pypi version](https://img.shields.io/pypi/v/oocone.svg)](https://pypi.org/project/oocone/)
20
+ [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://astral.sh/ruff)
21
+ [![pdm-managed](https://img.shields.io/badge/pdm-managed-blueviolet)](https://pdm.fming.dev)
22
+
23
+ Unofficial library for interacting with an enocoo energy management system
24
+
25
+ ## Requirements
26
+
27
+ oocone requires Python >= 3.11.
oocone-0.1/README.md ADDED
@@ -0,0 +1,12 @@
1
+ # oocone
2
+
3
+ [![Tests](https://github.com/sleiner/oocone/workflows/Tests/badge.svg)](https://github.com/sleiner/oocone/actions?query=workflow%3Aci)
4
+ [![pypi version](https://img.shields.io/pypi/v/oocone.svg)](https://pypi.org/project/oocone/)
5
+ [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://astral.sh/ruff)
6
+ [![pdm-managed](https://img.shields.io/badge/pdm-managed-blueviolet)](https://pdm.fming.dev)
7
+
8
+ Unofficial library for interacting with an enocoo energy management system
9
+
10
+ ## Requirements
11
+
12
+ oocone requires Python >= 3.11.
@@ -0,0 +1,136 @@
1
+ [project]
2
+ name = "oocone"
3
+ description = "Unofficial library for interacting with an enocoo energy management system"
4
+ authors = [
5
+ { name = "Simon Leiner", email = "simon@leiner.me" },
6
+ ]
7
+ readme = "README.md"
8
+ requires-python = ">=3.11"
9
+ dependencies = [
10
+ "aiohttp>=3.9.4",
11
+ "beautifulsoup4>=4.12.3",
12
+ ]
13
+ dynamic = []
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Programming Language :: Python :: 3",
17
+ ]
18
+ version = "0.1"
19
+
20
+ [project.license]
21
+ text = "ISC"
22
+
23
+ [project.urls]
24
+ Repository = "https://github.com/sleiner/oocone"
25
+ Homepage = "https://github.com/sleiner/oocone"
26
+
27
+ [build-system]
28
+ requires = [
29
+ "pdm-backend",
30
+ ]
31
+ build-backend = "pdm.backend"
32
+
33
+ [tool.pdm.version]
34
+ source = "scm"
35
+
36
+ [tool.pdm.build]
37
+ package-dir = "src"
38
+
39
+ [tool.pdm.dev-dependencies]
40
+ test = [
41
+ "pytest>=8.1.1",
42
+ "pytest-aiohttp>=1.0.5",
43
+ "pytest-asyncio>=0.23.6",
44
+ "pytest-cov>=5.0.0",
45
+ "typeguard>=4.2.1",
46
+ ]
47
+ dev = [
48
+ "towncrier>=23.11.0",
49
+ "parver>=0.5",
50
+ ]
51
+ doc = [
52
+ "mkdocs>=1.5.3",
53
+ "mkdocs-material>=9.5.17",
54
+ ]
55
+
56
+ [tool.pdm.scripts]
57
+ release = "python -m tasks.release"
58
+ update_api_responses = "python -m tasks.update_api_responses"
59
+ test = "pytest tests/"
60
+ lint = "pre-commit run --all-files"
61
+
62
+ [tool.pdm.scripts.doc]
63
+ shell = "cd docs && mkdocs serve"
64
+ help = "Start the dev server for doc preview"
65
+
66
+ [tool.pytest.ini_options]
67
+ markers = [
68
+ "mocked_api: mark test as using a mock of the enocoo website",
69
+ ]
70
+ addopts = "--typeguard-packages=oocone --cov=oocone --cov-report=html"
71
+
72
+ [tool.ruff]
73
+ line-length = 100
74
+ src = [
75
+ "src",
76
+ ]
77
+ target-version = "py312"
78
+
79
+ [tool.ruff.lint]
80
+ select = [
81
+ "B",
82
+ "C4",
83
+ "E",
84
+ "F",
85
+ "I001",
86
+ "PGH",
87
+ "RUF",
88
+ "TCH",
89
+ "W",
90
+ "YTT",
91
+ ]
92
+ extend-ignore = [
93
+ "B018",
94
+ "B019",
95
+ ]
96
+
97
+ [tool.ruff.lint.mccabe]
98
+ max-complexity = 10
99
+
100
+ [tool.ruff.lint.isort]
101
+ known-first-party = [
102
+ "oocone",
103
+ ]
104
+
105
+ [tool.mypy]
106
+ follow_imports = "silent"
107
+ disallow_incomplete_defs = true
108
+ disallow_untyped_defs = true
109
+ disallow_untyped_decorators = true
110
+ namespace_packages = true
111
+ mypy_path = "src"
112
+ explicit_package_bases = true
113
+
114
+ [[tool.mypy.overrides]]
115
+ module = [
116
+ "pytest_aiohttp.plugin",
117
+ ]
118
+ ignore_missing_imports = true
119
+
120
+ [tool.towncrier]
121
+ package = "oocone"
122
+ filename = "CHANGELOG.md"
123
+ issue_format = "[#{issue}](https://github.com/sleiner/oocone/issues/{issue})"
124
+ directory = "news/"
125
+ start_string = "<!-- insertion marker -->"
126
+ title_format = "[v{version}](https://github.com/sleiner/oocone/releases/tag/{version}) ({project_date})"
127
+ template = "news/towncrier_template.md"
128
+ underlines = "-~^"
129
+ type = [
130
+ { directory = "feature", name = "Features & Improvements", showcontent = true },
131
+ { directory = "bugfix", name = "Bug Fixes", showcontent = true },
132
+ { directory = "doc", name = "Improved Documentation", showcontent = true },
133
+ { directory = "dep", name = "Dependencies", showcontent = true },
134
+ { directory = "removal", name = "Removals and Deprecations", showcontent = true },
135
+ { directory = "misc", name = "Miscellaneous", showcontent = true },
136
+ ]
@@ -0,0 +1,11 @@
1
+ """
2
+ oocone
3
+
4
+ Unofficial library for interacting with an enocoo energy management system
5
+ :author: Simon Leiner <simon@leiner.me>
6
+ :license: ISC
7
+ """
8
+
9
+ from .enocoo import Auth, Enocoo
10
+
11
+ __all__ = ["Auth", "Enocoo"]
@@ -0,0 +1,10 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from pathlib import Path
7
+
8
+
9
+ def func(path: Path) -> str:
10
+ return str(path)
File without changes
@@ -0,0 +1,136 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import Any, Literal
5
+
6
+ import aiohttp
7
+ from bs4 import BeautifulSoup
8
+
9
+ from oocone import errors
10
+ from oocone.types import UNKNOWN, TrafficLightColor, TrafficLightStatus
11
+
12
+ ROUTE_LOGIN = "/signinForm.php?mode=ok"
13
+ BEAUTIFULSOUP_PARSER = "html.parser"
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class Auth:
19
+ def __init__(
20
+ self,
21
+ *,
22
+ websession: aiohttp.ClientSession | None = None,
23
+ base_url: str,
24
+ username: str,
25
+ password: str,
26
+ ):
27
+ self._base_url = base_url.rstrip("/")
28
+ self.__username = username
29
+ self.__password = password
30
+ self._session = websession or aiohttp.ClientSession()
31
+ self._logged_in = False
32
+
33
+ @staticmethod
34
+ def response_indicates_not_logged_in(response: BeautifulSoup) -> bool:
35
+ return response.find("input", {"type": "password"}) is not None
36
+
37
+ async def _login(self) -> None:
38
+ async with self._session.post(
39
+ self._base_url + ROUTE_LOGIN,
40
+ data={"user": self.__username, "passwort": self.__password},
41
+ ) as response:
42
+ response_text = await response.text()
43
+
44
+ soup = BeautifulSoup(response_text, features=BEAUTIFULSOUP_PARSER)
45
+ if self.response_indicates_not_logged_in(soup):
46
+ raise errors.AuthenticationFailed
47
+
48
+ async def _request(
49
+ self, method: str, path: str, retry_with_login: bool = True, **kwargs
50
+ ) -> (aiohttp.ClientResponse, BeautifulSoup):
51
+ """Make a request."""
52
+
53
+ response = await self._session.request(
54
+ method,
55
+ f"{self._base_url}/{path}",
56
+ **kwargs,
57
+ )
58
+ soup = BeautifulSoup(await response.text(), BEAUTIFULSOUP_PARSER)
59
+
60
+ if self.response_indicates_not_logged_in(soup):
61
+ if retry_with_login:
62
+ self._login()
63
+ return await self._request(method=method, retry_with_login=False, **kwargs)
64
+ else:
65
+ raise errors.AuthenticationFailed()
66
+ else:
67
+ return (response, soup)
68
+
69
+
70
+ class Enocoo:
71
+ """Provides access to the data accessible via the enocoo Web interface"""
72
+
73
+ def __init__(self, auth: Auth):
74
+ """Initialize the API and store the auth so we can make requests."""
75
+ self.auth = auth
76
+
77
+ @staticmethod
78
+ def __extract_key_from_response(response_data: dict[str, Any], key: str) -> Any:
79
+ try:
80
+ result = response_data[key]
81
+ except KeyError:
82
+ raise KeyError(
83
+ f'API response does not contain key "{key}".\n'
84
+ f"Response data:\n"
85
+ f"{response_data}"
86
+ ) from None
87
+
88
+ return result
89
+
90
+ async def get_traffic_light_status(self):
91
+ response, _ = await self.auth._request("GET", "php/getTrafficLightStatus.php")
92
+
93
+ try:
94
+ # We parse the response as JSON, even though the Content-Type header might indicate
95
+ # otherwise.
96
+ response_data = await response.json(content_type=None)
97
+ except Exception as e:
98
+ raise errors.UnexpectedResponse() from e
99
+
100
+ def parse_color(response_data: dict) -> TrafficLightColor | Literal[UNKNOWN]:
101
+ try:
102
+ raw = self.__extract_key_from_response(response_data, "color")
103
+ except KeyError as e:
104
+ logger.warning(e)
105
+ return UNKNOWN
106
+
107
+ if raw == "rot":
108
+ return TrafficLightColor.RED
109
+ if raw == "gelb":
110
+ return TrafficLightColor.YELLOW
111
+ if raw == r"grün":
112
+ return TrafficLightColor.GREEN
113
+
114
+ logger.warning(f'Got unexpected color: "{raw}"')
115
+ return UNKNOWN
116
+
117
+ def parse_current_energy_price(response_data: dict) -> float | Literal[UNKNOWN]:
118
+ try:
119
+ raw = self.__extract_key_from_response(response_data, "currentEnergyprice")
120
+ except KeyError as e:
121
+ logger.warning(e)
122
+ return UNKNOWN
123
+
124
+ try:
125
+ result = float(raw)
126
+ except Exception:
127
+ logger.warning("Could not parse energy price %s as a number")
128
+ return UNKNOWN
129
+
130
+ return result
131
+
132
+ result = TrafficLightStatus(
133
+ color=parse_color(response_data),
134
+ current_energy_price=parse_current_energy_price(response_data),
135
+ )
136
+ return result
@@ -0,0 +1,10 @@
1
+ class OoconeError(Exception):
2
+ """Base class for oocone."""
3
+
4
+
5
+ class AuthenticationFailed(OoconeError):
6
+ """Authentication has failed."""
7
+
8
+
9
+ class UnexpectedResponse(OoconeError):
10
+ """Got unexpected data back from the enocoo website"""
File without changes
@@ -0,0 +1,20 @@
1
+ """Types for library users"""
2
+
3
+ import enum
4
+ from dataclasses import dataclass
5
+ from typing import Literal
6
+
7
+
8
+ class TrafficLightColor(enum.StrEnum):
9
+ RED = enum.auto()
10
+ YELLOW = enum.auto()
11
+ GREEN = enum.auto()
12
+
13
+
14
+ UNKNOWN = "UNKNOWN"
15
+
16
+
17
+ @dataclass
18
+ class TrafficLightStatus:
19
+ color: TrafficLightColor | Literal[UNKNOWN]
20
+ current_energy_price: float | Literal[UNKNOWN]
@@ -0,0 +1,6 @@
1
+ """Tests suite for `oocone`."""
2
+
3
+ from pathlib import Path
4
+
5
+ TESTS_DIR = Path(__file__).parent
6
+ RESPONSES_DIR = TESTS_DIR / "data" / "responses"
@@ -0,0 +1,79 @@
1
+ from asyncio import AbstractEventLoop
2
+ from pathlib import Path
3
+
4
+ import pytest
5
+ from aiohttp import web
6
+ from aiohttp.test_utils import TestClient
7
+ from aiohttp.typedefs import Handler
8
+ from pytest_aiohttp.plugin import AiohttpClient
9
+
10
+ from oocone import Auth
11
+
12
+ from . import RESPONSES_DIR
13
+
14
+ pytest_plugins = ("pytest_asyncio",)
15
+
16
+ signed_in = web.AppKey("signed_in", bool)
17
+
18
+
19
+ async def _signin_form(request: web.Request) -> web.Response:
20
+ response_path = RESPONSES_DIR / "signinForm.failure.php"
21
+ response = web.Response()
22
+
23
+ if request.method == "POST":
24
+ data = await request.post()
25
+ username = data.get("user")
26
+ password = data.get("passwort")
27
+ credentials_correct = username == "correct" and password == "correct"
28
+
29
+ if credentials_correct and request.query.get("mode") == "ok":
30
+ response_path = RESPONSES_DIR / "signinForm.success.php"
31
+ response.set_cookie("logged_in", "true")
32
+
33
+ with open(response_path, "rb") as f:
34
+ response.body = f.read()
35
+
36
+ return response
37
+
38
+
39
+ def _response_from_file(response_path: Path, needs_login: bool = True) -> Handler:
40
+ original_response_path = response_path
41
+
42
+ async def handler(request: web.Request) -> web.Response:
43
+ response = web.Response()
44
+
45
+ if needs_login and request.cookies.get("logged_in") != "true":
46
+ response_path = RESPONSES_DIR / "newMeterTable.notLoggedIn.php"
47
+ else:
48
+ response_path = RESPONSES_DIR / original_response_path
49
+
50
+ with open(response_path, "rb") as f:
51
+ response.body = f.read()
52
+
53
+ return response
54
+
55
+ return handler
56
+
57
+
58
+ @pytest.fixture
59
+ def mock_api(event_loop: AbstractEventLoop, aiohttp_client: AiohttpClient) -> TestClient:
60
+ app = web.Application()
61
+ app[signed_in] = False
62
+
63
+ app.router.add_post("/signinForm.php", _signin_form)
64
+ app.router.add_get(
65
+ "/php/getTrafficLightStatus.php",
66
+ _response_from_file("getTrafficLightStatus.php", needs_login=False),
67
+ )
68
+ return event_loop.run_until_complete(aiohttp_client(app))
69
+
70
+
71
+ @pytest.fixture
72
+ def mock_auth(mock_api: TestClient):
73
+ auth = Auth(
74
+ websession=mock_api.session,
75
+ base_url=str(mock_api.server.make_url("/")),
76
+ username="correct",
77
+ password="correct",
78
+ )
79
+ return auth
@@ -0,0 +1,4 @@
1
+
2
+
3
+
4
+ {"color":"gr\u00fcn","highEnergyConsumption":0,"currentEnergyprice":0,"showAnnouncementSign":0}
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html>
2
+ <html lang="de">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="description" content="enocoo | Abgemeldet">
8
+ <meta name="author" content="enocoo">
9
+ <!--link rel="shortcut icon" href="../images/favicon.ico"-->
10
+ <link rel="apple-touch-icon" sizes="180x180" href="../apple-touch-icon.png">
11
+ <link rel="icon" type="image/png" sizes="32x32" href="../favicon-32x32.png">
12
+ <link rel="icon" type="image/png" sizes="16x16" href="../favicon-16x16.png">
13
+ <link rel="manifest" href="../site.webmanifest.json">
14
+ <link rel="mask-icon" href="../safari-pinned-tab.svg" color="#5bbad5">
15
+ <meta name="msapplication-TileColor" content="#2d89ef">
16
+ <meta name="theme-color" content="#ffffff">
17
+
18
+ <title>enocoo | Abgemeldet</title>
19
+ <link href="../css/signInStyle.css" rel="stylesheet">
20
+ </head>
21
+ <body>
22
+ <p /><DIV ALIGN=CENTER class="loggedOut"><a id="logo"><img align="center" src="../images/ENOCOO_LOGO_300x111.png" alt="enocoo Logo" /></a><h3>Sie sind nicht angemeldet.</h3><strong>M&ouml;glicherweise ist Ihre Sitzung abgelaufen.<br />Bitte gehen Sie zur <a href="../signinForm.php">Anmelde-Seite</a><br />und melden Sie sich an.</strong></DIV> </body>
23
+ </html>
@@ -0,0 +1,4 @@
1
+
2
+
3
+
4
+ <div class="row"><div class="col-md-12"><div class="kachelRund"><table class="table table-striped"><tr><th>Fläche</th><th>Bezeichnung</th><th>Zähler-Nr.</th><th>Zeitpunkt</th><th class="alignright">Zählerstand</th><th>Einheit</th></tr><tr><td>H12W34</td><td>Verbrauch Kaltwasser H12W34 Bad</td><td>00000001</td><td>01.01.2021 12:34:56</td><td class="alignright">1.234,56</td><td>m<sup>3</sup></td></tr><tr><td>H12W34</td><td>Verbrauch Kaltwasser H12W34 WC</td><td>00000002</td><td>01.01.2021 12:34:56</td><td class="alignright">1.234,56</td><td>m<sup>3</sup></td></tr><tr><td>H12W34</td><td>Verbrauch Strom H12W34</td><td>00000003</td><td>01.01.2021 12:34:56</td><td class="alignright">1.234,56</td><td>kWh</td></tr><tr><td>H12W34</td><td>Verbrauch Wärme H12W34</td><td>00000004</td><td>01.01.2021 12:34:56</td><td class="alignright">1.234,56</td><td>kWh</td></tr><tr><td>H12W34</td><td>Verbrauch Warmwasser H12W34 Bad</td><td>00000005</td><td>01.01.2021 12:34:56</td><td class="alignright">1.234,56</td><td>m<sup>3</sup></td></tr><tr><td>H12W34</td><td>Verbrauch Warmwasser H12W34 WC</td><td>00000006</td><td>01.01.2021 12:34:56</td><td class="alignright">1.234,56</td><td>m<sup>3</sup></td></tr></table></div></div></div>
@@ -0,0 +1,103 @@
1
+
2
+
3
+
4
+
5
+ <!DOCTYPE html>
6
+ <html lang="de">
7
+ <head>
8
+ <meta charset="utf-8">
9
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
10
+ <meta name="viewport" content="width=device-width, initial-scale=1">
11
+ <meta name="description" content="enocoo | Anmeldung">
12
+ <meta name="author" content="">
13
+ <!--link rel="shortcut icon" href="../images/favicon.ico"-->
14
+ <link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
15
+ <link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
16
+ <link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
17
+ <link rel="manifest" href="site.webmanifest.json">
18
+ <link rel="mask-icon" href="safari-pinned-tab.svg" color="#5bbad5">
19
+ <meta name="msapplication-TileColor" content="#2d89ef">
20
+ <meta name="theme-color" content="#ffffff">
21
+
22
+ <title>enocoo | Anmeldung</title>
23
+
24
+ <!-- enocoo custom styles. Bootstrap core CSS wird in diesem als erstes Nachgeladen -->
25
+ <link href="css/signInStyle.css" rel="stylesheet">
26
+
27
+ <!-- Just for debugging purposes. Don't actually copy this line! -->
28
+ <!--[if lt IE 9]><script src="../../assets/js/ie8-responsive-file-warning.js"></script><![endif]-->
29
+
30
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
31
+ <!--[if lt IE 9]>
32
+ <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
33
+ <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
34
+ <![endif]-->
35
+
36
+ <script src="js/jquery-2.1.0.js" type="text/javascript"></script>
37
+
38
+ </head>
39
+
40
+ <body>
41
+ <div class="container">
42
+
43
+ <form class="form-signin" role="form" method="post" action="/signinForm.php?mode=ok" accept-charset="UTF-8">
44
+ <a id="logo"><img align="center" src="images/ENOCOO_LOGO_300x111.png" alt="enocoo Logo" /></a>
45
+
46
+ <!-- Bei nur einem Projekt nichts anzeigen, sonst Auswahlbox -->
47
+
48
+ <!-- Bei nur einer Sprache nichts anzeigen, sonst Auswahlbox -->
49
+
50
+ <!-- bei nur 1 Projekt/Sprache keine Auswahlbox anzeigen und die einzige ID in jeweiliges Übergabefeld schreiben. value statt placeholder! -->
51
+ <input type="text" class="form-control" id="idProject" name="idProject" style="display:none;" value="1">
52
+ <input type="text" class="form-control" id="idLanguage" name="idLanguage" style="display:none;" value="1">
53
+ <h2 class="form-signin-heading">Ihre Anmeldung:</h2>
54
+ <input type="user" class="form-control" placeholder="Benutzername" name="user" required autofocus >
55
+ <input type="password" class="form-control" placeholder="Passwort" name="passwort" required >
56
+ <!-- <label class="checkbox">
57
+ <input type="checkbox" value="remember-me"> Remember me
58
+ </label> -->
59
+ <button class="btn btn-lg btn-primary btn-block" type="submit" >Anmelden</button>
60
+
61
+ <!-- PW-Änderung im Maintenancemode nicht möglich -->
62
+ </br><a href="php/pwLost.php">Passwort vergessen?</a>
63
+ <div align='center'><p></p></div> <div align='center'><p><strong>Ihre Anmeldedaten waren nicht korrekt. Bitte melden Sie sich erneut an.</strong></p></div>
64
+ </form>
65
+
66
+ </div> <!-- /container -->
67
+
68
+ <script type="text/javascript">
69
+ var chosenProject = "1";
70
+ var chosenLanguage = "1";
71
+
72
+ function dropDownActionProj(menuEntry) {
73
+ chosenProject = menuEntry.id;
74
+ // document.getElementById("resultColorValue").innerHTML = document.getElementById("Color").value;
75
+ $('#idProject').val(menuEntry.id);
76
+ $('#project').html($(menuEntry).text() + ' <span class="caret"></span>');
77
+ }
78
+
79
+ function dropDownActionLang(menuEntry) {
80
+ chosenLanguage = menuEntry.id;
81
+ $('#idLanguage').val(menuEntry.id);
82
+ $('#language').html($(menuEntry).text() + ' <span class="caret"></span>');
83
+ }
84
+
85
+ // assign all dropdown-entries a callback-function. Without event_ref.preventDefault(); the page scrolls to the top after the change! (a often suggested "return false;" instead of it does not work.
86
+ $('.pdmProj').each(function( index ) {
87
+ $( this ).click(function(event_ref){ dropDownActionProj(this); event_ref.preventDefault(); });
88
+ });
89
+ $('.pdmLang').each(function( index ) {
90
+ $( this ).click(function(event_ref){ dropDownActionLang(this); event_ref.preventDefault(); });
91
+ });
92
+ </script>
93
+
94
+
95
+ <!-- Bootstrap core JavaScript
96
+ ================================================== -->
97
+ <!-- Placed at the end of the document so the pages load faster -->
98
+ <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
99
+ <script src="js/jquery-2.1.0.js" type="text/javascript"></script>
100
+ <!-- Include all compiled plugins (below), or include individual files as needed -->
101
+ <script src="components/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
102
+ </body>
103
+ </html>
@@ -0,0 +1,80 @@
1
+
2
+
3
+
4
+
5
+ <!DOCTYPE html>
6
+ <html lang="de">
7
+ <head>
8
+ <meta http-equiv="refresh" content="0; URL=php/mainPage.php" /> <meta charset="utf-8">
9
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
10
+ <meta name="viewport" content="width=device-width, initial-scale=1">
11
+ <meta name="description" content="enocoo | Anmeldung">
12
+ <meta name="author" content="">
13
+ <!--link rel="shortcut icon" href="../images/favicon.ico"-->
14
+ <link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
15
+ <link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
16
+ <link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
17
+ <link rel="manifest" href="site.webmanifest.json">
18
+ <link rel="mask-icon" href="safari-pinned-tab.svg" color="#5bbad5">
19
+ <meta name="msapplication-TileColor" content="#2d89ef">
20
+ <meta name="theme-color" content="#ffffff">
21
+
22
+ <title>enocoo | Anmeldung</title>
23
+
24
+ <!-- enocoo custom styles. Bootstrap core CSS wird in diesem als erstes Nachgeladen -->
25
+ <link href="css/signInStyle.css" rel="stylesheet">
26
+
27
+ <!-- Just for debugging purposes. Don't actually copy this line! -->
28
+ <!--[if lt IE 9]><script src="../../assets/js/ie8-responsive-file-warning.js"></script><![endif]-->
29
+
30
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
31
+ <!--[if lt IE 9]>
32
+ <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
33
+ <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
34
+ <![endif]-->
35
+
36
+ <script src="js/jquery-2.1.0.js" type="text/javascript"></script>
37
+
38
+ </head>
39
+
40
+ <body>
41
+ <div align="center">
42
+ <strong>Die Startseite wird geladen...</strong>
43
+ </div>
44
+
45
+ <script type="text/javascript">
46
+ var chosenProject = "1";
47
+ var chosenLanguage = "1";
48
+
49
+ function dropDownActionProj(menuEntry) {
50
+ chosenProject = menuEntry.id;
51
+ // document.getElementById("resultColorValue").innerHTML = document.getElementById("Color").value;
52
+ $('#idProject').val(menuEntry.id);
53
+ $('#project').html($(menuEntry).text() + ' <span class="caret"></span>');
54
+ }
55
+
56
+ function dropDownActionLang(menuEntry) {
57
+ chosenLanguage = menuEntry.id;
58
+ $('#idLanguage').val(menuEntry.id);
59
+ $('#language').html($(menuEntry).text() + ' <span class="caret"></span>');
60
+ }
61
+
62
+ // assign all dropdown-entries a callback-function. Without event_ref.preventDefault(); the page scrolls to the top after the change! (a often suggested "return false;" instead of it does not work.
63
+ $('.pdmProj').each(function( index ) {
64
+ $( this ).click(function(event_ref){ dropDownActionProj(this); event_ref.preventDefault(); });
65
+ });
66
+ $('.pdmLang').each(function( index ) {
67
+ $( this ).click(function(event_ref){ dropDownActionLang(this); event_ref.preventDefault(); });
68
+ });
69
+ </script>
70
+
71
+
72
+ <!-- Bootstrap core JavaScript
73
+ ================================================== -->
74
+ <!-- Placed at the end of the document so the pages load faster -->
75
+ <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
76
+ <script src="js/jquery-2.1.0.js" type="text/javascript"></script>
77
+ <!-- Include all compiled plugins (below), or include individual files as needed -->
78
+ <script src="components/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
79
+ </body>
80
+ </html>
@@ -0,0 +1,29 @@
1
+ import pytest
2
+ from aiohttp.test_utils import TestClient
3
+
4
+ from oocone import Auth, errors
5
+
6
+
7
+ @pytest.mark.asyncio
8
+ @pytest.mark.mocked_api
9
+ async def test_login_successful(mock_api: TestClient) -> None:
10
+ auth = Auth(
11
+ websession=mock_api.session,
12
+ base_url=str(mock_api.server.make_url("/")),
13
+ username="correct",
14
+ password="correct",
15
+ )
16
+ await auth._login()
17
+
18
+
19
+ @pytest.mark.asyncio
20
+ @pytest.mark.mocked_api
21
+ async def test_login_raise_on_failure(mock_api: TestClient) -> None:
22
+ auth = Auth(
23
+ websession=mock_api.session,
24
+ base_url=str(mock_api.server.make_url("/")),
25
+ username="correct",
26
+ password="incorrect",
27
+ )
28
+ with pytest.raises(errors.AuthenticationFailed):
29
+ await auth._login()
@@ -0,0 +1,13 @@
1
+ import pytest
2
+
3
+ from oocone import Auth, Enocoo
4
+ from oocone.types import TrafficLightColor
5
+
6
+
7
+ @pytest.mark.asyncio
8
+ @pytest.mark.mocked_api
9
+ async def test_get_traffic_light_status(mock_auth: Auth):
10
+ enocoo = Enocoo(mock_auth)
11
+ result = await enocoo.get_traffic_light_status()
12
+ assert isinstance(result.color, TrafficLightColor)
13
+ assert isinstance(result.current_energy_price, float)