diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a40bea --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Dataplicity diff --git a/custom_components/dataplicity/__init__.py b/custom_components/dataplicity/__init__.py new file mode 100644 index 0000000..435c754 --- /dev/null +++ b/custom_components/dataplicity/__init__.py @@ -0,0 +1,51 @@ +from threading import Thread + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.requirements import async_process_requirements +from homeassistant.util import package + +DOMAIN = 'dataplicity' + + +async def async_setup(hass: HomeAssistant, hass_config: dict): + # fix problems with `enum34==1000000000.0.0` constraint in Hass + real_install = package.install_package + + def fake_install(*args, **kwargs): + kwargs.pop('constraints') + return real_install(*args, **kwargs) + + try: + package.install_package = fake_install + await async_process_requirements(hass, DOMAIN, [DOMAIN]) + return True + except: + return False + finally: + package.install_package = real_install + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + # fix: module 'platform' has no attribute 'linux_distribution' + from dataplicity import device_meta + device_meta.get_os_version = lambda: "Linux" + + from dataplicity.client import Client + + hass.data[DOMAIN] = client = Client(serial=entry.data['serial'], + auth_token=entry.data['auth']) + # replace default 80 port to 8123 + client.port_forward.add_service('web', 8123) + Thread(name=DOMAIN, target=client.run_forever).start() + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + from dataplicity.client import Client + + client: Client = hass.data[DOMAIN] + client.exit() + + return True diff --git a/custom_components/dataplicity/config_flow.py b/custom_components/dataplicity/config_flow.py new file mode 100644 index 0000000..de248f2 --- /dev/null +++ b/custom_components/dataplicity/config_flow.py @@ -0,0 +1,43 @@ +import logging +import re +import sys + +import voluptuous as vol +from homeassistant.config_entries import ConfigFlow +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from . import DOMAIN, utils + +_LOGGER = logging.getLogger(__name__) + +RE_TOKEN = re.compile(r'https://www\.dataplicity\.com/([^.]+)\.py') + + +class ConfigFlowHandler(ConfigFlow, domain=DOMAIN): + async def async_step_user(self, data=None, error=None): + if sys.platform == 'win32': + return self.async_abort('win32') + + if data is None: + return self.async_show_form( + step_id='user', + data_schema=vol.Schema({ + vol.Required('token'): str, + }), + errors={'base': error} if error else None + ) + + m = RE_TOKEN.search(data['token']) + token = m[1] if m else data['token'] + + session = async_get_clientsession(self.hass) + resp = await utils.register_device(session, token) + if resp: + return self.async_create_entry(title="Dataplicity", data={ + 'auth': resp['auth'], + 'serial': resp['serial'] + }, description_placeholders={ + 'device_url': resp['device_url'] + }) + + return await self.async_step_user(error='auth') diff --git a/custom_components/dataplicity/manifest.json b/custom_components/dataplicity/manifest.json new file mode 100644 index 0000000..754f37f --- /dev/null +++ b/custom_components/dataplicity/manifest.json @@ -0,0 +1,15 @@ +{ + "domain": "dataplicity", + "name": "Dataplicity", + "version": "1.0.0", + "config_flow": true, + "documentation": "https://github.com/AlexxIT/Dataplicity", + "issue_tracker": "https://github.com/AlexxIT/Dataplicity/issues", + "dependencies": [ + ], + "codeowners": [ + "@AlexxIT" + ], + "requirements": [ + ] +} \ No newline at end of file diff --git a/custom_components/dataplicity/translations/en.json b/custom_components/dataplicity/translations/en.json new file mode 100644 index 0000000..837a2a5 --- /dev/null +++ b/custom_components/dataplicity/translations/en.json @@ -0,0 +1,22 @@ +{ + "config": { + "create_entry": { + "default": "Home Assistant added to the Dataplicity. Enable **Wormhole** in [Device settings]({device_url}) for public HTTPS access." + }, + "abort": { + "win32": "Windows is not supported" + }, + "error": { + "auth": "Error during device registration" + }, + "step": { + "user": { + "title": "Register Dataplicity Device", + "description": "Sign up to [Dataplicity](https://www.dataplicity.com/) and paste full install line or url or token:\n`https://www.dataplicity.com/XXXXXXXX.py`", + "data": { + "token": "URL or Token" + } + } + } + } +} \ No newline at end of file diff --git a/custom_components/dataplicity/utils.py b/custom_components/dataplicity/utils.py new file mode 100644 index 0000000..b32f57d --- /dev/null +++ b/custom_components/dataplicity/utils.py @@ -0,0 +1,19 @@ +import logging + +from aiohttp import ClientSession + +_LOGGER = logging.getLogger(__name__) + + +async def register_device(session: ClientSession, token: str): + try: + r = await session.post('https://www.dataplicity.com/install/', data={ + 'name': "Home Assistant", 'serial': 'None', 'token': token + }) + if r.status != 200: + _LOGGER.error(f"Can't register dataplicity device: {r.status}") + return None + return await r.json() + except: + _LOGGER.exception("Can't register dataplicity device") + return None