Skip to content

Commit

Permalink
Merge pull request smarthomeNG#774 from sisamiwe/avm_update
Browse files Browse the repository at this point in the history
AVM: Exception Classes & Minor Fixes
  • Loading branch information
aschwith authored Jul 31, 2023
2 parents 50912ba + cad3d07 commit c7bc765
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 92 deletions.
138 changes: 82 additions & 56 deletions avm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,19 +87,8 @@ def to_float(arg) -> float:
return 0


def to_int_to_bool(arg) -> bool:
arg = to_int(arg)
try:
return bool(arg)
except (ValueError, TypeError):
return False


def clamp(n, min_n, max_n):
try:
return max(min(max_n, n), min_n)
except (ValueError, TypeError):
return 0
return max(min(max_n, n), min_n)


def walk_nodes(root, nodes: list):
Expand All @@ -124,7 +113,7 @@ class AVM(SmartPlugin):
"""
Main class of the Plugin. Does all plugin specific stuff
"""
PLUGIN_VERSION = '2.0.7'
PLUGIN_VERSION = '2.0.8'

# ToDo: FritzHome.handle_updated_item: implement 'saturation'
# ToDo: FritzHome.handle_updated_item: implement 'unmapped_hue'
Expand Down Expand Up @@ -162,30 +151,33 @@ def __init__(self, sh):
# init FritzDevice
try:
self.fritz_device = FritzDevice(_host, _port, ssl, _verify, _username, _passwort, _call_monitor_incoming_filter, _use_tr064_backlist, _log_entry_count, self)
except IOError as e:
except FritzAuthorizationError as e:
self.logger.warning(f"{e} occurred during establishing connection to FritzDevice via TR064-Interface. Not connected.")
self.fritz_device = None
else:
self.logger.debug("Connection to FritzDevice via TR064-Interface established.")
self.logger.info("Connection to FritzDevice via TR064-Interface established.")

# init FritzHome
try:
self.fritz_home = FritzHome(_host, ssl, _verify, _username, _passwort, _log_entry_count, self)
except IOError as e:
self.logger.warning(f"{e} occurred during establishing connection to FritzDevice via AHA-HTTP-Interface. Not connected.")
self.fritz_home = None
if self._aha_http_interface:
try:
self.fritz_home = FritzHome(_host, ssl, _verify, _username, _passwort, _log_entry_count, self)
except FritzAuthorizationError as e:
self.logger.warning(f"{e} occurred during establishing connection to FritzDevice via AHA-HTTP-Interface. Not connected.")
self.fritz_home = None
else:
self.logger.info("Connection to FritzDevice via AHA-HTTP-Interface established.")
else:
self.logger.debug("Connection to FritzDevice via AHA-HTTP-Interface established.")
self.fritz_home = None

# init Call Monitor
if self._call_monitor and self.fritz_device and self.fritz_device.connected:
try:
self.monitoring_service = Callmonitor(_host, 1012, self.fritz_device.get_contact_name_by_phone_number, _call_monitor_incoming_filter, self)
except IOError as e:
except FritzAuthorizationError as e:
self.logger.warning(f"{e} occurred during establishing connection to FritzDevice CallMonitor. Not connected.")
self.monitoring_service = None
else:
self.logger.debug("Connection to FritzDevice CallMonitor established.")
self.logger.info("Connection to FritzDevice CallMonitor established.")
else:
self.monitoring_service = None

