diff --git a/packages/modules/smarthome/shelly/off.py b/packages/modules/smarthome/shelly/off.py old mode 100755 new mode 100644 index bce4a2d76b..e9ce1b4f46 --- a/packages/modules/smarthome/shelly/off.py +++ b/packages/modules/smarthome/shelly/off.py @@ -1,12 +1,11 @@ #!/usr/bin/python3 import sys -import requests -from requests.auth import HTTPBasicAuth, HTTPDigestAuth +import time +import urllib.request import os import json -import logging - -log = logging.getLogger(__name__) +named_tuple = time.localtime() # getstruct_time +time_string = time.strftime("%m/%d/%Y, %H:%M:%S shelly off.py", named_tuple) devicenumber = str(sys.argv[1]) ipadr = str(sys.argv[2]) uberschuss = int(sys.argv[3]) @@ -19,44 +18,40 @@ shaut = int(sys.argv[5]) user = str(sys.argv[6]) pw = str(sys.argv[7]) - fbase = '/var/www/html/openWB/ramdisk/smarthome_device_ret.' -fnameg = fbase + str(ipadr) + '_shelly_infogv2' -log_pfx = "Device " + str(devicenumber) + " IP " + ipadr + ": " - -try: - if os.path.isfile(fnameg): - with open(fnameg, 'r') as f: - jsonin = json.loads(f.read()) - gen = str(jsonin['gen']) - model = str(jsonin['model']) - else: - gen = "1" - - if gen == "1": - if chan == 0: - url = f"http://{ipadr}/relay/0?turn=off" - else: - chan = chan - 1 - url = f"http://{ipadr}/relay/{chan}?turn=off" - else: - if chan > 0: - chan = chan - 1 - if "SPEM-003CE" in model: - chan = 100 - url = f"http://{ipadr}/rpc/Switch.Set?id={chan}&on=false" - - if shaut == 1: - if gen == "1": - # HTTP Basic Auth für Gen 1 - auth = HTTPBasicAuth(user, pw) - else: - # HTTP Digest Auth für Gen 2 oder SPEM-003CE - auth = HTTPDigestAuth("admin", pw) - response = requests.get(url, auth=auth, timeout=3) +fnameg = fbase + str(ipadr) + '_shelly_infogv1' +if os.path.isfile(fnameg): + with open(fnameg, 'r') as f: + jsonin = json.loads(f.read()) + gen = str(jsonin['gen']) + model = str(jsonin['model']) +else: + gen = "1" +if (gen == "1"): + if (chan == 0): + url = "http://" + str(ipadr) + "/relay/0?turn=off" + # urllib.request.urlopen("http://"+str(ipadr)+"/relay/0?turn=off", + # timeout=3) else: - response = requests.get(url, timeout=3) - - response.raise_for_status() # Fehler, wenn Statuscode nicht 2xx ist -except Exception as e: - log.error(f"{log_pfx}Error on changing switch: {str(e)}") + chan = chan - 1 + url = "http://" + str(ipadr) + "/relay/" + str(chan) + "?turn=off" + # urllib.request.urlopen("http://"+str(ipadr)+"/relay/" + str(chan) + + # "?turn=off", timeout=3) +else: + if (chan > 0): + chan = chan - 1 + # shelly pro 3em mit add on hat fix id 100 als switch Kanal, das Device muss auf jeden fall mit separater + # Leistunsmessung erfasst werden, da die Leistung auf drei verschiedenenen Kanälen angeliefert werden kann + if ("SPEM-003CE" in model): + chan = 100 + # gen 2 will das als off cmd IPderPro3EM/rpc/Switch.Set?id=100&on=false + url = "http://" + str(ipadr) + "/rpc/Switch.Set?id=" + str(chan) + "&on=false" +if (shaut == 1): + # print("Shelly off" + str(shaut) + user + pw) + passman = urllib.request.HTTPPasswordMgrWithDefaultRealm() + passman.add_password(None, url, user, pw) + authhandler = urllib.request.HTTPBasicAuthHandler(passman) + opener = urllib.request.build_opener(authhandler) + urllib.request.install_opener(opener) +with urllib.request.urlopen(url) as response: + response.read().decode("utf-8") diff --git a/packages/modules/smarthome/shelly/on.py b/packages/modules/smarthome/shelly/on.py index 11946788a5..85320a6444 100644 --- a/packages/modules/smarthome/shelly/on.py +++ b/packages/modules/smarthome/shelly/on.py @@ -1,12 +1,8 @@ #!/usr/bin/python3 import sys -import requests -from requests.auth import HTTPBasicAuth, HTTPDigestAuth +import urllib.request import os import json -import logging - -log = logging.getLogger(__name__) devicenumber = str(sys.argv[1]) ipadr = str(sys.argv[2]) uberschuss = int(sys.argv[3]) @@ -19,44 +15,40 @@ shaut = int(sys.argv[5]) user = str(sys.argv[6]) pw = str(sys.argv[7]) - fbase = '/var/www/html/openWB/ramdisk/smarthome_device_ret.' -fnameg = fbase + str(ipadr) + '_shelly_infogv2' -log_pfx = "Device " + str(devicenumber) + " IP " + ipadr + ": " - -try: - if os.path.isfile(fnameg): - with open(fnameg, 'r') as f: - jsonin = json.loads(f.read()) - gen = str(jsonin['gen']) - model = str(jsonin['model']) - else: - gen = "1" - - if gen == "1": - if chan == 0: - url = f"http://{ipadr}/relay/0?turn=on" - else: - chan = chan - 1 - url = f"http://{ipadr}/relay/{chan}?turn=on" - else: - if chan > 0: - chan = chan - 1 - if "SPEM-003CE" in model: - chan = 100 - url = f"http://{ipadr}/rpc/Switch.Set?id={chan}&on=true" - - if shaut == 1: - if gen == "1": - # HTTP Basic Auth für Gen 1 - auth = HTTPBasicAuth(user, pw) - else: - # HTTP Digest Auth für Gen 2 oder SPEM-003CE - auth = HTTPDigestAuth("admin", pw) - response = requests.get(url, auth=auth, timeout=3) +fnameg = fbase + str(ipadr) + '_shelly_infogv1' +if os.path.isfile(fnameg): + with open(fnameg, 'r') as f: + jsonin = json.loads(f.read()) + gen = str(jsonin['gen']) + model = str(jsonin['model']) +else: + gen = "1" +if (gen == "1"): + if (chan == 0): + url = "http://" + str(ipadr) + "/relay/0?turn=on" + # urllib.request.urlopen("http://"+str(ipadr)+"/relay/0?turn=on", + # timeout=3) else: - response = requests.get(url, timeout=3) - - response.raise_for_status() # Fehler, wenn Statuscode nicht 2xx ist -except Exception as e: - log.error(f"{log_pfx}Error on changing switch: {str(e)}") + chan = chan - 1 + url = "http://" + str(ipadr) + "/relay/" + str(chan) + "?turn=on" + # urllib.request.urlopen("http://"+str(ipadr)+"/relay/" + str(chan) + + # "?turn=on", timeout=3) +else: + if (chan > 0): + chan = chan - 1 + # shelly pro 3em mit add on hat fix id 100 als switch Kanal, das Device muss auf jeden fall mit separater + # Leistunsmessung erfasst werden, da die Leistung auf drei verschiedenenen Kanälen angeliefert werden kann + if ("SPEM-003CE" in model): + chan = 100 + # gen 2 will das als on cmd /rpc/Switch.Set?id=100&on=true + url = "http://" + str(ipadr) + "/rpc/Switch.Set?id=" + str(chan) + "&on=true" +if (shaut == 1): + # print("Shelly on" + str(shaut) + user + pw) + passman = urllib.request.HTTPPasswordMgrWithDefaultRealm() + passman.add_password(None, url, user, pw) + authhandler = urllib.request.HTTPBasicAuthHandler(passman) + opener = urllib.request.build_opener(authhandler) + urllib.request.install_opener(opener) +with urllib.request.urlopen(url) as response: + response.read().decode("utf-8") diff --git a/packages/modules/smarthome/shelly/watt.py b/packages/modules/smarthome/shelly/watt.py index aa80846eab..166e57ff0e 100644 --- a/packages/modules/smarthome/shelly/watt.py +++ b/packages/modules/smarthome/shelly/watt.py @@ -3,8 +3,7 @@ import os import time import json -import requests -from requests.auth import HTTPBasicAuth, HTTPDigestAuth +import urllib.request from typing import Any from smarthome.smartret import writeret import logging @@ -12,11 +11,23 @@ log = logging.getLogger(__name__) -def totalPowerFromShellyJson(answer: Any, workchan: int, component: str) -> int: - if workchan > 0: - return int(answer[component][workchan - 1]['power']) - power_sum = sum(emeter['power'] for emeter in answer[component] if isinstance(emeter, dict) and 'power' in emeter) - return int(power_sum) +def totalPowerFromShellyJson(answer: Any, workchan: int) -> int: + if (workchan == 0): + if 'meters' in answer: + meters = answer['meters'] # shelly + else: + meters = answer['emeters'] # shellyEM & shelly3EM + total = 0 + # shellyEM has one meter, shelly3EM has three meters: + for meter in meters: + total = total + meter['power'] + return int(total) + workchan = workchan - 1 + try: + total = int(answer['meters'][workchan]['power']) # Abfrage shelly + except Exception: + total = int(answer['emeters'][workchan]['power']) # Abfrage shellyEM + return int(total) named_tuple = time.localtime() # getstruct_time @@ -35,199 +46,134 @@ def totalPowerFromShellyJson(answer: Any, workchan: int, component: str) -> int: user = str(sys.argv[6]) pw = str(sys.argv[7]) # Setze Default-Werte, andernfalls wird der letzte Wert ewig fortgeschrieben. +# Insbesondere wichtig für aktuelle Leistung +# Zähler wird beim Neustart auf 0 gesetzt, darf daher nicht übergeben werden. powerc = 0 -temp = ['0.0', '0.0', '0.0'] +temp0 = '0.0' +temp1 = '0.0' +temp2 = '0.0' aktpower = 0 relais = 0 gen = '1' model = '???' -profile = '???' -components = {} # lesen endpoint, gen bestimmem. gen 1 hat unter Umstaenden keinen Eintrag -write_info = False -delete_info = False -device_info = {} -power_field = ['total_act_power', 'a_act_power', 'b_act_power', 'c_act_power'] - fbase = '/var/www/html/openWB/ramdisk/smarthome_device_ret.' -# Response of "/shelly"-url: -fname_shellyinfo = fbase + ipadr + '_shelly_info' -# Response of "/rpc/Shelly.ListProfiles": -fname_profiles = fbase + ipadr + '_shelly_infoc' -# Internal cache for gathered device info: -fname_devcache = fbase + ipadr + '_shelly_infogv2' -# Response for "/status" or "/rpc/Shelly.GetStatus": -fname_statusrsp = fbase + ipadr + '_shelly_res' - -log_pfx = "Device " + str(devicenumber) + " IP " + ipadr + ": " - -# Do we have a cache of the device features? +fname = fbase + str(ipadr) + '_shelly_info' +fnameg = fbase + str(ipadr) + '_shelly_infogv1' +if os.path.isfile(fnameg): + with open(fnameg, 'r') as f: + jsonin = json.loads(f.read()) + gen = str(jsonin['gen']) + model = str(jsonin['model']) +else: + aread = urllib.request.urlopen("http://" + str(ipadr) + "/shelly", + timeout=3).read().decode("utf-8") + agen = json.loads(str(aread)) + with open(fname, 'w') as f: + json.dump(agen, f) + if 'gen' in agen: + gen = str(int(agen['gen'])) + if 'model' in agen: + model = str(agen['model']) + elif 'type' in agen: + model = str(agen['type']) + jsontype = {"gen": str(gen), "model": str(model)} + with open(fnameg, 'w') as f: + f.write(json.dumps(jsontype)) +# Versuche Daten von Shelly abzurufen. try: - if os.path.isfile(fname_devcache): - try: - with open(fname_devcache, 'r') as f: - device_info = json.loads(f.read()) - gen = str(device_info['gen']) - model = str(device_info['model']) - profile = str(device_info['profile']) - components = device_info['components'] # Kein str(), da dict - except Exception: - # Delete this cache file - it seems broken - delete_info = True - pass + # print("Shelly " + str(shaut) + user + pw) + if (gen == "1"): + url = "http://" + str(ipadr) + "/status" + if (shaut == 1): + passman = urllib.request.HTTPPasswordMgrWithDefaultRealm() + passman.add_password(None, url, user, pw) + authhandler = urllib.request.HTTPBasicAuthHandler(passman) + opener = urllib.request.build_opener(authhandler) + urllib.request.install_opener(opener) + with urllib.request.urlopen(url, timeout=3) as response: + aread = response.read().decode("utf-8") + answer = json.loads(str(aread)) else: - # New device analysis: Start with /shelly URL - url = f'http://{ipadr}/shelly' - response = requests.get(url, timeout=3) - response.raise_for_status() - aread = response.text - log.warning(log_pfx + "/shelly response " + aread) - device_info = json.loads(aread) - agen = json.loads(aread) - with open(fname_shellyinfo, 'w') as f: - json.dump(agen, f) - if 'gen' in agen: - gen = str(int(agen['gen'])) - device_info['gen'] = gen - if 'model' in agen: - model = str(agen['model']) - elif 'type' in agen: - model = str(agen['type']) - device_info['model'] = model - if 'profile' in agen: - # Shelly with multiple profiles (z.B. 3EM, 2PM) - profile = str(agen['profile']) - device_info['profile'] = profile - if gen != "1": - url = f'http://{ipadr}/rpc/Shelly.ListProfiles' - if shaut == 1: - response = requests.get(url, timeout=3, - auth=HTTPDigestAuth("admin", pw)) - else: - response = requests.get(url, timeout=3) - response.raise_for_status() - aread = response.text - log.warning(log_pfx + " /rpc/Shelly.ListProfiles response " + aread) - agen = json.loads(aread) - with open(fname_profiles, 'w') as f: - json.dump(agen, f) - for item in agen['profiles'][profile]['components']: - components[item['type']] = item['count'] - device_info['components'] = components - # We have a new device analysis, store it: - write_info = True -except Exception as e: - log.error(log_pfx + 'Error on device analysis ' + str(e)) - pass - -# Pre-Analysis done / loaded, now get the data: + aread = urllib.request.urlopen("http://"+str(ipadr) + + "/rpc/Shelly.GetStatus", + timeout=3).read().decode("utf-8") + answer = json.loads(str(aread)) + with open('/var/www/html/openWB/ramdisk/smarthome_device_ret.' + + str(ipadr) + '_shelly', 'w') as f: + f.write(str(answer)) +except Exception: + log.debug("failed to connect to device on " + + ipadr + ", setting all values to 0") +# answer.update(a_dictionary) +# Versuche Werte aus der Antwort zu extrahieren. try: - # For future use: Caching of response: - # Check the last response is < 5 seconds old - if (os.path.exists(fname_statusrsp) and - os.path.getmtime(fname_statusrsp) + 4 > time.time()): - # We will use a cached Status-page - with open(fname_statusrsp, 'r') as f: - answer = json.loads(f.read()) + if (gen == "1"): + aktpower = totalPowerFromShellyJson(answer, chan) else: - # No (valid) cache: We have to fetch the data: - url = f'http://{ipadr}/{"status" if gen == "1" else "rpc/Shelly.GetStatus"}' - if shaut == 1: - if gen == "1": - response = requests.get(url, timeout=3, - auth=HTTPBasicAuth(user, pw)) + if (chan > 0): + workchan = chan - 1 + else: + workchan = chan + sw = 'switch:' + str(workchan) + if ("SPEM-003CE" in model): + if (workchan == 1): + aktpower = int(answer['em:0']['a_act_power']) + elif (workchan == 2): + aktpower = int(answer['em:0']['b_act_power']) + elif (workchan == 3): + aktpower = int(answer['em:0']['c_act_power']) else: - response = requests.get(url, timeout=3, - auth=HTTPDigestAuth("admin", pw)) + aktpower = int(answer['em:0']['total_act_power']) + elif ("PM-001PCEU16" in model): + # "SNPM-001PCEU16" (gen 2) und "S3PM-001PCEU16" (gen 3) + aktpower = int(answer['pm1:0']['apower']) else: - response = requests.get(url, timeout=3) - - response.raise_for_status() - aread = response.text - answer = json.loads(aread) - with open(fname_statusrsp, 'w') as f: - json.dump(answer, f) - - if not components: - # Late device analysis, based on the first response: - prefixes = ['switch:', 'em:', 'emdata:', 'pm1:', 'em1:', 'em1data:', 'temperature:'] - components = { - prefix[:-1]: count - for prefix in prefixes - if (count := sum(key.startswith(prefix) for key in answer.keys())) > 0 - } - # Gen1 - Komponenten: - prefixes = ['relays', 'emeters', 'meters', 'ext_temperature'] - for prefix in prefixes: - if prefix in answer: - components[prefix] = len(answer.get(prefix)) - device_info['components'] = components - write_info = True - -except Exception as e: - log.error(log_pfx + 'Error on data fetch ' + str(e)) + aktpower = int(answer[sw]['apower']) +except Exception: pass -# We have the response: Start parsing: -workchan = chan - 1 if chan > 0 else chan - try: - if 'switch' in components: - # Beim Shelly Pro 3EM mit AddOn ist die Switch-ID 100, sonst ab 0: - sw = 'switch:' + str(workchan) if 'SPEM-003CE' not in model else 'switch:100' - if sw not in answer: - # Typisch, wenn der Messwert auf einem höheren Kanal geholt werden soll - sw = 'switch:0' + if (chan > 0): + workchan = chan - 1 + else: + workchan = chan + if (gen == "1"): + relais = int(answer['relays'][workchan]['ison']) + else: + # shelly pro 3em mit add on hat fix id 100 als switch Kanal, das Device muss auf jeden fall mit separater + # Leistunsmessung erfasst werden, da die Leistung auf drei verschieden Kanäle angeliefert werden kann + if ("SPEM-003CE" in model): + workchan = 100 + sw = 'switch:' + str(workchan) relais = int(answer[sw]['output']) - aktpower = int(answer[sw]['apower']) if 'apower' in answer[sw] else 0 - if 'relays' in components: - relais = int(answer['relays'][workchan if (workchan < len(answer['relays'])) else 0]['ison']) - if 'meters' in components: - aktpower = totalPowerFromShellyJson(answer, chan, 'meters') - if 'pm1' in components: - if chan == 0: - aktpower = int(sum(answer['pm1:' + str(em)]['apower'] for em in range(components['pm1']))) - else: - sw = 'pm1:' + str(workchan) - aktpower = int(answer[sw]['apower']) - if 'em1' in components: - if chan == 0: - aktpower = int(sum(answer['em1:' + str(em)]['act_power'] for em in range(components['em1']))) - else: - sw = 'em1:' + str(workchan) - aktpower = int(answer[sw]['act_power']) - if 'em' in components: - aktpower = int(answer['em:0'][power_field[chan]]) - if 'emeters' in components: - aktpower = totalPowerFromShellyJson(answer, chan, 'emeters') - if 'ext_temperature' in components: - for i in range(len(answer['ext_temperature'])): - temp[i] = str(answer['ext_temperature'][str(i)]['tC']) - if 'temperature' in components: - for i in range(components['temperature']): - field = 'temperature:' + str(i + 100) - if field in answer: - temp[i] = str(answer[field]['tC']) -except Exception as e: - exc_type, exc_value, exc_traceback = sys.exc_info() - filename = exc_traceback.tb_frame.f_code.co_filename - line_number = exc_traceback.tb_lineno - function_name = exc_traceback.tb_frame.f_code.co_name - log.error( - f"{log_pfx}Error on data parsing: {str(e)} (File: {filename}, Line: {line_number}, Function: {function_name})") +except Exception: + pass -if write_info: - with open(fname_devcache, 'w') as f: - f.write(json.dumps(device_info)) - log.warning(log_pfx + " cached info " + json.dumps(device_info)) +try: + if gen == "1": + temp0 = str(answer['ext_temperature']['0']['tC']) + else: + temp0 = str(answer['temperature:100']['tC']) +except Exception: + pass -if delete_info: - try: - os.remove(fname_shellyinfo) - except Exception: - pass +try: + if gen == "1": + temp1 = str(answer['ext_temperature']['1']['tC']) + else: + temp1 = str(answer['temperature:101']['tC']) +except Exception: + pass +try: + if gen == "1": + temp2 = str(answer['ext_temperature']['2']['tC']) + else: + temp2 = str(answer['temperature:102']['tC']) +except Exception: + pass answer = '{"power":' + str(aktpower) + ',"powerc":' + str(powerc) -answer += ',"on":' + str(relais) + ',"temp0":' + str(temp[0]) -answer += ',"temp1":' + str(temp[1]) + ',"temp2":' + str(temp[2]) + '}' +answer += ',"on":' + str(relais) + ',"temp0":' + str(temp0) +answer += ',"temp1":' + str(temp1) + ',"temp2":' + str(temp2) + '}' writeret(answer, devicenumber)