diff --git a/hmdooris/AppConfig.py b/hmdooris/AppConfig.py index e525c08..3fc5246 100644 --- a/hmdooris/AppConfig.py +++ b/hmdooris/AppConfig.py @@ -1,4 +1,5 @@ import json +from json import JSONDecodeError from os import getenv, path @@ -16,6 +17,17 @@ class AppConfig: self.client_id = getenv('HMDOORIS_CLIENT_ID', 'hmdooris') self.client_secret = getenv('HMDOORIS_CLIENT_SECRET') self.requires_group = getenv('HMDOORIS_REQUIRES_GROUP', None) + self.ccujack_url = getenv('HMDOORIS_CCUJACK_URL', None) + self.ccujack_username = getenv('HMDOORIS_CCUJACK_USERNAME', None) + self.ccujack_password = getenv('HMDOORIS_CCUJACK_PASSWORD', None) + try: + self.ccujack_locks = json.loads(getenv('HMDOORIS_CCUJACK_LOCKS', '[]')) + except json.decoder.JSONDecodeError as e: + raise ValueError(f"Unable to decode HMDOORIS_CCUJACK_LOCKS=\"{getenv('HMDOORIS_CCUJACK_LOCKS', '{}')}\"", e) if self.client_secret is None or self.client_secret == '': raise ValueError('You need to provide HMDOORIS_CLIENT_SECRET') + if self.ccujack_url is None or self.ccujack_url == '': + raise ValueError('You need to provide HMDOORIS_CCUJACK_URL') + if len(self.ccujack_locks) == 0: + raise ValueError('You need to provide HMDOORIS_CCUJACK_LOCKS as a JSON object of locks') diff --git a/hmdooris/WebEndpoints.py b/hmdooris/WebEndpoints.py index 2702c06..c4ef671 100644 --- a/hmdooris/WebEndpoints.py +++ b/hmdooris/WebEndpoints.py @@ -9,6 +9,7 @@ from bottle import static_file, request, redirect, Bottle from hmdooris import CSRF from hmdooris.AppConfig import AppConfig from hmdooris.Template import template_or_error +from hmdooris.ccujack import CCUJack class WebEndpoints: @@ -16,10 +17,11 @@ class WebEndpoints: Defines endpoints for interaction with the user's browser. """ - def __init__(self, app: Bottle, auth: BottleOIDC, basepath: str, config: AppConfig): + def __init__(self, app: Bottle, auth: BottleOIDC, basepath: str, ccujack: CCUJack, config: AppConfig): self.app = app self.auth = auth self.basepath = basepath + self.ccujack = ccujack self.config = config self.valid_username_re = '^[a-zA-Z0-9_-]+$' @@ -27,11 +29,8 @@ class WebEndpoints: app.route(path='/static/', callback=self.static) app.get(path='/', callback=template_or_error('home')(self.home)) app.get(path='/foo', callback=self.require_login(template_or_error('home')(self.home))) - app.get(path='/api', callback=self.require_login(self.api_get)) - # app.get(path='/invite', callback=self.get_invite) - # app.post(path='/invite', callback=self.require_login(template_or_error('invite_result')(self.post_invite))) - # app.get(path='/claim', callback=template_or_error('claim_form')(self.get_claim)) - # app.post(path='/claim', callback=template_or_error('claim_result')(self.post_claim)) + app.get(path='/api', callback=self.api_get) + app.post(path='/api', callback=self.require_login(self.api_post), ) def require_login(self, func: Callable) -> Callable: if self.config.requires_group is not None: @@ -55,17 +54,20 @@ class WebEndpoints: def api_get(self): """ - Interact withe HomeMatic CCU through ccu-jack + Interact with the HomeMatic CCU through ccu-jack :return: """ + return { '_csrf': CSRF.get_token(), - 'foo': 'bar' + 'locks': self.ccujack.get_locks() } - def api_put(self): + def api_post(self): """ Interact withe HomeMatic CCU through ccu-jack :return: """ + #self.ccujack.update_lock(name) + print(f"name {request.json}") return {} \ No newline at end of file diff --git a/hmdooris/__main__.py b/hmdooris/__main__.py index 70e60da..e0511cc 100644 --- a/hmdooris/__main__.py +++ b/hmdooris/__main__.py @@ -9,6 +9,7 @@ from bottle import Bottle, TEMPLATE_PATH from hmdooris.AppConfig import AppConfig from hmdooris.WebEndpoints import WebEndpoints +from hmdooris.ccujack import CCUJack if __name__ == '__main__': argp = ArgumentParser(prog="hmdooris") @@ -29,7 +30,8 @@ if __name__ == '__main__': "client_id": config.client_id, "client_secret": config.client_secret, }) - WebEndpoints(app, auth, config.basepath, config) + ccujack = CCUJack(config.ccujack_url, config.ccujack_username, config.ccujack_password, config.ccujack_locks) + WebEndpoints(app, auth, config.basepath, ccujack, config) bottle_params = { 'debug': bool(getenv('BOTTLE_DEBUG', 'False')) or args.debug, diff --git a/hmdooris/ccujack.py b/hmdooris/ccujack.py new file mode 100644 index 0000000..044d297 --- /dev/null +++ b/hmdooris/ccujack.py @@ -0,0 +1,63 @@ +from typing import List, Dict + +import requests +from requests.auth import HTTPBasicAuth + +from hmdooris.AppException import AppException + + +class CCUJackHmIPDLD: + def __init__(self, ccu: 'CCUJack', name: str, id:str): + self.ccu = ccu + self.name = name + self.id = id + self.value_list = self.get_value_list(id) + + def status(self): + r = self.ccu.get_json(f"/device/{self.id}/1/LOCK_STATE/~pv") + return self.value_list[r["v"]] + + def get_value_list(self, id) -> List[str]: + """ + Uses the fist lock to obtain the valueList property that maps the enum values to strings + :param id: + :return: + """ + r = self.ccu.get_json(f"/device/{id}/1/LOCK_STATE") + return r["valueList"] + + +class CCUJack: + def __init__(self, url: str, username: str, password: str, locks:Dict): + self.url = url + self.username = username + self.password = password + self.auth = HTTPBasicAuth(username, password) + self.locks = {} + self.get_all_locks() + + def get_json(self, url:str): + url = self.url + url + r = requests.get(url, auth=self.auth) + if r.status_code != 200: + raise AppException(f"Unable to talk to CCU at {url}: {r.status_code}") + return r.json() + + def get_all_locks(self): + r = self.get_json(f"/device") + for link in r["~links"]: + if link["rel"] == "device": + device = self.get_json(f"/device/{link['href']}") + if device["type"] == "HmIP-DLD": + self.locks[device["title"]] = CCUJackHmIPDLD(self, device["title"], device["identifier"]) + + + def get_locks(self): + s = [] + for name, lock in self.locks.items(): + s.append({ + "name": name, + "id": lock.id, + "status": lock.status() + }) + return s diff --git a/hmdooris/static/main.js b/hmdooris/static/main.js index 7204f2a..96372a1 100644 --- a/hmdooris/static/main.js +++ b/hmdooris/static/main.js @@ -1,3 +1,26 @@ -(function(){ +(function () { + setInterval(function () { + fetch("/api").then(function (response) { + return response.json(); + }).then(function (json) { + let e = document.getElementById("lock-status"); + e.innerText = JSON.stringify(json.locks); + }) + }, 10000) + + for (let e of document.querySelectorAll("button.lock-unlock")) { + e.onclick = function () { + fetch("/api", { + method: "POST", + body: JSON.stringify({ + name: e.name, + contentType: "application/json", + }) + }).then(function (response) { + // + }) + } + } + console.log("Setup complete") })(); \ No newline at end of file diff --git a/hmdooris/views/home.tpl b/hmdooris/views/home.tpl index 6ca6d8e..f46b15d 100644 --- a/hmdooris/views/home.tpl +++ b/hmdooris/views/home.tpl @@ -7,5 +7,7 @@

HM Dooris

Hello, world!

+

Lock Status: unknown

+ \ No newline at end of file