Skip to content

Commit

Permalink
Merge branch 'develop' of https://github.com/smarthomeNG/plugins into…
Browse files Browse the repository at this point in the history
… develop
  • Loading branch information
aschwith committed Nov 14, 2024
2 parents b35e073 + 940af3a commit d820b95
Show file tree
Hide file tree
Showing 82 changed files with 12,064 additions and 8,488 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ nosetests.xml
ehthumbs.db
Thumbs.db

# don't upload private plugins
/priv_*/
# don't upload private plugins or symlinked dirs
/priv_*

# don't upload plugins loaded from develop to a release installation
/*_dev/
Expand Down
76 changes: 40 additions & 36 deletions beolink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class BeoNetlink(SmartPlugin):
the update functions for the items
"""

PLUGIN_VERSION = '0.8.0'
PLUGIN_VERSION = '0.8.1'

def __init__(self, sh):
"""
Expand Down Expand Up @@ -267,51 +267,55 @@ def poll_device(self):
item = self.beo_items[beo_itemkey]
beo_id = item.conf['beo_id']
beo_status = item.conf['beo_status']
if beo_status:
if beo_status and beo_id != '':
# set items according to beo_status
#if beo_status == 'beoname':
# deviceinfo = self.beodevices.beodeviceinfo[beo_id].get('FriendlyName', None)
#elif beo_status == 'beotype':
# deviceinfo = self.beodevices.beodeviceinfo[beo_id].get('productType', None)
#else:
# deviceinfo = self.beodevices.beodeviceinfo[beo_id].get(beo_status, None)
if beo_status == 'audiomode':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('audiomode'[1], False)
elif beo_status == 'videomode':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('videomode'[1], False)
elif beo_status == 'powerstate':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('powerstate', False)
elif beo_status == 'stand':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('stand'[1], False)
elif beo_status == 'source':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['source'].get('source', '-')
elif beo_status == 'volume':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['volume'].get('level', 0)
elif beo_status == 'muted':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['volume'].get('muted', False)
elif beo_status == 'FriendlyName':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('FriendlyName', False)
elif beo_status == 'productType':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('productType', False)

beo_device = self.beodevices.beodeviceinfo.get(beo_id, None)
if beo_device is None:
self.logger.warning(f"poll_device: No deviceinfo found for device-id '{beo_id}'")
else:
deviceinfo = self.beodevices.beodeviceinfo[beo_id].get(beo_status, None)
#self.logger.info(f"poll_device: item={item.property.path}, beo_id={beo_id}, beo_status={beo_status}, self.beodevices.beodeviceinfo[beo_id]={self.beodevices.beodeviceinfo[beo_id]}")
#self.logger.info(f"poll_device: item={item.property.path}, deviceinfo={deviceinfo}")
if isinstance(deviceinfo, tuple):
if item._type == 'num':
beo_value = deviceinfo[1]
if beo_status == 'audiomode':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('audiomode'[1], False)
elif beo_status == 'videomode':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('videomode'[1], False)
elif beo_status == 'powerstate':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('powerstate', False)
elif beo_status == 'stand':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('stand'[1], False)
elif beo_status == 'source':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['source'].get('source', '-')
elif beo_status == 'volume':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['volume'].get('level', 0)
elif beo_status == 'muted':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['volume'].get('muted', False)
elif beo_status == 'FriendlyName':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('FriendlyName', False)
elif beo_status == 'productType':
deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('productType', False)

else:
beo_value = deviceinfo[0]
else:
beo_value = deviceinfo
deviceinfo = self.beodevices.beodeviceinfo[beo_id].get(beo_status, None)
#self.logger.info(f"poll_device: item={item.property.path}, beo_id={beo_id}, beo_status={beo_status}, self.beodevices.beodeviceinfo[beo_id]={self.beodevices.beodeviceinfo[beo_id]}")
#self.logger.info(f"poll_device: item={item.property.path}, deviceinfo={deviceinfo}")
if isinstance(deviceinfo, tuple):
if item._type == 'num':
beo_value = deviceinfo[1]
else:
beo_value = deviceinfo[0]
else:
beo_value = deviceinfo

if item() == beo_value:
self.logger.debug("update_deviceinfo: Updated item {} with beo-{} {}".format(item.property.path, beo_status, beo_value))
else:
self.logger.info("update_deviceinfo: Changed item {} with beo-{} {}".format(item.property.path, beo_status, beo_value))
item(beo_value, self.get_shortname())
self._update_item_values(item, beo_value)
if item() == beo_value:
self.logger.debug("update_deviceinfo: Updated item {} with beo-{} {}".format(item.property.path, beo_status, beo_value))
else:
self.logger.info("update_deviceinfo: Changed item {} with beo-{} {}".format(item.property.path, beo_status, beo_value))
item(beo_value, self.get_shortname())
self._update_item_values(item, beo_value)
else:
self.logger.info(f"poll_device: No beo_status")
return
Expand Down
24 changes: 13 additions & 11 deletions beolink/beodevices.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,20 +234,22 @@ def update_deviceinfo(self, beo_id):
self.beodeviceinfo[beo_id]['device']['audiomode'] = ['-', -1]
self.beodeviceinfo[beo_id]['device']['stand'] = ['-', -1]
else:
self.beodeviceinfo[beo_id]['source']['source'] = self.get_beo_api(ip, '/BeoZone/Zone/ActiveSources', ['primaryExperience','source','friendlyName'])

# get picture-mode of the B&O device
self.beodeviceinfo[beo_id]['device']['videomode'] = list(self.read_list_value(ip, '/Picture/Mode', 'mode'))
self.logger.debug("update_deviceinfo: ip: {} videomode-friendly = {}".format(ip, self.beodeviceinfo[beo_id]['device']['videomode']))
try:
self.beodeviceinfo[beo_id]['source']['source'] = self.get_beo_api(ip, '/BeoZone/Zone/ActiveSources', ['primaryExperience','source','friendlyName'])

# get sound-mode of the B&O device
self.beodeviceinfo[beo_id]['device']['audiomode'] = list(self.read_list_value(ip, '/Sound/Mode', 'mode'))
self.logger.debug("update_deviceinfo: ip: {} audiomode-friendly = {}".format(ip, self.beodeviceinfo[beo_id]['device']['audiomode']))
# get picture-mode of the B&O device
self.beodeviceinfo[beo_id]['device']['videomode'] = list(self.read_list_value(ip, '/Picture/Mode', 'mode'))
self.logger.debug("update_deviceinfo: ip: {} videomode-friendly = {}".format(ip, self.beodeviceinfo[beo_id]['device']['videomode']))

# get stand position of the B&O device
self.beodeviceinfo[beo_id]['device']['stand'] = list(self.read_list_value(ip, '/Stand', 'stand'))
self.logger.debug("update_deviceinfo: ip: {} stand-friendly = {}".format(ip, self.beodeviceinfo[beo_id]['device']['stand']))
# get sound-mode of the B&O device
self.beodeviceinfo[beo_id]['device']['audiomode'] = list(self.read_list_value(ip, '/Sound/Mode', 'mode'))
self.logger.debug("update_deviceinfo: ip: {} audiomode-friendly = {}".format(ip, self.beodeviceinfo[beo_id]['device']['audiomode']))

# get stand position of the B&O device
self.beodeviceinfo[beo_id]['device']['stand'] = list(self.read_list_value(ip, '/Stand', 'stand'))
self.logger.debug("update_deviceinfo: ip: {} stand-friendly = {}".format(ip, self.beodeviceinfo[beo_id]['device']['stand']))
except Exception as ex:
self.logger.warning(f"beodevices/update_deviceinfo: {beo_id} - Exception '{ex}'")
# get possible sources of the B&O device
#raw_sources = self.get_beo_api(ip, '/BeoZone/Zone/Sources', [])
#self.beodeviceinfo[beo_id]['sources'] = []
Expand Down
2 changes: 1 addition & 1 deletion beolink/plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ plugin:
# documentation: https://github.com/smarthomeNG/smarthome/wiki/CLI-Plugin # url of documentation (wiki) page
# support: https://knx-user-forum.de/forum/supportforen/smarthome-py

version: 0.8.0 # Plugin version
version: 0.8.1 # Plugin version
sh_minversion: '1.9' # minimum shNG version to use this plugin
# sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
multi_instance: False # plugin supports multi instance
Expand Down
40 changes: 37 additions & 3 deletions database/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class Database(SmartPlugin):
"""

ALLOW_MULTIINSTANCE = True
PLUGIN_VERSION = '1.6.12'
PLUGIN_VERSION = '1.6.13'

# SQL queries: {item} = item table name, {log} = log table name
# time, item_id, val_str, val_num, val_bool, changed
Expand Down Expand Up @@ -104,6 +104,7 @@ def __init__(self, sh, *args, **kwargs):
self._precision = self.get_parameter_value('precision')
self.count_logentries = self.get_parameter_value('count_logentries')
self.max_delete_logentries = self.get_parameter_value('max_delete_logentries')
self.max_reassign_logentries = self.get_parameter_value('max_reassign_logentries')
self._default_maxage = float(self.get_parameter_value('default_maxage'))

self._copy_database = self.get_parameter_value('copy_database')
Expand Down Expand Up @@ -967,6 +968,38 @@ def _count_orphanlogentries(self):
return


def reassign_orphaned_id(self, orphan_id, to):
"""
Reassign values from orphaned item ID to given item ID
:param orphan_id: item id of the orphaned item
:param to: item id of the target item
:type orphan_id: int
:type to: int
"""
log_info = self.logger.warning # info
log_debug = self.logger.error # debug
try:
log_info(f'reassigning orphaned data from (old) id {orphan_id} to (new) id {to}')
cur = self._db_maint.cursor()
count = self.readLogCount(orphan_id, cur=cur)
log_debug(f'found {count} entries to reassign, reassigning {self.max_reassign_logentries} at once')

while count > 0:
log_debug(f'reassigning {min(count, self.max_reassign_logentries)} log entries')
self._execute(self._prepare("UPDATE {log} SET item_id = :newid WHERE item_id = :orphanid LIMIT :limit;"), {'newid': to, 'orphanid': orphan_id, 'limit': self.max_reassign_logentries}, cur=cur)
count -= self.max_reassign_logentries

self._execute(self._prepare("DELETE FROM {item} WHERE id = :orphanid LIMIT 1;"), {'orphanid': orphan_id}, cur=cur)
log_info(f'reassigned orphaned id {orphan_id} to new id {to}')
cur.close()
self._db_maint.commit()
log_debug('rebuilding orphan list')
self.build_orphanlist()
except Exception as e:
self.logger.error(f'error on reassigning id {orphan_id} to {to}: {e}')
return e

def _delete_orphan(self, item_path):
"""
Delete orphan item or logentries it
Expand All @@ -988,7 +1021,7 @@ def _delete_orphan(self, item_path):
return True

cur = self._db_maint.cursor()
self._execute(self._prepare("DELETE FROM {log} WHERE item_id = :id ORDER BY time ASC LIMIT :maxrecords;"), {'id': item_id, 'maxrecords': self.delete_orphan_chunk_size}, cur=cur)
self._execute(self._prepare("DELETE FROM {log} WHERE item_id = :id LIMIT :maxrecords;"), {'id': item_id, 'maxrecords': self.delete_orphan_chunk_size}, cur=cur)
delete_orphan_chunk_size_str = f"{self.delete_orphan_chunk_size:,}".replace(',', '.')
self.logger.info(f"_delete_orphan: Deleted (up to) {delete_orphan_chunk_size_str} log entries for Item {item_path}")
cur.close()
Expand Down Expand Up @@ -1137,6 +1170,7 @@ def _series(self, func, start, end='now', count=100, ratio=1, update=False, step
'step': logs['step'], 'sid': sid},
'update': self.shtime.now() + datetime.timedelta(seconds=int(logs['step'] / 1000))
}
self.logger.dbgmed(f"_series: {sid=}, {step=}, update={result['update']}, delta={int(logs['step'] / 1000)}, now={self.shtime.now()}")
#self.logger.debug("_series: result={}".format(result))

return result
Expand Down Expand Up @@ -1183,7 +1217,7 @@ def _expression(self, func):
expression['finalizer'] = func[:func.index(":")]
func = func[func.index(":") + 1:]
if func == 'count' or func.startswith('count'):
parts = re.match('(count)((<>|!=|<|=|>)(\d+))?', func)
parts = re.match(r'(count)((<>|!=|<|=|>)(\d+))?', func)
func = 'count'
if parts and parts.group(3) is not None:
expression['params']['op'] = parts.group(3)
Expand Down
10 changes: 9 additions & 1 deletion database/plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ plugin:
keywords: database
support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1021844-neues-database-plugin

version: 1.6.12 # Plugin version
version: 1.6.13 # Plugin version
sh_minversion: '1.9.3.2' # minimum shNG version to use this plugin
# sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
multi_instance: True # plugin supports multi instance
Expand Down Expand Up @@ -72,6 +72,14 @@ parameters:
de: "Maximal auf einmal zu löschende Anzahl an Log Einträgen mit dem database_maxage Attribut, reduziert die Belastung der Datenbank bei alten Datenbeständen"
en: "Maximum number of Logentries to delete at once with database_maxage attribute, reduces load on database with old datasets"

max_reassign_logentries:
type: int
default: 20 # 000
valid_min: 10 # 00
description:
de: "Maximal auf einmal neu zuzuweisende Anzahl an Log Einträgen, reduziert die Belastung der Datenbank bei großen Datenbeständen"
en: "Maximum number of Logentries to reassign at once, reduces load on database with large datasets"

default_maxage:
type: int
default: 0
Expand Down
23 changes: 22 additions & 1 deletion database/webif/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,28 @@ def index(self, reload=None, action=None, item_id=None, item_path=None, time_end
tabcount=2, action=action, item_id=item_id, delete_triggered=delete_triggered,
language=self.plugin.get_sh().get_defaultlanguage())

@cherrypy.expose
def reassign(self):
cl = cherrypy.request.headers['Content-Length']
if not cl:
return
try:
rawbody = cherrypy.request.body.read(int(cl))
data = json.loads(rawbody)
except Exception:
return
orphan_id = data.get("orphan_id")
new_id = data.get("new_id")
result = {"operation": "request", "result": "success"}
if orphan_id is not None and new_id is not None and orphan_id != new_id:
self.logger.info(f'reassigning orphaned id {orphan_id} to new id {new_id}')
err = self.plugin.reassign_orphaned_id(orphan_id, to=new_id)
if err:
return
return json.dumps(result)
else:
self.logger.warning(f'reassigning orphaned id {orphan_id} to new id {new_id} failed')

@cherrypy.expose
def get_data_html(self, dataSet=None, params=None):
"""
Expand Down Expand Up @@ -271,7 +293,6 @@ def db_sqldump(self):

return


@cherrypy.expose
def cleanup(self):
self.plugin.cleanup()
Expand Down
37 changes: 37 additions & 0 deletions database/webif/static/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* The Modal (background) */
.or-modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 999; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}