Expand Down Expand Up @@ -782,7 +774,7 @@ def __init__(self, host, port, ssl, verify, username, password, call_monitor_inc
# check connection:
conn_test_result = self.model_name()
if isinstance(conn_test_result, int):
raise IOError(f"Error {conn_test_result}-'{self.ERROR_CODES.get(conn_test_result, 'unknown')}'")
raise FritzAuthorizationError(f"Error {conn_test_result}-'{self.ERROR_CODES.get(conn_test_result, 'unknown')}'")

self.connected = True
if self.is_fritzbox():
Expand All @@ -805,11 +797,12 @@ def handle_updated_item(self, item, avm_data_type: str, readafterwrite: int):
# to be set value
to_be_set_value = item()

# define command per avm_data_type
_dispatcher = {'wlanconfig': ('set_wlan', {'NewEnable': int(to_be_set_value)}, index),
'wps_active': ('set_wps', {'NewX_AVM_DE_WPSEnable': int(to_be_set_value)}, index),
'tam': ('set_tam', {'NewIndex': int(index), 'NewEnable': int(to_be_set_value)}, None),
'deflection_enable': ('set_deflection', {'NewDeflectionId': int(index), 'NewEnable': int(to_be_set_value)}, None),
# define command per avm_data_type // all avm_data_type of TR064_RW_ATTRIBUTES must be defined here
_dispatcher = {'wlanconfig': ('set_wlan', {'NewEnable': int(to_be_set_value)}, index),
'wps_active': ('set_wps', {'NewX_AVM_DE_WPSEnable': int(to_be_set_value)}, index),
'tam': ('set_tam', {'NewIndex': int(index), 'NewEnable': int(to_be_set_value)}, None),
'deflection_enable': ('set_deflection', {'NewDeflectionId': int(index), 'NewEnable': int(to_be_set_value)}, None),
'aha_device': ('set_aha_device', {'NewAIN': index, 'NewSwitchState': 'ON' if to_be_set_value else 'OFF'}, None)
}

# do logging
Expand Down Expand Up @@ -923,9 +916,13 @@ def wlan_devices_count(self):
def cyclic_item_update(self, read_all: bool = False):
"""Updates Item Values"""

if not self._plugin_instance.alive:
return

current_time = int(time.time())

# iterate over items and get data
item_count = 0
for item in self.item_list():

if not self.connected:
Expand Down Expand Up @@ -963,6 +960,7 @@ def cyclic_item_update(self, read_all: bool = False):
self.logger.debug(f"Item={item.path()} with avm_data_type={avm_data_type} and index={index} will be updated")

# get data and set item value
item_count += 1
if not self._update_item_value(item, avm_data_type, index) and self.use_tr064_blacklist:
error_count += 1
self.logger.debug(f"{item.path()} caused error. New error_count: {error_count}. Item will be blacklisted after more than 2 errors.")
Expand All @@ -974,6 +972,8 @@ def cyclic_item_update(self, read_all: bool = False):
# clear data cache dict after update cycle
self._clear_data_cache()

self.logger.debug(f"Update of {item_count} TR064-Items took {int(time.time()) - current_time}s")

def _update_item_value(self, item, avm_data_type: str, index: str) -> bool:
""" Polls data and set item value; Return True if action was successful, else False"""

Expand Down Expand Up @@ -1503,8 +1503,10 @@ def get_device_log_from_tr064_separated(self):
l_text = text[18:]
l_cat = '-'
l_type = '-'
l_ts = int(datetime.datetime.timestamp(datetime.datetime.strptime(text[:17], '%d.%m.%y %H:%M:%S')))
log_list.append([l_text, l_type, l_cat, l_ts, l_date, l_time])
# l_ts = int(datetime.datetime.timestamp(datetime.datetime.strptime(text[:17], '%d.%m.%y %H:%M:%S')))
# log_list.append([l_text, l_type, l_cat, l_ts, l_date, l_time])
dt = datetime.datetime.strptime(f"{l_date} {l_time}", '%d.%m.%y %H:%M:%S').strftime('%d.%m.%Y %H:%M:%S')
log_list.append([dt, l_text, l_type, l_cat])

return log_list

Expand Down Expand Up @@ -2108,6 +2110,10 @@ def item_list(self):
def _request(self, url: str, params: dict = None):
"""
Send a get request with parameters and return response as tuple with (content_type, response)
Raises FritzHttpTimeoutError on timeout
Raises a FritzHttpRequestError if the device does not support the command or arguments.
Raises FritzHttpInterfaceError on missing rights.
Raises a FritzAuthorizationError if server error occurred.
:param url: URL to be requested
:param params: params for request
Expand Down Expand Up @@ -2135,9 +2141,7 @@ def get_sid():
else:
msg = "HTTP request timed out."
self.logger.info(msg)
raise IOError(msg)
except requests.exceptions.ConnectionError:
raise IOError("ConnectionError during HTTP request.")
raise FritzHttpTimeoutError(msg)

if response.status_code == 200:
content_type = response.headers.get('content-type')
Expand All @@ -2148,17 +2152,17 @@ def get_sid():
elif response.status_code == 403:
msg = f"{response.status_code!r} Forbidden: 'Session-ID ungültig oder Benutzer nicht autorisiert'"
self.logger.info(msg)
raise IOError(msg)
raise FritzHttpInterfaceError(msg)

elif response.status_code == 400:
msg = f"{response.status_code!r} HTTP Request fehlerhaft, Parameter sind ungültig, nicht vorhanden oder Wertebereich überschritten"
self.logger.info(f"Error {msg}, params: {params}")
raise IOError(msg)
raise FritzHttpRequestError(msg)

else:
msg = f"Error {response.status_code!r} Internal Server Error: 'Interner Fehler'"
self.logger.info(f"{msg}, params: {params}")
raise IOError(msg)
raise FritzAuthorizationError(msg)

def aha_request(self, cmd: str, ain: str = None, param: dict = None, result_type: str = None):
"""Send an AHA request.
Expand All @@ -2178,7 +2182,7 @@ def aha_request(self, cmd: str, ain: str = None, param: dict = None, result_type

try:
header, content = self._request(url=url, params=params)
except IOError as e:
except (FritzAuthorizationError, FritzHttpTimeoutError, FritzHttpInterfaceError, FritzHttpRequestError) as e:
self.logger.warning(f"Error '{e}' occurred during requesting AHA Interface")
return None

Expand All @@ -2195,7 +2199,7 @@ def aha_request(self, cmd: str, ain: str = None, param: dict = None, result_type
return None

if result_type == 'bool':
return to_int_to_bool(content)
return bool(to_int(content))
elif result_type == 'int':
return to_int(content)
elif result_type == 'float':
Expand Down Expand Up @@ -2255,7 +2259,7 @@ def get_md5_hash() -> str:

if sid2 == "0000000000000000":
self.logger.warning(f"Login failed for user '{self.user}'")
raise IOError(f"Error 'AHA Login failed for user '{self.user}''")
raise FritzAuthorizationError(f"Error 'AHA Login failed for user '{self.user}''")

self._sid = sid2

Expand Down Expand Up @@ -2951,7 +2955,7 @@ def get_device_log_from_lua(self) -> Union[None, list]:
# get data
try:
header, content = self._request(url=url, params={})
except IOError as e:
except (FritzAuthorizationError, FritzHttpTimeoutError, FritzHttpInterfaceError, FritzHttpRequestError) as e:
self.logger.warning(f"Error '{e}' occurred during requesting AHA Interface")
return None

Expand Down Expand Up @@ -2986,7 +2990,7 @@ def get_device_log_from_lua_separated(self) -> Union[None, list]:
# get data
try:
header, content = self._request(url=url, params={})
except IOError as e:
except (FritzAuthorizationError, FritzHttpTimeoutError, FritzHttpInterfaceError, FritzHttpRequestError) as e:
self.logger.warning(f"Error '{e}' occurred during requesting AHA Interface")
return None

Expand Down Expand Up @@ -3285,7 +3289,6 @@ def _update_from_node(self, node):
if not self.connected:
return

@property
def has_light(self):
"""Check if the device has LightBulb function."""
return self._has_feature(FritzHome.FritzhomeDeviceFeatures.LIGHT)
Expand All @@ -3303,10 +3306,9 @@ def _update_from_node(self, node):
if not self.connected:
return

if self.has_powermeter:
if self.has_powermeter():
self._update_powermeter_from_node(node)

@property
def has_powermeter(self):
"""Check if the device has powermeter function."""
return self._has_feature(FritzHome.FritzhomeDeviceFeatures.POWER_METER)
Expand Down Expand Up @@ -3618,10 +3620,9 @@ def _update_from_node(self, node):
self.levelpercentage = 0
return

if self.has_level:
if self.has_level():
self._update_level_from_node(node)

@property
def has_level(self):
"""Check if the device has dimmer function."""
return self._has_feature(FritzHome.FritzhomeDeviceFeatures.LEVEL)
Expand Down Expand Up @@ -3663,10 +3664,9 @@ def _update_from_node(self, node):
if self.connected is False:
return

if self.has_color:
if self.has_color():
self._update_color_from_node(node)

@property
def has_color(self):
"""Check if the device has LightBulb function."""
return self._has_feature(FritzHome.FritzhomeDeviceFeatures.COLOR)
Expand Down Expand Up @@ -3732,31 +3732,31 @@ def _update_color_from_node(self, node):

def get_colors(self):
"""Get the supported colors."""
if self.has_color:
if self.has_color():
return self._fritz.get_colors(self.ain)
else:
return {}

def set_color(self, hsv, duration=0):
"""Set HSV color."""
if self.has_color:
if self.has_color():
self._fritz.set_color(self.ain, hsv, duration, True)

def set_unmapped_color(self, hsv, duration=0):
"""Set unmapped HSV color (Free color selection)."""
if self.has_color:
if self.has_color():
self._fritz.set_color(self.ain, hsv, duration, False)

def get_color_temps(self):
"""Get the supported color temperatures energy."""
if self.has_color:
if self.has_color():
return self._fritz.get_color_temps(self.ain)
else:
return []

def set_color_temp(self, temperature, duration=0):
"""Set white color temperature."""
if self.has_color:
if self.has_color():
self._fritz.set_color_temp(self.ain, temperature, duration)

class FritzhomeDeviceHumidity(FritzhomeDeviceBase):
Expand Down Expand Up @@ -3860,7 +3860,7 @@ def __init__(self, host, port, callback, call_monitor_incoming_filter, plugin_in
# connect
self.connect()
if not self.conn:
raise IOError("Connection Error")
raise FritzAuthorizationError("Callmonitor Connection Error")

def connect(self):
"""
Expand Down Expand Up @@ -4110,7 +4110,8 @@ def _stop_counter(self, direction: str):
self._duration_counter_thread_incoming.join(1)
elif direction == 'outgoing':
self._duration_counter_thread_outgoing.join(1)
except Exception:
except Exception as e:
self.logger.warning(f"Error {e!r} occurred during stopping counter of Callmonitor")
pass

def _count_duration_incoming(self):
Expand Down Expand Up @@ -4342,10 +4343,35 @@ def _trigger(self, call_from: str, call_to: str, dt: str, callid: str, event: st
self._call_incoming_cid = None


class FritzHttpInterfaceError(Exception):
"""
Exception raised on calling the aha-interface and getting a response with a status-code other than 200.
"""


class FritzHttpRequestError(Exception):
"""
Exception raised on calling the aha-interface with non-valid parameters
"""


class FritzHttpTimeoutError(Exception):
"""
Exception raised on calling the aha-interface and getting a timeout
"""


class FritzAuthorizationError(Exception):
"""
Authentication error. Not allowed to access the box at all.
"""


#
# static XML helpers
#


def get_node_value(elem, node):
return elem.findtext(node)

Expand Down
2 changes: 1 addition & 1 deletion avm/plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ plugin:
documentation: http://smarthomeng.de/user/plugins/avm/user_doc.html
support: https://knx-user-forum.de/forum/supportforen/smarthome-py/934835-avm-plugin

version: 2.0.7 # Plugin version (must match the version specified in __init__.py)
version: 2.0.8 # Plugin version (must match the version specified in __init__.py)
sh_minversion: 1.8 # minimum shNG version to use this plugin
# sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
# py_minversion: 3.6 # minimum Python version to use for this plugin
Expand Down
4 changes: 2 additions & 2 deletions avm/webif/__init__.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ def index(self, reload=None, action=None):
if self.plugin.fritz_home:
aha_items = self.plugin.get_aha_items()
aha_item_count = len(aha_items)
logentries = self.plugin.get_device_log_from_lua_separated()
logentries = self.plugin.fritz_home.get_device_log_from_lua_separated()
else:
aha_items = None
aha_item_count = None
if self.plugin.fritz_device:
logentries = self.plugin.get_device_log_from_tr064_separated()
logentries = self.plugin.fritz_device.get_device_log_from_tr064_separated()
else:
logentries = None

Expand Down
Binary file added avm/webif/static/img/lamp_amber.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit c7bc765

Please sign in to comment.