-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 6147a35
Showing
13 changed files
with
616 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
custom: ["buymeacoffee.com/PiotrMachowski", "paypal.me/PiMachowski"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2020 Piotr Machowski | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
[](https://hacs.xyz/docs/faq/custom_repositories) | ||
[](https://www.buymeacoffee.com/PiotrMachowski) | ||
[](https://paypal.me/PiMachowski) | ||
 | ||
|
||
# Dom 5 Sensor | ||
|
||
This custom integration retrieves data from [Dom 5](https://www.sacer.pl/dom5) - housing cooperative management system. | ||
|
||
## Installation | ||
|
||
### Using [HACS](https://hacs.xyz/) (recommended) | ||
|
||
This integration can be added to HACS as a [custom repository](https://hacs.xyz/docs/faq/custom_repositories): | ||
* URL: `https://github.com/PiotrMachowski/Home-Assistant-custom-components-Dom-5` | ||
* Category: `Integration` | ||
|
||
After adding a custom repository you can use HACS to install this integration using user interface. | ||
|
||
### Manual | ||
|
||
To install this integration manually you have to download [*dom_5.zip*](https://github.com/PiotrMachowski/Home-Assistant-custom-components-Dom-5/releases/latest/download/dom_5.zip) extract its contents to `config/custom_components/dom_5` directory: | ||
```bash | ||
mkdir -p custom_components/dom_5 | ||
cd custom_components/dom_5 | ||
wget https://github.com/PiotrMachowski/Home-Assistant-custom-components-Dom-5/releases/latest/download/dom_5.zip | ||
unzip dom_5.zip | ||
rm dom_5.zip | ||
``` | ||
|
||
## Configuration | ||
|
||
### Config flow (recommended) | ||
|
||
To configure this integration go to: _Configuration_ -> _Integrations_ -> _Add integration_ -> _Dom 5_. | ||
|
||
You can also use following [My Home Assistant](http://my.home-assistant.io/) link | ||
|
||
[](https://my.home-assistant.io/redirect/config_flow_start/?domain=dom_5) | ||
|
||
### Manual - yaml | ||
| Key | Type | Required | Value | Description | | ||
|---|---|---|---|---| | ||
| `platform` | string | true | `dom_5` | Name of a platform | | ||
| `name` | string | false | | Desired name of a entity | | ||
| `url` | string | true | | URL of system (in format: `https://dom5.pl` | | ||
| `username` | string | true | | Username in Dom 5 system | | ||
| `password` | string | true | | Password in Dom 5 system | | ||
|
||
#### Example configuration | ||
```yaml | ||
sensor: | ||
- platform: dom_5 | ||
url: "https://dom5.pl" | ||
username: "123456" | ||
password: "SecretPassword" | ||
``` | ||
<a href="https://www.buymeacoffee.com/PiotrMachowski" target="_blank"><img src="https://bmc-cdn.nyc3.digitaloceanspaces.com/BMC-button-images/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import asyncio | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.core import HomeAssistant | ||
|
||
from .connector import Dom5Connector | ||
from .const import * | ||
|
||
|
||
async def async_setup(hass: HomeAssistant, config: dict): | ||
hass.data.setdefault(DOMAIN, {}) | ||
|
||
return True | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
url = entry.data.get(CONF_URL) | ||
username = entry.data.get(CONF_USERNAME) | ||
password = entry.data.get(CONF_PASSWORD) | ||
hass.data[DOMAIN][entry.entry_id] = Dom5Connector(url, username, password) | ||
|
||
for component in PLATFORMS: | ||
hass.async_create_task( | ||
hass.config_entries.async_forward_entry_setup(entry, component) | ||
) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): | ||
unload_ok = all( | ||
await asyncio.gather( | ||
*[ | ||
hass.config_entries.async_forward_entry_unload(entry, component) | ||
for component in PLATFORMS | ||
] | ||
) | ||
) | ||
if unload_ok: | ||
hass.data[DOMAIN].pop(entry.entry_id) | ||
|
||
return unload_ok |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import logging | ||
|
||
import voluptuous as vol | ||
|
||
from homeassistant import config_entries, core, exceptions | ||
from homeassistant.core import HomeAssistant | ||
|
||
from .const import * | ||
from .connector import test_connection | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
DATA_SCHEMA = vol.Schema({ | ||
vol.Required(CONF_URL): str, | ||
vol.Required(CONF_USERNAME): str, | ||
vol.Required(CONF_PASSWORD): str | ||
}) | ||
|
||
|
||
async def validate_input(hass: HomeAssistant, data: dict): | ||
if len(data.get(CONF_URL)) < 3: | ||
raise InvalidUrl | ||
|
||
url = data.get(CONF_URL) | ||
username = data.get(CONF_USERNAME) | ||
password = data.get(CONF_PASSWORD) | ||
|
||
try: | ||
result = await hass.async_add_executor_job(test_connection, url, username, password) | ||
if not result: | ||
raise InvalidCredentials | ||
except: | ||
raise InvalidUrl | ||
|
||
|
||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): | ||
VERSION = 1 | ||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL | ||
|
||
async def async_step_user(self, user_input=None): | ||
errors = {} | ||
if user_input is not None: | ||
try: | ||
await validate_input(self.hass, user_input) | ||
title = f"{user_input.get(CONF_USERNAME)} ({user_input.get(CONF_URL)})" | ||
return self.async_create_entry(title=title, data=user_input) | ||
except InvalidCredentials: | ||
errors[CONF_PASSWORD] = "invalid_auth" | ||
except InvalidUrl: | ||
errors[CONF_URL] = "cannot_connect" | ||
|
||
return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA, errors=errors) | ||
|
||
|
||
class InvalidCredentials(exceptions.HomeAssistantError): | ||
pass | ||
|
||
|
||
class InvalidUrl(exceptions.HomeAssistantError): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
from datetime import timedelta | ||
|
||
from homeassistant.util import Throttle | ||
import logging | ||
from typing import Any, Callable, List, Optional | ||
|
||
import requests | ||
import html2text | ||
|
||
from requests import Response, Session | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
THROTTLE_INTERVAL = timedelta(minutes=30) | ||
|
||
|
||
def test_connection(url: str, username: str, password: str) -> bool: | ||
sensor = Dom5Connector(url, username, password) | ||
return sensor.test_connection() | ||
|
||
|
||
class Dom5Data: | ||
messages_number: Optional[int] | ||
last_messages_titles: Optional[List[str]] | ||
last_message_id: Optional[str] | ||
last_message_title: Optional[str] | ||
last_message_body: Optional[str] | ||
last_message_date: Optional[str] | ||
announcements_number: Optional[int] | ||
last_announcements_titles: Optional[List[str]] | ||
last_announcement_id: Optional[str] | ||
last_announcement_title: Optional[str] | ||
last_announcement_body: Optional[str] | ||
last_announcement_date: Optional[str] | ||
arrear: Optional[float] | ||
overpayment: Optional[float] | ||
balance: Optional[float] | ||
|
||
def __init__(self): | ||
self.messages_number = None | ||
self.last_messages_titles = None | ||
self.last_message_id = None | ||
self.last_message_title = None | ||
self.last_message_body = None | ||
self.last_message_date = None | ||
self.announcements_number = None | ||
self.last_announcements_titles = None | ||
self.last_announcement_id = None | ||
self.last_announcement_title = None | ||
self.last_announcement_body = None | ||
self.last_announcement_date = None | ||
self.arrear = None | ||
self.overpayment = None | ||
self.balance = None | ||
|
||
def set_messages(self, messages_response: Response): | ||
if not Dom5Data.is_valid(messages_response): | ||
return | ||
self.messages_number, self.last_message_id, self.last_messages_titles = \ | ||
Dom5Data.parse_communications(messages_response.json()) | ||
|
||
def set_last_message(self, last_message_response: Response): | ||
if not Dom5Data.is_valid(last_message_response): | ||
return | ||
self.last_message_title, self.last_message_body, self.last_message_date = \ | ||
Dom5Data.parse_specific_communication(last_message_response.json()) | ||
|
||
def set_announcements(self, announcements_response: Response): | ||
if not Dom5Data.is_valid(announcements_response): | ||
return | ||
self.announcements_number, self.last_announcement_id, self.last_announcements_titles = \ | ||
Dom5Data.parse_communications(announcements_response.json()) | ||
|
||
def set_last_announcement(self, last_announcement_response: Response): | ||
if not Dom5Data.is_valid(last_announcement_response): | ||
return | ||
self.last_announcement_title, self.last_announcement_body, self.last_announcement_date = \ | ||
Dom5Data.parse_specific_communication(last_announcement_response.json()) | ||
|
||
def set_finances(self, finances_response: Response): | ||
if not Dom5Data.is_valid(finances_response): | ||
return | ||
json = finances_response.json()["data"] | ||
self.arrear = json["Zaleglosci"] | ||
self.overpayment = json["Nadplaty"] | ||
self.balance = self.overpayment - self.arrear | ||
|
||
@staticmethod | ||
def is_valid(response: Response): | ||
return response.status_code == 200 and "status" in response.json() and response.json()["status"] == "success" | ||
|
||
@staticmethod | ||
def parse_communications(json: Any): | ||
communications_number = len(json["data"]) | ||
last_communication_id = None if communications_number == 0 else json["data"][0]["Ident"] | ||
last_titles = list(map(lambda r: r["Tytul"], json["data"]))[0:10] | ||
return communications_number, last_communication_id, last_titles | ||
|
||
@staticmethod | ||
def parse_specific_communication(json: Any): | ||
title = json["data"]["Tytul"] | ||
message = html2text.html2text(json["data"]["Tresc"]) | ||
date = json["data"]["Data"] | ||
return title, message, date | ||
|
||
|
||
class Dom5Connector: | ||
data: Optional[Dom5Data] | ||
|
||
def __init__(self, url: str, username: str, password: str): | ||
self._base_url = url | ||
self._username = username | ||
self._password = password | ||
self.data = Dom5Data() | ||
self.update = Throttle(THROTTLE_INTERVAL)(self._update) | ||
|
||
@property | ||
def url(self) -> str: | ||
return self._base_url | ||
|
||
@property | ||
def username(self) -> str: | ||
return self._username | ||
|
||
def _update(self): | ||
session = self._login() | ||
if session is None: | ||
_LOGGER.error('Failed to login') | ||
return | ||
try: | ||
data = Dom5Data() | ||
messages_response = session.get(self._url('/iokEwid/DajKorespPoz')) | ||
data.set_messages(messages_response) | ||
if data.last_message_id is not None: | ||
last_message_response = session.get( | ||
self._url(f'/iokEwid/DajSzczegKorespPoz?Ident={data.last_message_id}')) | ||
data.set_last_message(last_message_response) | ||
announcements_response = session.get(self._url('/iok/DajOgloszenia')) | ||
data.set_announcements(announcements_response) | ||
if data.last_announcement_id is not None: | ||
last_announcement_response = session.get( | ||
self._url(f'/iok/DajOgloszenie?Ident={data.last_announcement_id}')) | ||
data.set_last_announcement(last_announcement_response) | ||
finances_response = session.get(self._url('/iokRozr/DajListeRozrachFin')) | ||
data.set_finances(finances_response) | ||
self.data = data | ||
finally: | ||
self._logout(session) | ||
|
||
def _login(self) -> Optional[Session]: | ||
session = requests.session() | ||
login = session.post(url=self._url('/iok/Zaloguj'), | ||
data={"Ident": self._username, "Haslo": self._password}, | ||
headers={"Referer": self._url("/content/InetObsKontr/#")}) | ||
if Dom5Data.is_valid(login): | ||
return session | ||
return None | ||
|
||
def _logout(self, session: Session): | ||
session.post(self._url('/iok/Wyloguj')) | ||
|
||
def _url(self, path: str): | ||
return f'{self._base_url}{path}' | ||
|
||
def test_connection(self) -> bool: | ||
session = self._login() | ||
if session is not None: | ||
self._logout(session) | ||
return True | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from homeassistant.const import CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_URL | ||
|
||
DOMAIN = "dom_5" | ||
DEFAULT_NAME = 'Dom 5' | ||
|
||
PLATFORMS = ["sensor"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"domain": "dom_5", | ||
"name": "Dom 5", | ||
"documentation": "https://github.com/PiotrMachowski/Home-Assistant-custom-components-Dom-5", | ||
"issue_tracker": "https://github.com/PiotrMachowski/Home-Assistant-custom-components-Dom-5/issues", | ||
"dependencies": [], | ||
"codeowners": ["@PiotrMachowski"], | ||
"requirements": ["requests", "html2text"], | ||
"version": "v1.0.0", | ||
"config_flow": true | ||
} |
Oops, something went wrong.