This commit is contained in:
Stefan Bethke 2025-05-21 17:58:34 +02:00
commit d60f8f8be0
6 changed files with 115 additions and 11 deletions

View file

@ -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')

View file

@ -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/<filename>', 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 {}

View file

@ -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,

63
hmdooris/ccujack.py Normal file
View file

@ -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

View file

@ -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")
})();

View file

@ -7,5 +7,7 @@
<body>
<h1>HM Dooris</h1>
<p>Hello, world!</p>
<p>Lock Status: <span id="lock-status">unknown</span></p>
<button class="lock-unlock" name="Werkstatt">Lock/Unlock Werkstatt</button>
</body>
</html>