Add connection and battery status
All checks were successful
docker-image / docker (push) Successful in 1m17s

This commit is contained in:
Stefan Bethke 2025-05-30 14:19:17 +02:00
commit c05e5786de
6 changed files with 101 additions and 165 deletions

View file

@ -1,6 +1,5 @@
import json import json
import logging import logging
import ssl
from typing import List, Dict from typing import List, Dict
import requests import requests
@ -17,11 +16,22 @@ class CCUJackHmIPDLD:
self.name = name self.name = name
self.id = id self.id = id
self.value_list = self.get_value_list(id) self.value_list = self.get_value_list(id)
self.ops = {
'low_battery': True,
'unreachable': True,
'voltage': -99,
}
def status(self): def status(self):
r = self.ccu.get_json(f"/device/{self.id}/1/LOCK_STATE/~pv") r = self.ccu.get_json(f"/device/{self.id}/1/LOCK_STATE/~pv")
return self.value_list[r["v"]] return self.value_list[r["v"]]
def get_ops(self):
self.ops['low_battery'] = self.ccu.get_value(f"/device/{self.id}/0/LOW_BAT/~pv")
self.ops['unreachable'] = self.ccu.get_value(f"/device/{self.id}/0/UNREACH/~pv")
self.ops['voltage'] = self.ccu.get_value(f"/device/{self.id}/0/OPERATING_VOLTAGE/~pv")
return self.ops
def get_value_list(self, id) -> List[str]: def get_value_list(self, id) -> List[str]:
""" """
Uses the fist lock to obtain the valueList property that maps the enum values to strings Uses the fist lock to obtain the valueList property that maps the enum values to strings
@ -47,7 +57,7 @@ class CCUJack:
self.kvargs['verify'] = certpath self.kvargs['verify'] = certpath
if certpath is True: if certpath is True:
urllib3.disable_warnings() urllib3.disable_warnings()
self.locks : Dict[str, CCUJackHmIPDLD] = {} self.locks: Dict[str, CCUJackHmIPDLD] = {}
self.get_all_locks() self.get_all_locks()
def get_json(self, url: str): def get_json(self, url: str):
@ -61,6 +71,9 @@ class CCUJack:
pass pass
return {} return {}
def get_value(self, url: str):
return self.get_json(url)["v"]
def put_json(self, url: str, data: dict): def put_json(self, url: str, data: dict):
url = self.url + url url = self.url + url
r = requests.put(url, json=data, **self.kvargs) r = requests.put(url, json=data, **self.kvargs)
@ -86,11 +99,12 @@ class CCUJack:
s[lock.id] = { s[lock.id] = {
"name": lock.name, "name": lock.name,
"id": lock.id, "id": lock.id,
"status": lock.status() "status": lock.status(),
'ops': lock.get_ops(),
} }
return s return s
def lock_unlock(self, lock_id, locked_state = None): def lock_unlock(self, lock_id, locked_state=None):
lock = self.locks.get(lock_id) lock = self.locks.get(lock_id)
s = lock.status() s = lock.status()
if locked_state is None: if locked_state is None:

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Before After
Before After

View file

@ -6,6 +6,12 @@ body {
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
} }
@keyframes blink-animation {
to {
visibility: hidden;
}
}
#locks { #locks {
width: 40em; width: 40em;
} }
@ -30,11 +36,16 @@ body {
margin: 1em 0 0 0; margin: 1em 0 0 0;
} }
.lock__line .lock__state-bat {
color: red;
animation: blink-animation 1s steps(5, start) infinite;
}
.lock__line button { .lock__line button {
min-width: 10em; min-width: 10em;
margin: 0.1em 0.5em; margin: 0.1em 0.5em;
padding: 0.1em 0.5em; padding: 0.1em 0.5em;
border-radius: 1em; border-radius: 2em;
background-color: white; background-color: white;
color: black; color: black;
} }
@ -64,4 +75,18 @@ svg.icon {
font-size: 0.7em; font-size: 0.7em;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
}
.legend {
width: 40em;
}
.legend__icons {
list-style-type: none;
padding: calc(32px + 0.3em);
text-indent: calc(-32px - 0.3em);
}
.legend__icons .icon {
color: black;
} }

View file