/* Modal Content/Box */
.or-modal-content {
background-color: #fefefe;
margin: 15% auto; /* 15% from the top and centered */
padding: 20px;
border: 1px solid #888;
width: 80%; /* Could be more or less, depending on screen size */
}

/* The Close Button */
.or-close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}

.or-close:hover,
.or-close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
4 changes: 3 additions & 1 deletion database/webif/templates/base_database.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
{ className: "time", targets: 2 },
{ className: "type", targets: 3 },
{ className: "id", targets: 4, render: $.fn.dataTable.render.number('.', ',', 0, '') },
{ className: "logcount", targets: 5, render: $.fn.dataTable.render.number('.', ',', 0, '') },
{ className: "reassign", targets: 5 },
{ className: "logcount", targets: 6, render: $.fn.dataTable.render.number('.', ',', 0, '') },
].concat($.fn.dataTable.defaults.columnDefs)});
{% else %}
orphantable = $('#orphantable').DataTable( {
Expand All @@ -42,6 +43,7 @@
{ className: "time", targets: 2 },
{ className: "type", targets: 3 },
{ className: "id", targets: 4, render: $.fn.dataTable.render.number('.', ',', 0, '') },
{ className: "reassign", targets: 5 },
].concat($.fn.dataTable.defaults.columnDefs)});
{% endif %}

Expand Down
Loading

0 comments on commit d820b95

Please sign in to comment.