@ -1,16 +1,17 @@
(function () { (function () {
let update_button = function (lock) { let update_button = function (lock) {
let state_icon = document.getElementById(`lock__state-icon__${lock.id}`); let state_icon = document.querySelector(`#lock__line__${lock.id} .lock__state-icon`);
let state_label = document.getElementById(`lock__state-label__${lock.id}`); let state_label = document.querySelector(`#lock__line__${lock.id} .lock__state-label`);
let button_lock = document.getElementById(`lock__lock__${lock.id}`); let state_bat = document.querySelector(`#lock__line__${lock.id} .lock__state-bat`);
let button_unlock = document.getElementById(`lock__unlock__${lock.id}`); let button_lock = document.querySelector(`#lock__line__${lock.id} .lock__button__lock`);
let button_unlock = document.querySelector(`#lock__line__${lock.id} .lock__button__unlock`);
if (lock.status === "LOCKED") { if (lock.status === "LOCKED") {
state_icon.innerHTML = '<svg class="icon"><use href="static/icon-lock-alt-svgrepo-com"/></svg>'; state_icon.innerHTML = '<svg class="icon"><use href="static/icons.svg#icon-lock-alt-svgrepo-com"/></svg>';
state_label.innerText = 'locked'; state_label.innerText = 'locked';
button_lock.disabled = true; button_lock.disabled = true;
button_unlock.disabled = false; button_unlock.disabled = false;
} else if (lock.status === "UNLOCKED") { } else if (lock.status === "UNLOCKED") {
state_icon.innerHTML = '<svg class="icon"><use href="static/icon-unlock-alt-svgrepo-com"/></svg>'; state_icon.innerHTML = '<svg class="icon"><use href="static/icons.svg#icon-unlock-alt-svgrepo-com"/></svg>';
state_label.innerText = 'unlocked'; state_label.innerText = 'unlocked';
button_lock.disabled = false; button_lock.disabled = false;
button_unlock.disabled = true; button_unlock.disabled = true;
@ -20,6 +21,13 @@
button_lock.disabled = true; button_lock.disabled = true;
button_unlock.disabled = true; button_unlock.disabled = true;
} }
state_bat.innerHTML = '';
if (lock.ops.low_battery) {
state_bat.innerHTML = state_bat.innerHTML + '<svg class="icon"><use href="static/icons.svg#icon-mobile-bolt-svgrepo-com"/></svg>';
}
if (lock.ops.low_battery) {
state_bat.innerHTML = state_bat.innerHTML + '<svg class="icon"><use href="static/icons.svg#icon-broken-link-svgrepo-com"/></svg>';
}
} }
let lock_button_add_event_handler = function (e, id, locking) { let lock_button_add_event_handler = function (e, id, locking) {
@ -68,20 +76,24 @@
let lock_line = document.createElement("div"); let lock_line = document.createElement("div");
lock_line.id = `lock__line__${lock.id}`; lock_line.id = `lock__line__${lock.id}`;
lock_line.classList.add("lock__line"); lock_line.classList.add("lock__line");
lock_line.innerHTML = `<div class="lock__state-and-label"><span id="lock__state-icon__${lock.id}" class="lock__state-icon"></span><span class="lock__name">${lock.name}:&nbsp;</span><span id="lock__state-label__${lock.id}" class="lock__state-label"></span></div>`; let lock_line_inner = `<span class="lock__state-icon"></span>`
lock_line_inner += `<span class="lock__name">${lock.name}:&nbsp;</span>`
lock_line_inner += `<span class="lock__state-label"></span>`
lock_line_inner += `<span class="lock__state-bat"></span>`
lock_line.innerHTML = `<div class="lock__state-and-label">${lock_line_inner}</div>`;
locks.appendChild(lock_line); locks.appendChild(lock_line);
let lock_button = document.createElement("button"); let lock_button = document.createElement("button");
lock_button.classList.add("lock__button__lock");
lock_button.name = lock.id; lock_button.name = lock.id;
lock_button.id = `lock__lock__${lock.id}`;
lock_button.disabled = true; lock_button.disabled = true;
lock_button.innerText = "Lock"; lock_button.innerText = "Lock";
lock_button_add_event_handler(lock_button, lock.id, true); lock_button_add_event_handler(lock_button, lock.id, true);
lock_line.appendChild(lock_button); lock_line.appendChild(lock_button);
lock_button = document.createElement("button"); lock_button = document.createElement("button");
lock_button.classList.add("lock__button__unlock");
lock_button.name = lock.id; lock_button.name = lock.id;
lock_button.id = `lock__unlock__${lock.id}`;
lock_button.disabled = true; lock_button.disabled = true;
lock_button.innerText = "Unlock"; lock_button.innerText = "Unlock";
lock_button_add_event_handler(lock_button, lock.id, false); lock_button_add_event_handler(lock_button, lock.id, false);

View file

@ -1,147 +0,0 @@
/* Generated using nucleoapp.com */
/* --------------------------------
General
-------------------------------- */
:root {
--icon-color-primary: inherit;
--icon-color-secondary: currentColor;
}
.icon {
display: inline-block;
color: var(--icon-color-primary); /* icon primary color */
height: 1em;
width: 1em;
line-height: 1;
flex-shrink: 0;
max-width: initial;
}
.icon use {
/* icon secondary color */
fill: var(--icon-color-secondary);
stroke: var(--icon-color-secondary);
}
/* --------------------------------
Themes
-------------------------------- */
.icon-theme-1 {
--icon-color-primary: #212121;
--icon-color-secondary: inherit;
}
/* --------------------------------
Sizes
-------------------------------- */
:root {
--icon-sm: 0.8em;
--icon-lg: 1.2em;
}
/* relative units */
.icon-sm {
font-size: var(--icon-sm);
}
.icon-lg {
font-size: var(--icon-lg);
}
/* absolute units */
.icon-16 {
font-size: 16px;
}
.icon-32 {
font-size: 32px;
}
/* --------------------------------
Stroke
-------------------------------- */
.stroke-1 {
stroke-width: 1px;
}
.stroke-2 {
stroke-width: 2px;
}
.stroke-3 {
stroke-width: 3px;
}
.stroke-4 {
stroke-width: 4px;
}
/* --------------------------------
Caps/Corners
-------------------------------- */
.icon use {
--icon-stroke-linecap-butt: butt;
stroke-miterlimit: 10;
stroke-linecap: square;
stroke-linejoin: miter;
}
.stroke-round use {
--icon-stroke-linecap-butt: round;
stroke-linecap: round;
stroke-linejoin: round;
}
/* --------------------------------
Transformations/Animations
-------------------------------- */
.icon-rotate-90 {
transform: rotate(90deg);
}
.icon-rotate-180 {
transform: rotate(180deg);
}
.icon-rotate-270 {
transform: rotate(270deg);
}
.icon-flip-y {
transform: scaleY(-1);
}
.icon-flip-x {
transform: scaleX(-1);
}
.icon-is-spinning {
animation: icon-spin 1s infinite linear;
}
@keyframes icon-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View file

@ -2,8 +2,40 @@
{% block page_title %}CCCHH Dooris{% endblock %} {% block page_title %}CCCHH Dooris{% endblock %}
{% block page_body %} {% block page_body %}
<div id="locks"></div> <div id="locks"></div>
<div> <div class="legend">
<p>Click the respective buttons to lock or unlock a door. If the status of a door is ”unknown”, you will need to <p>Click the respective buttons to lock or unlock a door.</p>
run the lock manually by pressing the lock or unlock button on the door lock.</p> <ul class="legend__icons">
<li>
<svg class="icon">
<use href="static/icons.svg#icon-lock-alt-svgrepo-com"/>
</svg>
The door is locked.
</li>
<li>
<svg class="icon">
<use href="static/icons.svg#icon-unlock-alt-svgrepo-com"/>
</svg>
The door is unlocked.
</li>
<li>
<svg class="icon">
<use href="static/icons.svg#icon-arrow-spin-svgrepo-com"/>
</svg>
The lock status is moving or unknown. If the status does not change within 30 seconds, you will need to
run the lock manually by pressing the lock or unlock button on the door lock.
</li>
<li>
<svg class="icon">
<use href="static/icons.svg#icon-mobile-bolt-svgrepo-com"/>
</svg>
The battery is getting low. Please open the door motor and swap out the batteries.
</li>
<li>
<svg class="icon">
<use href="static/icons.svg#icon-broken-link-svgrepo-com"/>
</svg>
The lock is offline. Verify the lock batteries and the CCU radio configuration.
</li>
</ul>
</div> </div>
{% endblock %} {% endblock %}