From f3f7b6c0a970357c021d4c47414e47eb60e2e914 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 10 Mar 2016 20:33:08 +0100 Subject: [PATCH 001/107] merge restpost and restget into one function --- gigasetelements/gigasetelements.py | 94 +++++++++++++----------------- 1 file changed, 40 insertions(+), 54 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 372e9cf..d9051d3 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -93,6 +93,9 @@ s = requests.Session() s.mount("http://", requests.adapters.HTTPAdapter(max_retries=3)) s.mount("https://", requests.adapters.HTTPAdapter(max_retries=3)) + POST = s.post + GET = s.get + HEAD = s.head URL_IDENTITY = 'https://im.gigaset-elements.de/identity/api/v1/user/login' @@ -112,6 +115,9 @@ AUTH_EXPIRE = 21540 +AGENT = {'user-agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'} +CONTENT = {'content-type': 'application/json; charset=UTF-8'} + class bcolors: """Define color classes.""" @@ -243,14 +249,13 @@ def configure(): return -def restget(url, head=0, seconds=90, end=1): - """REST interaction using GET or HEAD.""" +def rest(method, url, payload=None, header=AGENT, timeout=90, end=1): + """REST interaction using requests module.""" try: - if head == 1: - header = {'user-agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'} - r = s.head(url, timeout=seconds, headers=header, allow_redirects=True, verify=pem) + if method == POST: + r = method(url, timeout=timeout, data=payload, headers=header, allow_redirects=True, verify=pem) else: - r = s.get(url, timeout=seconds, stream=False, verify=pem) + r = method(url, timeout=timeout, headers=header, allow_redirects=True, verify=pem) except requests.exceptions.RequestException as e: log('ERROR'.ljust(17) + ' | ' + 'UNKNOWN'.ljust(8) + ' | ' + str(time.strftime('%m/%d/%y %H:%M:%S')) + ' ' + str(e.message), 3, end) if r.status_code != requests.codes.ok: @@ -262,24 +267,6 @@ def restget(url, head=0, seconds=90, end=1): return data -def restpost(url, payload, head=None): - """REST interaction using POST.""" - try: - if head is not None: - r = s.post(url, data=payload, timeout=90, stream=False, headers=head, verify=pem) - else: - r = s.post(url, data=payload, timeout=90, stream=False, verify=pem) - except requests.exceptions.RequestException as e: - log('ERROR'.ljust(17) + ' | ' + 'UNKNOWN'.ljust(8) + ' | ' + str(time.strftime('%m/%d/%y %H:%M:%S')) + ' ' + str(e.message), 3, 1) - if r.status_code != requests.codes.ok: - log('HTTP ERROR'.ljust(17) + ' | ' + str(r.status_code).ljust(8) + ' | ' + str(time.strftime('%m/%d/%y %H:%M:%S')), 3, 1) - try: - commit_data = r.json() - except ValueError: - commit_data = r.text - return commit_data - - def connect(): """Gigaset Elements API authentication and status retrieval.""" global basestation_data @@ -292,16 +279,16 @@ def connect(): except Exception: pass payload = {'password': args.password, 'email': args.username} - commit_data = restpost(URL_IDENTITY, payload) + commit_data = rest(POST, URL_IDENTITY, payload) log('Identity'.ljust(17) + ' | ' + color('verified') + ' | ' + commit_data['message']) auth_time = time.time() - restget(URL_AUTH) + rest(GET, URL_AUTH) log('Authentication'.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') - restget(URL_USAGE, 1, 3, 0) - basestation_data = restget(URL_BASE) + rest(HEAD, URL_USAGE, None, AGENT, 3, 0) + basestation_data = rest(GET, URL_BASE) log('Basestation'.ljust(17) + ' | ' + color(basestation_data[0]['status'].ljust(8)) + ' | ' + basestation_data[0]['id']) - camera_data = restget(URL_CAMERA) - status_data = restget(URL_HEALTH) + camera_data = rest(GET, URL_CAMERA) + status_data = rest(GET, URL_HEALTH) if status_data['system_health'] == 'green': status_data['status_msg_id'] = '' else: @@ -345,7 +332,7 @@ def collect_hw(): def modus_switch(): """Switch alarm modus.""" switch = {'intrusion_settings': {'active_mode': args.modus}} - restpost(URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) + rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) log('Status'.ljust(17) + ' | ' + color(status_data['system_health'].ljust(8)) + status_data['status_msg_id'].upper() + ' | Modus set from ' + color(basestation_data[0]['intrusion_settings']['active_mode']) + ' to ' + color(args.modus)) return @@ -362,7 +349,7 @@ def set_delay(): sinfo = 'normal' linfo = 'No delay' switch = {"intrusion_settings": {"modes": [{"away": {"trigger_delay": delay}}]}} - restpost(URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) + rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) log('Alarm timer'.ljust(17) + ' | ' + color((sinfo).ljust(8)) + ' | ' + linfo) return @@ -375,11 +362,11 @@ def siren(): if args.siren == 'disarm': for m in modus: switch = {"intrusion_settings": {"modes": [{m: {"sirens_on": False}}]}} - restpost(URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) + rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) else: for m in modus: switch = {"intrusion_settings": {"modes": [{m: {"sirens_on": True}}]}} - restpost(URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) + rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) log('Siren'.ljust(17) + ' | ' + color((args.siren + 'ED').ljust(8)) + ' | ') return @@ -389,8 +376,7 @@ def plug(): if not sensor_exist['smart_plug']: log('Plug'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) switch = {"name": args.plug} - header = {'content-type': 'application/json; charset=UTF-8'} - restpost(URL_BASE + '/' + basestation_data[0]['id'] + '/endnodes/' + sensor_id['sp01'][0] + '/cmd', json.dumps(switch), header) + rest(POST, URL_BASE + '/' + basestation_data[0]['id'] + '/endnodes/' + sensor_id['sp01'][0] + '/cmd', json.dumps(switch), CONTENT) log('Plug'.ljust(17) + ' | ' + color(args.plug.ljust(8)) + ' | ') return @@ -471,10 +457,10 @@ def list_events(): """List past events optionally filtered by date and/or type.""" if args.filter is None and args.date is None: log('Event(s)'.ljust(17) + ' | ' + str(args.events).ljust(8) + ' | ' + 'No filter') - event_data = restget(URL_EVENTS + '?limit=' + str(args.events)) + event_data = rest(GET, URL_EVENTS + '?limit=' + str(args.events)) if args.filter is not None and args.date is None: log('Event(s)'.ljust(17) + ' | ' + str(args.events).ljust(8) + ' | ' + args.filter.title()) - event_data = restget(URL_EVENTS + '?limit=' + str(args.events) + '&group=' + str(args.filter)) + event_data = rest(GET, URL_EVENTS + '?limit=' + str(args.events) + '&group=' + str(args.filter)) if args.date is not None: try: from_ts = str(int(time.mktime(time.strptime(args.date[0], '%d/%m/%Y'))) * 1000) @@ -483,10 +469,10 @@ def list_events(): log('Event(s)'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | ' + 'Date(s) filter not in DD/MM/YYYY format', 3, 1) if args.filter is None and args.date is not None: log('Event(s)'.ljust(17) + ' | ' + 'DATE'.ljust(8) + ' | ' + args.date[0] + ' - ' + args.date[1]) - event_data = restget(URL_EVENTS + '?from_ts=' + from_ts + '&to_ts=' + to_ts + '&limit=999') + event_data = rest(GET, URL_EVENTS + '?from_ts=' + from_ts + '&to_ts=' + to_ts + '&limit=999') if args.filter is not None and args.date is not None: log('Event(s)'.ljust(17) + ' | ' + '*'.ljust(8) + ' | ' + args.filter.title() + ' | ' + args.date[0] + ' - ' + args.date[1]) - event_data = restget(URL_EVENTS + '?from_ts=' + from_ts + '&to_ts=' + to_ts + '&group=' + str(args.filter) + '&limit=999') + event_data = rest(GET, URL_EVENTS + '?from_ts=' + from_ts + '&to_ts=' + to_ts + '&group=' + str(args.filter) + '&limit=999') for item in event_data['events']: try: if 'type' in item['o']: @@ -508,7 +494,7 @@ def monitor(): if args.monitor > 1: mode = 'Domoticz mode' print - restget(url_domo + URL_LOG + 'Gigaset Elements - Command-line Interface: Domoticz mode started') + rest(GET, url_domo + URL_LOG + 'Gigaset Elements - Command-line Interface: Domoticz mode started') domoticz(status_data['system_health'].lower(), basestation_data[0]['id'].lower(), basestation_data[0]['friendly_name'].lower()) else: mode = 'Monitor mode' @@ -516,7 +502,7 @@ def monitor(): from_ts = str(int(time.time())*1000) try: while 1: - lastevents = restget(url_monitor + '&from_ts=' + from_ts) + lastevents = rest(GET, url_monitor + '&from_ts=' + from_ts) for item in reversed(lastevents['events']): try: if 'type' in item['o']: @@ -536,12 +522,12 @@ def monitor(): continue if time.time() - auth_time >= AUTH_EXPIRE: auth_time = time.time() - restget(URL_AUTH) + rest(GET, URL_AUTH) else: time.sleep(1) except KeyboardInterrupt: if args.monitor > 1: - restget(url_domo + URL_LOG + 'Gigaset Elements - Command-line Interface: Domoticz mode halted') + rest(GET, url_domo + URL_LOG + 'Gigaset Elements - Command-line Interface: Domoticz mode halted') log('Program'.ljust(17) + ' | ' + color('halted'.ljust(8)) + ' | ' + 'CTRL+C') return @@ -554,11 +540,11 @@ def domoticz(event, sid, friendly): cmd = 'off' else: cmd = 'on' - restget(url_domo + URL_SWITCH + cmd.title() + '&idx=' + dconfig[sid]) + rest(GET, url_domo + URL_SWITCH + cmd.title() + '&idx=' + dconfig[sid]) else: - status_data = restget(URL_HEALTH) - restget(url_domo + URL_ALERT + dconfig[basestation_data[0]['id'].lower()] + '&nvalue=' + - LEVEL.get(status_data['system_health'], '3') + '&svalue=' + friendly + ' | ' + event) + status_data = rest(GET, URL_HEALTH) + rest(GET, url_domo + URL_ALERT + dconfig[basestation_data[0]['id'].lower()] + '&nvalue=' + + LEVEL.get(status_data['system_health'], '3') + '&svalue=' + friendly + ' | ' + event) sys.stdout.write("\033[F") sys.stdout.write("\033[K") return @@ -586,7 +572,7 @@ def sensor(): def rules(): """List custom rule(s).""" - rules = restget(URL_BASE + '/' + basestation_data[0]['id'] + '/rules?rules=custom') + rules = rest(GET, URL_BASE + '/' + basestation_data[0]['id'] + '/rules?rules=custom') for item in rules: try: if item['active']: @@ -611,7 +597,7 @@ def rules(): def notifications(): """List notification settings per mobile device.""" - channels = restget(URL_CHANNEL) + channels = rest(GET, URL_CHANNEL) for item in channels.get('gcm', ''): try: print('[-] ' + item['friendlyName'].ljust(17) + ' | ' + color(item['status'].ljust(8)) + ' |'), @@ -637,7 +623,7 @@ def camera_info(): print '| ssid ' + bcolors.OKGREEN + str(camera_data[0]['wifi_ssid']).upper() + bcolors.ENDC except KeyError: print - stream_data = restget(URL_CAMERA + '/' + camera_data[0]['id'] + '/liveview/start') + stream_data = rest(GET, URL_CAMERA + '/' + camera_data[0]['id'] + '/liveview/start') for stream in ('m3u8', 'rtmp', 'rtsp'): log('Camera stream'.ljust(17) + ' | ' + stream.upper().ljust(8) + ' | ' + stream_data['uri'][stream]) return @@ -647,12 +633,12 @@ def record(): """Start or stop camera recording based on current state.""" if not sensor_exist['camera']: log('Camera'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) - camera_status = restget(URL_CAMERA + '/' + str(camera_data[0]['id']) + '/recording/status') + camera_status = rest(GET, URL_CAMERA + '/' + str(camera_data[0]['id']) + '/recording/status') if camera_status['description'] == 'Recording not started': - restget(URL_CAMERA + '/' + str(camera_data[0]['id']) + '/recording/start') + rest(GET, URL_CAMERA + '/' + str(camera_data[0]['id']) + '/recording/start') log('Camera recording'.ljust(17) + ' | ' + color('started'.ljust(8)) + ' | ') if camera_status['description'] == 'Recording already started': - restget(URL_CAMERA + '/' + str(camera_data[0]['id']) + '/recording/stop') + rest(GET, URL_CAMERA + '/' + str(camera_data[0]['id']) + '/recording/stop') log('Camera recording'.ljust(17) + ' | ' + color('stopped'.ljust(8)) + ' | ') return From b8db7aa513f9bb5b83795a00c8ebc5b01c36fe88 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 14 Mar 2016 12:01:43 +0100 Subject: [PATCH 002/107] add default app config location Windows --- gigasetelements/gigasetelements.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index d9051d3..e461965 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -62,6 +62,9 @@ colorama.init() args.cronjob = None args.remove = False + ntconfig = os.path.join(os.environ['APPDATA'], 'gigasetelements-cli\gigasetelements-cli.conf') +else: + ntconfig = '' if args.daemon and os.name != 'nt': from daemonize import Daemonize @@ -199,7 +202,7 @@ def configure(): pem = True if args.config is None: locations = ['/opt/etc/gigasetelements-cli.conf', '/usr/local/etc/gigasetelements-cli.conf', '/usr/etc/gigasetelements-cli.conf', - '/etc/gigasetelements-cli.conf', os.path.expanduser('~/.gigasetelements-cli/gigasetelements-cli.conf'), + '/etc/gigasetelements-cli.conf', os.path.expanduser('~/.gigasetelements-cli/gigasetelements-cli.conf'), ntconfig, os.path.expanduser('~/Library/Application Support/gigasetelements-cli/gigasetelements-cli.conf')] for i in locations: if os.path.exists(i): From 45c51ffcb68801f942bce3c90f341d989af7a505 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 15 Mar 2016 11:21:22 +0100 Subject: [PATCH 003/107] notify in case of new release on github --- gigasetelements-cli.conf.template | 3 +++ gigasetelements/gigasetelements.py | 24 +++++++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/gigasetelements-cli.conf.template b/gigasetelements-cli.conf.template index d4a0cdc..111c8bf 100644 --- a/gigasetelements-cli.conf.template +++ b/gigasetelements-cli.conf.template @@ -29,6 +29,9 @@ modus: home # suppress urllib3 warnings {true,false} (optional) nowarning: true +# disable periodical check for updates {true,false} (optional) +noupdate: false + [domoticz] # domoticz server ip address (required) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index e461965..8f661e2 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -9,6 +9,7 @@ import os import sys import time +import random import datetime import argparse import json @@ -44,6 +45,7 @@ parser.add_argument('-r', '--record', help='switch camera recording on/off', action='store_true', required=False) parser.add_argument('-t', '--monitor', help='show events using monitor mode (use -tt to activate domoticz mode)', action='count', default=0, required=False) parser.add_argument('-i', '--ignore', help='ignore configuration-file at predefined locations', action='store_true', required=False) +parser.add_argument('-N', '--noupdate', help='do not periodically check for updates', action='store_true', required=False) parser.add_argument('-j', '--restart', help='automatically restart program in case of a connection error', action='store_true', required=False) parser.add_argument('-q', '--quiet', help='do not send pushbullet message', action='store_true', required=False) parser.add_argument('-I', '--insecure', help='disable SSL/TLS certificate verification', action='store_true', required=False) @@ -54,7 +56,7 @@ args = parser.parse_args() print -print 'Gigaset Elements - Command-line Interface' +print 'Gigaset Elements - Command-line Interface v' + _VERSION_ print if os.name == 'nt': @@ -99,7 +101,8 @@ POST = s.post GET = s.get HEAD = s.head - +else: + args.noupdate = True URL_IDENTITY = 'https://im.gigaset-elements.de/identity/api/v1/user/login' URL_AUTH = 'https://api.gigaset-elements.de/api/v1/auth/openid/begin?op=gigaset' @@ -108,6 +111,7 @@ URL_CAMERA = 'https://api.gigaset-elements.de/api/v1/me/cameras' URL_HEALTH = 'https://api.gigaset-elements.de/api/v2/me/health' URL_CHANNEL = 'https://api.gigaset-elements.de/api/v1/me/notifications/users/channels' +URL_RELEASE = 'https://github.com/gitapi/repos/dynasticorpheus/gigasetelements-cli/releases/latest' URL_USAGE = 'https://goo.gl/wjLswA' URL_SWITCH = '/json.htm?type=command¶m=switchlight&switchcmd=' @@ -171,7 +175,7 @@ def color(txt): """Add color to string based on presence in list and return in uppercase.""" green = ['ok', 'online', 'closed', 'up_to_date', 'home', 'auto', 'on', 'hd', 'cable', 'normal', 'daemon', 'wifi', 'started', 'active', 'green', 'armed', 'pushed', 'verified', 'loaded', 'success'] - orange = ['orange', 'warning'] + orange = ['orange', 'warning', 'update'] if args.log is not None: txt = txt.upper() else: @@ -247,6 +251,8 @@ def configure(): else: if config.getboolean('options', 'nowarning'): args.warning = True + if config.getboolean('options', 'noupdate'): + args.noupdate = True if None in (args.username, args.password): log('Configuration'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Username and/or password missing', 3, 1) return @@ -302,6 +308,15 @@ def connect(): return +def check_version(): + from distutils.version import LooseVersion, StrictVersion + remotedata = rest(GET, URL_RELEASE, None, AGENT, 3, 0) + remoteversion = str(remotedata['tag_name']) + if LooseVersion(_VERSION_) < LooseVersion(remoteversion[1:]): + log('Program'.ljust(17) + ' | ' + color('update'.ljust(8)) + ' | Version ' + remoteversion[1:] + ' of gigasetelements-cli is available for download') + return + + def collect_hw(): """Retrieve sensor list and details.""" global sensor_id @@ -654,6 +669,9 @@ def main(): try: configure() + if not args.noupdate and random.randint(1, 10) == 1: + check_version() + if args.cronjob is not None: add_cron() print From fd0aa9a067b8450abbb8d1316d0b0e90b0e2a31f Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 15 Mar 2016 11:51:59 +0100 Subject: [PATCH 004/107] check for new version on pypi instead of github --- gigasetelements/gigasetelements.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 8f661e2..91ed389 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -111,7 +111,7 @@ URL_CAMERA = 'https://api.gigaset-elements.de/api/v1/me/cameras' URL_HEALTH = 'https://api.gigaset-elements.de/api/v2/me/health' URL_CHANNEL = 'https://api.gigaset-elements.de/api/v1/me/notifications/users/channels' -URL_RELEASE = 'https://github.com/gitapi/repos/dynasticorpheus/gigasetelements-cli/releases/latest' +URL_RELEASE = 'https://pypi.python.org/pypi/gigasetelements-cli/json' URL_USAGE = 'https://goo.gl/wjLswA' URL_SWITCH = '/json.htm?type=command¶m=switchlight&switchcmd=' @@ -311,9 +311,10 @@ def connect(): def check_version(): from distutils.version import LooseVersion, StrictVersion remotedata = rest(GET, URL_RELEASE, None, AGENT, 3, 0) - remoteversion = str(remotedata['tag_name']) - if LooseVersion(_VERSION_) < LooseVersion(remoteversion[1:]): - log('Program'.ljust(17) + ' | ' + color('update'.ljust(8)) + ' | Version ' + remoteversion[1:] + ' of gigasetelements-cli is available for download') + remoteversion = str(remotedata['info']['version']) + if LooseVersion(_VERSION_) < LooseVersion(remoteversion): + log('Program'.ljust(17) + ' | ' + color('update'.ljust(8)) + ' | Version ' + remoteversion + + ' is available. Run pip install --upgrade gigasetelements-cli') return From 58e58051b9d330639141e20e6aa36d42d29daace Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 17 Mar 2016 13:25:51 +0100 Subject: [PATCH 005/107] refactor conf file loading and make less error prone --- gigasetelements-cli.conf.template | 47 +++++++----- gigasetelements/gigasetelements.py | 119 ++++++++++++++--------------- 2 files changed, 88 insertions(+), 78 deletions(-) diff --git a/gigasetelements-cli.conf.template b/gigasetelements-cli.conf.template index 111c8bf..6ad1690 100644 --- a/gigasetelements-cli.conf.template +++ b/gigasetelements-cli.conf.template @@ -2,49 +2,62 @@ # # Note: command-line options override below values # -# On POSIX configuration file is automatically read from below locations: -# ~/.gigasetelements-cli +# The configuration file is automatically read from below locations: +# +# POSIX +# ~/.gigasetelements-cli/gigasetelements-cli.conf # /etc/gigasetelements-cli.conf # /usr/etc/gigasetelements-cli.conf # /usr/local/etc/gigasetelements-cli.conf # /opt/etc/gigasetelements-cli.conf +# +# OS X +# ~/Library/Application Support/gigasetelements-cli/gigasetelements-cli.conf +# +# WINDOWS +# %APPDATA%\gigasetelements-cli\gigasetelements-cli.conf + [accounts] # username (email) in use with my.gigaset-elements.com -username: first.last@domain.com +username=first.last@domain.com # password in use with my.gigaset-elements.com -password: mybigsecret +password=mybigsecret -# add token to enable pushbullet notification (optional) -pbtoken: z9FaKeSCKQDi2cmPUSHB62aiXx5I57eiujTOKENfS34 +# access token to enable pushbullet notifications (optional) +# pbtoken=z9FaKeSCKQDi2cmPUSHB62aiXx5I57eiujTOKENfS34 [options] # set modus {home,away,custom} (optional) -modus: home +# modus=home + +# disable SSL/TLS certificate verification {true,false} (optional) +# insecure=false # suppress urllib3 warnings {true,false} (optional) -nowarning: true +# silent=false + +# Don't periodically check PyPI to determine whether a new version of gigasetelements-cli is available for download {true,false} (optional) +# noupdate= false -# disable periodical check for updates {true,false} (optional) -noupdate: false [domoticz] # domoticz server ip address (required) -ip: 127.0.0.1 +ip=127.0.0.1 # domoticz server port (required) -port: 8080 +port=8080 # domoticz user name (optional) -username: +username= # domoticz password (optional) -password: +password= # domoticz idx sensor pairing (required) @@ -57,10 +70,10 @@ password: # 7. Run "gigasetelements-cli -a" to get camera sensor id. Use the 12 uppercase digits in stream url. This MAC address can also be found on the back of the camera. # example motion/door/window/siren/plug -# 123FC4577H: 99 +# 123FC4577H=99 # example basestation id -# F32A76C4DHJ1B743A0E0D74EFD2375D1: 98 +# F32A76C4DHJ1B743A0E0D74EFD2375D1=98 # example camera id -# 7C2G30873SED: 97 +# 7C2G30873SED=97 diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 91ed389..22cbba6 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -20,6 +20,27 @@ _AUTHOR_ = 'dynasticorpheus@gmail.com' _VERSION_ = '1.4.0' +LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4'} +OPTDEF = {'username': None, 'password': None, 'modus': None, 'pbtoken': None, 'silent': 'False', 'noupdate': 'False', 'insecure': 'False'} +AGENT = {'user-agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'} +CONTENT = {'content-type': 'application/json; charset=UTF-8'} +AUTH_EXPIRE = 21540 + +URL_IDENTITY = 'https://im.gigaset-elements.de/identity/api/v1/user/login' +URL_AUTH = 'https://api.gigaset-elements.de/api/v1/auth/openid/begin?op=gigaset' +URL_EVENTS = 'https://api.gigaset-elements.de/api/v2/me/events' +URL_BASE = 'https://api.gigaset-elements.de/api/v1/me/basestations' +URL_CAMERA = 'https://api.gigaset-elements.de/api/v1/me/cameras' +URL_HEALTH = 'https://api.gigaset-elements.de/api/v2/me/health' +URL_CHANNEL = 'https://api.gigaset-elements.de/api/v1/me/notifications/users/channels' +URL_RELEASE = 'https://pypi.python.org/pypi/gigasetelements-cli/json' +URL_USAGE = 'https://goo.gl/wjLswA' + +URL_SWITCH = '/json.htm?type=command¶m=switchlight&switchcmd=' +URL_ALERT = '/json.htm?type=command¶m=udevice&idx=' +URL_LOG = '/json.htm?type=command¶m=addlogmessage&message=' + + parser = argparse.ArgumentParser(description='Gigaset Elements - Command-line Interface by dynasticorpheus@gmail.com') parser.add_argument('-c', '--config', help='fully qualified name of configuration-file', required=False) parser.add_argument('-u', '--username', help='username (email) in use with my.gigaset-elements.com', required=False) @@ -49,11 +70,12 @@ parser.add_argument('-j', '--restart', help='automatically restart program in case of a connection error', action='store_true', required=False) parser.add_argument('-q', '--quiet', help='do not send pushbullet message', action='store_true', required=False) parser.add_argument('-I', '--insecure', help='disable SSL/TLS certificate verification', action='store_true', required=False) -parser.add_argument('-w', '--warning', help='suppress urllib3 warnings', action='store_true', required=False) +parser.add_argument('-S', '--silent', help='suppress urllib3 warnings', action='store_true', required=False) parser.add_argument('-v', '--version', help='show version', action='version', version='%(prog)s version ' + str(_VERSION_)) gc.disable() args = parser.parse_args() +config = ConfigParser.ConfigParser(defaults=OPTDEF) print print 'Gigaset Elements - Command-line Interface v' + _VERSION_ @@ -104,27 +126,6 @@ else: args.noupdate = True -URL_IDENTITY = 'https://im.gigaset-elements.de/identity/api/v1/user/login' -URL_AUTH = 'https://api.gigaset-elements.de/api/v1/auth/openid/begin?op=gigaset' -URL_EVENTS = 'https://api.gigaset-elements.de/api/v2/me/events' -URL_BASE = 'https://api.gigaset-elements.de/api/v1/me/basestations' -URL_CAMERA = 'https://api.gigaset-elements.de/api/v1/me/cameras' -URL_HEALTH = 'https://api.gigaset-elements.de/api/v2/me/health' -URL_CHANNEL = 'https://api.gigaset-elements.de/api/v1/me/notifications/users/channels' -URL_RELEASE = 'https://pypi.python.org/pypi/gigasetelements-cli/json' -URL_USAGE = 'https://goo.gl/wjLswA' - -URL_SWITCH = '/json.htm?type=command¶m=switchlight&switchcmd=' -URL_ALERT = '/json.htm?type=command¶m=udevice&idx=' -URL_LOG = '/json.htm?type=command¶m=addlogmessage&message=' - -LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4'} - -AUTH_EXPIRE = 21540 - -AGENT = {'user-agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'} -CONTENT = {'content-type': 'application/json; charset=UTF-8'} - class bcolors: """Define color classes.""" @@ -188,6 +189,21 @@ def color(txt): return txt +def load_option(arg, section, option): + fromfile = False + if arg is None: + arg = config.get(section, option) + if arg == '' or arg is None: + arg = None + else: + fromfile = True + elif isinstance(arg, bool): + if config.getboolean(section, option): + arg = True + fromfile = True + return arg, fromfile + + def configure(): """Load variables based on command line arguments and config file.""" global dconfig @@ -196,14 +212,6 @@ def configure(): global pem credfromfile = False authstring = '' - if args.insecure: - pem = False - else: - try: - import certifi - pem = certifi.old_where() - except Exception: - pem = True if args.config is None: locations = ['/opt/etc/gigasetelements-cli.conf', '/usr/local/etc/gigasetelements-cli.conf', '/usr/etc/gigasetelements-cli.conf', '/etc/gigasetelements-cli.conf', os.path.expanduser('~/.gigasetelements-cli/gigasetelements-cli.conf'), ntconfig, @@ -217,7 +225,6 @@ def configure(): if not os.path.exists(args.config): log('Configuration'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | File does not exist ' + args.config, 3, 1) if args.config is not None: - config = ConfigParser.ConfigParser() config.read(args.config) if args.monitor > 1: try: @@ -228,33 +235,28 @@ def configure(): except Exception: log('Configuration'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Domoticz setting(s) incorrect and/or missing', 3, 1) log('Configuration'.ljust(17) + ' | ' + color('loaded'.ljust(8)) + ' | ' + args.config) - if args.username is None: - args.username = config.get('accounts', 'username') - credfromfile = True - if args.username == '': - args.username = None - credfromfile = False - if args.password is None: - args.password = config.get('accounts', 'password') - credfromfile = True - if args.password == '': - args.password = None - credfromfile = False - if args.modus is None: - args.modus = config.get('options', 'modus') - if args.modus == '': - args.modus = None - if args.notify is None: - args.notify = config.get('accounts', 'pbtoken') - if args.notify == '': - args.notify = None - else: - if config.getboolean('options', 'nowarning'): - args.warning = True - if config.getboolean('options', 'noupdate'): - args.noupdate = True + args.noupdate, credfromfile = load_option(args.noupdate, 'options', 'noupdate') + args.silent, credfromfile = load_option(args.silent, 'options', 'silent') + args.insecure, credfromfile = load_option(args.insecure, 'options', 'insecure') + args.modus, credfromfile = load_option(args.modus, 'options', 'modus') + args.notify, credfromfile = load_option(args.notify, 'accounts', 'pbtoken') + args.username, credfromfile = load_option(args.username, 'accounts', 'username') + args.password, credfromfile = load_option(args.password, 'accounts', 'password') if None in (args.username, args.password): log('Configuration'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Username and/or password missing', 3, 1) + if args.silent: + try: + requests.packages.urllib3.disable_warnings() + except Exception: + pass + if args.insecure: + pem = False + else: + try: + import certifi + pem = certifi.old_where() + except Exception: + pem = True return @@ -282,11 +284,6 @@ def connect(): global status_data global camera_data global auth_time - if args.warning: - try: - requests.packages.urllib3.disable_warnings() - except Exception: - pass payload = {'password': args.password, 'email': args.username} commit_data = rest(POST, URL_IDENTITY, payload) log('Identity'.ljust(17) + ' | ' + color('verified') + ' | ' + commit_data['message']) From 360de0d006f689d282c1a481b2f744bccd72320e Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 18 Mar 2016 08:08:21 +0100 Subject: [PATCH 006/107] add silent option to rest function and better handle connection issues --- gigasetelements/gigasetelements.py | 35 +++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 22cbba6..6e68f8d 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -260,22 +260,26 @@ def configure(): return -def rest(method, url, payload=None, header=AGENT, timeout=90, end=1): +def rest(method, url, payload=None, header=AGENT, timeout=90, end=1, silent=False): """REST interaction using requests module.""" + r = None try: if method == POST: r = method(url, timeout=timeout, data=payload, headers=header, allow_redirects=True, verify=pem) else: r = method(url, timeout=timeout, headers=header, allow_redirects=True, verify=pem) except requests.exceptions.RequestException as e: - log('ERROR'.ljust(17) + ' | ' + 'UNKNOWN'.ljust(8) + ' | ' + str(time.strftime('%m/%d/%y %H:%M:%S')) + ' ' + str(e.message), 3, end) - if r.status_code != requests.codes.ok: - log('HTTP ERROR'.ljust(17) + ' | ' + str(r.status_code).ljust(8) + ' | ' + str(time.strftime('%m/%d/%y %H:%M:%S')), 3, end) - try: - data = r.json() - except ValueError: - data = r.text - return data + if not silent: + log('ERROR'.ljust(17) + ' | ' + 'UNKNOWN'.ljust(8) + ' | ' + str(time.strftime('%m/%d/%y %H:%M:%S')) + ' ' + str(e.message), 3, end) + if r is not None: + if not silent: + if r.status_code != requests.codes.ok: + log('HTTP ERROR'.ljust(17) + ' | ' + str(r.status_code).ljust(8) + ' | ' + str(time.strftime('%m/%d/%y %H:%M:%S')), 3, end) + try: + data = r.json() + except ValueError: + data = r.text + return data def connect(): @@ -290,7 +294,7 @@ def connect(): auth_time = time.time() rest(GET, URL_AUTH) log('Authentication'.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') - rest(HEAD, URL_USAGE, None, AGENT, 3, 0) + rest(HEAD, URL_USAGE, None, AGENT, 2, 0, True) basestation_data = rest(GET, URL_BASE) log('Basestation'.ljust(17) + ' | ' + color(basestation_data[0]['status'].ljust(8)) + ' | ' + basestation_data[0]['id']) camera_data = rest(GET, URL_CAMERA) @@ -307,11 +311,12 @@ def connect(): def check_version(): from distutils.version import LooseVersion, StrictVersion - remotedata = rest(GET, URL_RELEASE, None, AGENT, 3, 0) - remoteversion = str(remotedata['info']['version']) - if LooseVersion(_VERSION_) < LooseVersion(remoteversion): - log('Program'.ljust(17) + ' | ' + color('update'.ljust(8)) + ' | Version ' + remoteversion + - ' is available. Run pip install --upgrade gigasetelements-cli') + remotedata = rest(GET, URL_RELEASE, None, AGENT, 2, 0, True) + if remotedata is not None: + remoteversion = str(remotedata['info']['version']) + if LooseVersion(_VERSION_) < LooseVersion(remoteversion): + log('Program'.ljust(17) + ' | ' + color('update'.ljust(8)) + ' | Version ' + remoteversion + + ' is available. Run pip install --upgrade gigasetelements-cli') return From d60aaa8f21eb96ac8a857d50ccab72bf0a50e487 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 18 Mar 2016 12:39:00 +0100 Subject: [PATCH 007/107] add http error code reason --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 6e68f8d..9d94ca4 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -274,7 +274,7 @@ def rest(method, url, payload=None, header=AGENT, timeout=90, end=1, silent=Fals if r is not None: if not silent: if r.status_code != requests.codes.ok: - log('HTTP ERROR'.ljust(17) + ' | ' + str(r.status_code).ljust(8) + ' | ' + str(time.strftime('%m/%d/%y %H:%M:%S')), 3, end) + log('HTTP ERROR'.ljust(17) + ' | ' + str(r.status_code).ljust(8) + ' | ' + r.reason + ' ' + str(time.strftime('%m/%d/%y %H:%M:%S')), 3, end) try: data = r.json() except ValueError: From e3a0f3d60ee28ffc5e9f808f1bdcc2d9f5116901 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Wed, 6 Apr 2016 14:32:32 +0200 Subject: [PATCH 008/107] authenticate every 4 hours and provide url path in case of error --- gigasetelements/gigasetelements.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 9d94ca4..d826421 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -11,6 +11,7 @@ import time import random import datetime +import urlparse import argparse import json import ConfigParser @@ -24,7 +25,7 @@ OPTDEF = {'username': None, 'password': None, 'modus': None, 'pbtoken': None, 'silent': 'False', 'noupdate': 'False', 'insecure': 'False'} AGENT = {'user-agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'} CONTENT = {'content-type': 'application/json; charset=UTF-8'} -AUTH_EXPIRE = 21540 +AUTH_EXPIRE = 14400 URL_IDENTITY = 'https://im.gigaset-elements.de/identity/api/v1/user/login' URL_AUTH = 'https://api.gigaset-elements.de/api/v1/auth/openid/begin?op=gigaset' @@ -270,11 +271,12 @@ def rest(method, url, payload=None, header=AGENT, timeout=90, end=1, silent=Fals r = method(url, timeout=timeout, headers=header, allow_redirects=True, verify=pem) except requests.exceptions.RequestException as e: if not silent: - log('ERROR'.ljust(17) + ' | ' + 'UNKNOWN'.ljust(8) + ' | ' + str(time.strftime('%m/%d/%y %H:%M:%S')) + ' ' + str(e.message), 3, end) + log('ERROR'.ljust(17) + ' | ' + 'UNKNOWN'.ljust(8) + ' | ' + str(e.message), 3, end) if r is not None: if not silent: if r.status_code != requests.codes.ok: - log('HTTP ERROR'.ljust(17) + ' | ' + str(r.status_code).ljust(8) + ' | ' + r.reason + ' ' + str(time.strftime('%m/%d/%y %H:%M:%S')), 3, end) + u = urlparse.urlparse(r.url) + log('HTTP ERROR'.ljust(17) + ' | ' + str(r.status_code).ljust(8) + ' | ' + r.reason + ' ' + str(u.path), 3, end) try: data = r.json() except ValueError: @@ -544,6 +546,7 @@ def monitor(): if time.time() - auth_time >= AUTH_EXPIRE: auth_time = time.time() rest(GET, URL_AUTH) + log('Re-authentication'.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') else: time.sleep(1) except KeyboardInterrupt: From ca014c9b11bb9be4a53dfa092b2d7ab40313e9cd Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 7 Apr 2016 19:43:02 +0200 Subject: [PATCH 009/107] code cleanup - pylint --- gigasetelements/gigasetelements.py | 98 ++++++++++++++---------------- 1 file changed, 47 insertions(+), 51 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index d826421..db48494 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -15,16 +15,20 @@ import argparse import json import ConfigParser +import logging import unidecode _AUTHOR_ = 'dynasticorpheus@gmail.com' _VERSION_ = '1.4.0' +OKGREEN = '\033[92m' +WARN = '\033[93m' +FAIL = '\033[91m' +ENDC = '\033[0m' + LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4'} OPTDEF = {'username': None, 'password': None, 'modus': None, 'pbtoken': None, 'silent': 'False', 'noupdate': 'False', 'insecure': 'False'} -AGENT = {'user-agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'} -CONTENT = {'content-type': 'application/json; charset=UTF-8'} AUTH_EXPIRE = 14400 URL_IDENTITY = 'https://im.gigaset-elements.de/identity/api/v1/user/login' @@ -41,7 +45,6 @@ URL_ALERT = '/json.htm?type=command¶m=udevice&idx=' URL_LOG = '/json.htm?type=command¶m=addlogmessage&message=' - parser = argparse.ArgumentParser(description='Gigaset Elements - Command-line Interface by dynasticorpheus@gmail.com') parser.add_argument('-c', '--config', help='fully qualified name of configuration-file', required=False) parser.add_argument('-u', '--username', help='username (email) in use with my.gigaset-elements.com', required=False) @@ -87,7 +90,7 @@ colorama.init() args.cronjob = None args.remove = False - ntconfig = os.path.join(os.environ['APPDATA'], 'gigasetelements-cli\gigasetelements-cli.conf') + ntconfig = os.path.join(os.environ['APPDATA'], os.path.normpath('gigasetelements-cli/gigasetelements-cli.conf')) else: ntconfig = '' @@ -97,19 +100,18 @@ target = open(args.pid, 'w') target.close() except IOError: - print('\033[91m' + '[-] Unable to write pid file ' + args.pid + '\033[0m') + print FAIL + '[-] Unable to write pid file ' + args.pid + ENDC print sys.exit() if args.log is not None: - import logging logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) logger.propagate = False try: fh = logging.FileHandler(args.log, 'a') except IOError: - print('\033[91m' + '[-] Unable to write log file ' + args.log + '\033[0m') + print FAIL + '[-] Unable to write log file ' + args.log + ENDC print sys.exit() fh.setLevel(logging.INFO) @@ -128,18 +130,6 @@ args.noupdate = True -class bcolors: - """Define color classes.""" - HEADER = '\033[95m' - OKBLUE = '\033[94m' - OKGREEN = '\033[92m' - WARN = '\033[93m' - FAIL = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' - - def restart_program(): """Restarts the current program.""" python = sys.executable @@ -154,11 +144,11 @@ def log(logme, rbg=0, exitnow=0, newline=1): if args.log is not None: logger.info('[' + time.strftime("%c") + '] ' + unidecode.unidecode(unicode(logme))) if rbg == 1: - print bcolors.OKGREEN + '[-] ' + logme.encode('utf-8') + bcolors.ENDC + print OKGREEN + '[-] ' + logme.encode('utf-8') + ENDC elif rbg == 2: - print bcolors.WARN + '[-] ' + logme.encode('utf-8') + bcolors.ENDC + print WARN + '[-] ' + logme.encode('utf-8') + ENDC elif rbg == 3: - print bcolors.FAIL + '[-] ' + logme.encode('utf-8') + bcolors.ENDC + print FAIL + '[-] ' + logme.encode('utf-8') + ENDC else: if newline == 1: print '[-] ' + logme.encode('utf-8') @@ -182,15 +172,16 @@ def color(txt): txt = txt.upper() else: if txt.lower().strip() in green: - txt = bcolors.OKGREEN + txt.upper() + bcolors.ENDC + txt = OKGREEN + txt.upper() + ENDC elif txt.lower().strip() in orange: - txt = bcolors.WARN + txt.upper() + bcolors.ENDC + txt = WARN + txt.upper() + ENDC else: - txt = bcolors.FAIL + txt.upper() + bcolors.ENDC + txt = FAIL + txt.upper() + ENDC return txt def load_option(arg, section, option): + """Load options safely from conf file.""" fromfile = False if arg is None: arg = config.get(section, option) @@ -261,26 +252,30 @@ def configure(): return -def rest(method, url, payload=None, header=AGENT, timeout=90, end=1, silent=False): +def rest(method, url, payload=None, header=False, timeout=90, end=1, silent=False): """REST interaction using requests module.""" - r = None + request = None + if header: + header = {'content-type': 'application/json; charset=UTF-8'} + else: + header = {'user-agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'} try: if method == POST: - r = method(url, timeout=timeout, data=payload, headers=header, allow_redirects=True, verify=pem) + request = method(url, timeout=timeout, data=payload, headers=header, allow_redirects=True, verify=pem) else: - r = method(url, timeout=timeout, headers=header, allow_redirects=True, verify=pem) - except requests.exceptions.RequestException as e: + request = method(url, timeout=timeout, headers=header, allow_redirects=True, verify=pem) + except requests.exceptions.RequestException as error: if not silent: - log('ERROR'.ljust(17) + ' | ' + 'UNKNOWN'.ljust(8) + ' | ' + str(e.message), 3, end) - if r is not None: + log('ERROR'.ljust(17) + ' | ' + 'UNKNOWN'.ljust(8) + ' | ' + str(error.message), 3, end) + if request is not None: if not silent: - if r.status_code != requests.codes.ok: - u = urlparse.urlparse(r.url) - log('HTTP ERROR'.ljust(17) + ' | ' + str(r.status_code).ljust(8) + ' | ' + r.reason + ' ' + str(u.path), 3, end) + if request.status_code != requests.codes.ok: + u = urlparse.urlparse(request.url) + log('HTTP ERROR'.ljust(17) + ' | ' + str(request.status_code).ljust(8) + ' | ' + request.reason + ' ' + str(u.path), 3, end) try: - data = r.json() + data = request.json() except ValueError: - data = r.text + data = request.text return data @@ -296,7 +291,7 @@ def connect(): auth_time = time.time() rest(GET, URL_AUTH) log('Authentication'.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') - rest(HEAD, URL_USAGE, None, AGENT, 2, 0, True) + rest(HEAD, URL_USAGE, None, False, 2, 0, True) basestation_data = rest(GET, URL_BASE) log('Basestation'.ljust(17) + ' | ' + color(basestation_data[0]['status'].ljust(8)) + ' | ' + basestation_data[0]['id']) camera_data = rest(GET, URL_CAMERA) @@ -312,8 +307,9 @@ def connect(): def check_version(): + """Check if new version exists on pypi.""" from distutils.version import LooseVersion, StrictVersion - remotedata = rest(GET, URL_RELEASE, None, AGENT, 2, 0, True) + remotedata = rest(GET, URL_RELEASE, None, False, 2, 0, True) if remotedata is not None: remoteversion = str(remotedata['info']['version']) if LooseVersion(_VERSION_) < LooseVersion(remoteversion): @@ -381,14 +377,14 @@ def siren(): """Dis(arm) siren.""" if not sensor_exist['indoor_siren']: log('Siren'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) - modus = ['home', 'away', 'custom'] + moduslist = ['home', 'away', 'custom'] if args.siren == 'disarm': - for m in modus: - switch = {"intrusion_settings": {"modes": [{m: {"sirens_on": False}}]}} + for modus in moduslist: + switch = {"intrusion_settings": {"modes": [{modus: {"sirens_on": False}}]}} rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) else: - for m in modus: - switch = {"intrusion_settings": {"modes": [{m: {"sirens_on": True}}]}} + for modus in moduslist: + switch = {"intrusion_settings": {"modes": [{modus: {"sirens_on": True}}]}} rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) log('Siren'.ljust(17) + ' | ' + color((args.siren + 'ED').ljust(8)) + ' | ') return @@ -399,7 +395,7 @@ def plug(): if not sensor_exist['smart_plug']: log('Plug'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) switch = {"name": args.plug} - rest(POST, URL_BASE + '/' + basestation_data[0]['id'] + '/endnodes/' + sensor_id['sp01'][0] + '/cmd', json.dumps(switch), CONTENT) + rest(POST, URL_BASE + '/' + basestation_data[0]['id'] + '/endnodes/' + sensor_id['sp01'][0] + '/cmd', json.dumps(switch), True) log('Plug'.ljust(17) + ' | ' + color(args.plug.ljust(8)) + ' | ') return @@ -465,13 +461,13 @@ def pb_message(pbmsg): if args.notify is not None and args.quiet is not True: from pushbullet import PushBullet, InvalidKeyError, PushbulletError try: - pb = PushBullet(args.notify) + pushb = PushBullet(args.notify) except InvalidKeyError: log('Notification'.ljust(17) + ' | ' + color('token'.ljust(8)) + ' | ') except PushbulletError: log('Notification'.ljust(17) + ' | ' + color('error'.ljust(8)) + ' | ') else: - pb.push_note('Gigaset Elements', pbmsg) + pushb.push_note('Gigaset Elements', pbmsg) log('Notification'.ljust(17) + ' | ' + color('pushed'.ljust(8)) + ' | ') return @@ -522,7 +518,7 @@ def monitor(): else: mode = 'Monitor mode' log(mode.ljust(17) + ' | ' + color('started'.ljust(8)) + ' | ' + 'CTRL+C to exit') - from_ts = str(int(time.time())*1000) + from_ts = str(int(time.time()) * 1000) try: while 1: lastevents = rest(GET, url_monitor + '&from_ts=' + from_ts) @@ -596,8 +592,8 @@ def sensor(): def rules(): """List custom rule(s).""" - rules = rest(GET, URL_BASE + '/' + basestation_data[0]['id'] + '/rules?rules=custom') - for item in rules: + ruleset = rest(GET, URL_BASE + '/' + basestation_data[0]['id'] + '/rules?rules=custom') + for item in ruleset: try: if item['active']: item['active'] = 'active' @@ -644,7 +640,7 @@ def camera_info(): color(camera_data[0]['settings']['nightmode']) + ' | mic ' + color(camera_data[0]['settings']['mic'])), print('| motion detection ' + color(camera_data[0]['motion_detection']['status']) + ' | connection ' + color(camera_data[0]['settings']['connection'])), if camera_data[0]['settings']['connection'] == 'wifi': - print '| ssid ' + bcolors.OKGREEN + str(camera_data[0]['wifi_ssid']).upper() + bcolors.ENDC + print '| ssid ' + OKGREEN + str(camera_data[0]['wifi_ssid']).upper() + ENDC except KeyError: print stream_data = rest(GET, URL_CAMERA + '/' + camera_data[0]['id'] + '/liveview/start') From 13716f742752588b874dc94fc9e09fd8fccc345c Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 7 Apr 2016 19:58:34 +0200 Subject: [PATCH 010/107] fix not working option --rules due to api changes --- gigasetelements/gigasetelements.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index db48494..ebd07cc 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -599,17 +599,7 @@ def rules(): item['active'] = 'active' else: item['active'] = 'inactive' - if item['parameter']['start_time'] == 0 and item['parameter']['end_time'] == 86400: - timer = '00:00 - 00:00'.ljust(13) - else: - timer = str(datetime.timedelta(seconds=int(item['parameter']['start_time']))).rjust(8, '0')[ - 0:5] + ' - ' + str(datetime.timedelta(seconds=int(item['parameter']['end_time']))).rjust(8, '0')[0:5] - if item['parameter']['repeater']['frequency'] == 'daily': - days = '1, 2, 3, 4, 5, 6, 7' - else: - days = str(item['parameter']['repeater']['at']).replace('[', '').replace(']', '').ljust(19) - log(item['friendly_name'].ljust(17) + ' | ' + color(item['active'].ljust(8)) + ' | ' + item['parameter']['repeater'] - ['frequency'].ljust(7) + ' | ' + timer + ' | ' + days + ' | ' + item['recipe'].replace('_', ' ') + ' | ' + item['id']) + log(item['friendly_name'].ljust(17) + ' | ' + color(item['active'].ljust(8)) + ' | ' + item['friendly_description']) except KeyError: continue return From 6522aa9c200f21153908b362158f9be42ed34788 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 7 Apr 2016 20:16:04 +0200 Subject: [PATCH 011/107] remove unused import --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index ebd07cc..1aa31f1 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -308,7 +308,7 @@ def connect(): def check_version(): """Check if new version exists on pypi.""" - from distutils.version import LooseVersion, StrictVersion + from distutils.version import LooseVersion remotedata = rest(GET, URL_RELEASE, None, False, 2, 0, True) if remotedata is not None: remoteversion = str(remotedata['info']['version']) From f4115757ec6be09cd63d0b06fca23e188213a5ba Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 7 Apr 2016 20:54:19 +0200 Subject: [PATCH 012/107] add smoke sensor to hardware detection routine --- gigasetelements/gigasetelements.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 1aa31f1..3227d5c 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -323,7 +323,7 @@ def collect_hw(): global sensor_id global sensor_exist sensor_id = {} - sensor_exist = dict.fromkeys(['button', 'camera', 'door_sensor', 'indoor_siren', 'presence_sensor', 'smart_plug'], False) + sensor_exist = dict.fromkeys(['button', 'camera', 'door_sensor', 'indoor_siren', 'presence_sensor', 'smart_plug', 'smoke'], False) for item in basestation_data[0]['sensors']: sensor_id.setdefault(item['type'], []).append(item['id']) try: @@ -339,6 +339,8 @@ def collect_hw(): sensor_exist.update(dict.fromkeys(['button'], True)) if 'yc01' in sensor_id: sensor_exist.update(dict.fromkeys(['camera'], True)) + if 'sd01' in sensor_id: + sensor_exist.update(dict.fromkeys(['smoke'], True)) if 'ws02' in sensor_id: sensor_exist.update(dict.fromkeys(['window_sensor'], True)) if 'ps01' in sensor_id or 'ps02' in sensor_id: From 51ee52a79cabaa1f8319b737a860252e0b2f9622 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 7 Apr 2016 21:22:07 +0200 Subject: [PATCH 013/107] add window, phone and smoke to filter options --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 3227d5c..449cca8 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -55,7 +55,7 @@ parser.add_argument('-o', '--cronjob', help='schedule cron job at HH:MM (requires -m option)', required=False, metavar='HH:MM') parser.add_argument('-x', '--remove', help='remove all cron jobs linked to this program', action='store_true', required=False) parser.add_argument('-f', '--filter', help='filter events on type', required=False, choices=( - 'door', 'motion', 'siren', 'plug', 'button', 'homecoming', 'intrusion', 'systemhealth', 'camera')) + 'door', 'window', 'motion', 'siren', 'plug', 'button', 'homecoming', 'intrusion', 'systemhealth', 'camera', 'phone', 'smoke')) parser.add_argument('-m', '--modus', help='set modus', required=False, choices=('home', 'away', 'custom')) parser.add_argument('-k', '--delay', help='set alarm timer delay in seconds (use 0 to disable)', type=int, required=False) parser.add_argument('-D', '--daemon', help='daemonize during monitor/domoticz mode', action='store_true', required=False) From d9165135f4e6652a68901f66a60ca3bc4d610026 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 12 Apr 2016 10:33:02 +0200 Subject: [PATCH 014/107] refactor global variables to function return --- gigasetelements/gigasetelements.py | 243 +++++++++++++++-------------- 1 file changed, 130 insertions(+), 113 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 449cca8..7bb181b 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -5,18 +5,35 @@ """Main code for gigasetelements command-line interface.""" -import gc import os import sys import time -import random import datetime import urlparse import argparse import json import ConfigParser import logging -import unidecode + +try: + from pushbullet import PushBullet, InvalidKeyError, PushbulletError + import requests + import unidecode +except ImportError as error: + sys.exit(str(error) + '. Please install from PyPI: pip install --upgrade ' + str(error).rsplit(None, 1)[-1]) + +if os.name == 'nt': + try: + import colorama + except ImportError as error: + sys.exit(str(error) + '. Please install from PyPI: pip install --upgrade ' + str(error).rsplit(None, 1)[-1]) + +if os.name == 'posix': + try: + from crontab import CronTab + from daemonize import Daemonize + except ImportError as error: + sys.exit(str(error) + '. Please install from PyPI: pip install --upgrade ' + str(error).rsplit(None, 1)[-1]) _AUTHOR_ = 'dynasticorpheus@gmail.com' @@ -77,25 +94,18 @@ parser.add_argument('-S', '--silent', help='suppress urllib3 warnings', action='store_true', required=False) parser.add_argument('-v', '--version', help='show version', action='version', version='%(prog)s version ' + str(_VERSION_)) -gc.disable() args = parser.parse_args() config = ConfigParser.ConfigParser(defaults=OPTDEF) -print -print 'Gigaset Elements - Command-line Interface v' + _VERSION_ -print - if os.name == 'nt': - import colorama colorama.init() args.cronjob = None args.remove = False - ntconfig = os.path.join(os.environ['APPDATA'], os.path.normpath('gigasetelements-cli/gigasetelements-cli.conf')) + NTCONFIG = os.path.join(os.environ['APPDATA'], os.path.normpath('gigasetelements-cli/gigasetelements-cli.conf')) else: - ntconfig = '' + NTCONFIG = '' if args.daemon and os.name != 'nt': - from daemonize import Daemonize try: target = open(args.pid, 'w') target.close() @@ -104,22 +114,7 @@ print sys.exit() -if args.log is not None: - logger = logging.getLogger(__name__) - logger.setLevel(logging.INFO) - logger.propagate = False - try: - fh = logging.FileHandler(args.log, 'a') - except IOError: - print FAIL + '[-] Unable to write log file ' + args.log + ENDC - print - sys.exit() - fh.setLevel(logging.INFO) - logger.addHandler(fh) - logger.info('[' + time.strftime("%c") + '] Gigaset Elements - Command-line Interface') - if args.cronjob is None and args.remove is False: - import requests s = requests.Session() s.mount("http://", requests.adapters.HTTPAdapter(max_retries=3)) s.mount("https://", requests.adapters.HTTPAdapter(max_retries=3)) @@ -142,6 +137,7 @@ def log(logme, rbg=0, exitnow=0, newline=1): if os.name == 'nt': logme = unidecode.unidecode(unicode(logme)) if args.log is not None: + logger = logging.getLogger(__name__) logger.info('[' + time.strftime("%c") + '] ' + unidecode.unidecode(unicode(logme))) if rbg == 1: print OKGREEN + '[-] ' + logme.encode('utf-8') + ENDC @@ -198,15 +194,13 @@ def load_option(arg, section, option): def configure(): """Load variables based on command line arguments and config file.""" - global dconfig - global url_domo - global credfromfile - global pem + cfg_domo = None + url_domo = None credfromfile = False authstring = '' if args.config is None: locations = ['/opt/etc/gigasetelements-cli.conf', '/usr/local/etc/gigasetelements-cli.conf', '/usr/etc/gigasetelements-cli.conf', - '/etc/gigasetelements-cli.conf', os.path.expanduser('~/.gigasetelements-cli/gigasetelements-cli.conf'), ntconfig, + '/etc/gigasetelements-cli.conf', os.path.expanduser('~/.gigasetelements-cli/gigasetelements-cli.conf'), NTCONFIG, os.path.expanduser('~/Library/Application Support/gigasetelements-cli/gigasetelements-cli.conf')] for i in locations: if os.path.exists(i): @@ -220,10 +214,10 @@ def configure(): config.read(args.config) if args.monitor > 1: try: - dconfig = dict(config.items('domoticz')) - if dconfig['username'] != '': - authstring = dconfig['username'] + ':' + dconfig['password'] + '@' - url_domo = 'http://' + authstring + dconfig['ip'] + ':' + dconfig['port'] + cfg_domo = dict(config.items('domoticz')) + if cfg_domo['username'] != '': + authstring = cfg_domo['username'] + ':' + cfg_domo['password'] + '@' + url_domo = 'http://' + authstring + cfg_domo['ip'] + ':' + cfg_domo['port'] except Exception: log('Configuration'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Domoticz setting(s) incorrect and/or missing', 3, 1) log('Configuration'.ljust(17) + ' | ' + color('loaded'.ljust(8)) + ' | ' + args.config) @@ -241,20 +235,16 @@ def configure(): requests.packages.urllib3.disable_warnings() except Exception: pass - if args.insecure: - pem = False - else: - try: - import certifi - pem = certifi.old_where() - except Exception: - pem = True - return + return url_domo, cfg_domo, credfromfile def rest(method, url, payload=None, header=False, timeout=90, end=1, silent=False): """REST interaction using requests module.""" request = None + if args.insecure: + pem = False + else: + pem = True if header: header = {'content-type': 'application/json; charset=UTF-8'} else: @@ -269,9 +259,9 @@ def rest(method, url, payload=None, header=False, timeout=90, end=1, silent=Fals log('ERROR'.ljust(17) + ' | ' + 'UNKNOWN'.ljust(8) + ' | ' + str(error.message), 3, end) if request is not None: if not silent: - if request.status_code != requests.codes.ok: - u = urlparse.urlparse(request.url) - log('HTTP ERROR'.ljust(17) + ' | ' + str(request.status_code).ljust(8) + ' | ' + request.reason + ' ' + str(u.path), 3, end) + if request.status_code != requests.codes.ok: # pylint: disable=no-member + urlsplit = urlparse.urlparse(request.url) + log('HTTP ERROR'.ljust(17) + ' | ' + str(request.status_code).ljust(8) + ' | ' + request.reason + ' ' + str(urlsplit.path), 3, end) try: data = request.json() except ValueError: @@ -279,19 +269,24 @@ def rest(method, url, payload=None, header=False, timeout=90, end=1, silent=Fals return data -def connect(): - """Gigaset Elements API authentication and status retrieval.""" - global basestation_data - global status_data - global camera_data - global auth_time - payload = {'password': args.password, 'email': args.username} - commit_data = rest(POST, URL_IDENTITY, payload) - log('Identity'.ljust(17) + ' | ' + color('verified') + ' | ' + commit_data['message']) +def authenticate(reauthenticate=False): + """Gigaset Elements API authentication.""" + if not reauthenticate: + payload = {'password': args.password, 'email': args.username} + commit_data = rest(POST, URL_IDENTITY, payload) + log('Identity'.ljust(17) + ' | ' + color('verified') + ' | ' + commit_data['message']) + rest(HEAD, URL_USAGE, None, False, 2, 0, True) auth_time = time.time() rest(GET, URL_AUTH) - log('Authentication'.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') - rest(HEAD, URL_USAGE, None, False, 2, 0, True) + if not reauthenticate: + log('Authentication'.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') + else: + log('Re-authentication'.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') + return auth_time + + +def systemstatus(): + """Gigaset Elements system status retrieval.""" basestation_data = rest(GET, URL_BASE) log('Basestation'.ljust(17) + ' | ' + color(basestation_data[0]['status'].ljust(8)) + ' | ' + basestation_data[0]['id']) camera_data = rest(GET, URL_CAMERA) @@ -303,7 +298,7 @@ def connect(): if args.modus is None: log('Status'.ljust(17) + ' | ' + color(status_data['system_health'].ljust(8)) + status_data['status_msg_id'].upper() + ' | Modus ' + color(basestation_data[0]['intrusion_settings']['active_mode'])) - return + return basestation_data, status_data, camera_data def check_version(): @@ -318,10 +313,8 @@ def check_version(): return -def collect_hw(): +def collect_hw(basestation_data, camera_data): """Retrieve sensor list and details.""" - global sensor_id - global sensor_exist sensor_id = {} sensor_exist = dict.fromkeys(['button', 'camera', 'door_sensor', 'indoor_siren', 'presence_sensor', 'smart_plug', 'smoke'], False) for item in basestation_data[0]['sensors']: @@ -347,10 +340,10 @@ def collect_hw(): sensor_exist.update(dict.fromkeys(['presence_sensor'], True)) if 'ds01' in sensor_id or 'ds02' in sensor_id: sensor_exist.update(dict.fromkeys(['door_sensor'], True)) - return + return sensor_id, sensor_exist -def modus_switch(): +def modus_switch(basestation_data, status_data): """Switch alarm modus.""" switch = {'intrusion_settings': {'active_mode': args.modus}} rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) @@ -359,7 +352,7 @@ def modus_switch(): return -def set_delay(): +def set_delay(basestation_data): """Set alarm trigger delay.""" linfo = '' delay = str(args.delay * 1000) @@ -375,7 +368,7 @@ def set_delay(): return -def siren(): +def siren(basestation_data, sensor_exist): """Dis(arm) siren.""" if not sensor_exist['indoor_siren']: log('Siren'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) @@ -392,7 +385,7 @@ def siren(): return -def plug(): +def plug(basestation_data, sensor_exist, sensor_id): """Switch Plug on or off.""" if not sensor_exist['smart_plug']: log('Plug'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) @@ -411,9 +404,8 @@ def istimeformat(timestr): return False -def add_cron(): +def add_cron(credfromfile): """Add job to crontab to set alarm modus.""" - from crontab import CronTab if args.modus is None: log('Cronjob'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Specify modus using -m option', 3, 1) if istimeformat(args.cronjob): @@ -443,7 +435,6 @@ def add_cron(): def remove_cron(): """Remove all jobs from crontab setting alarm modus.""" - from crontab import CronTab cron = CronTab(user=True) existing = cron.find_command('gigasetelements-cli') count = 0 @@ -461,7 +452,6 @@ def remove_cron(): def pb_message(pbmsg): """Send message using pushbullet module.""" if args.notify is not None and args.quiet is not True: - from pushbullet import PushBullet, InvalidKeyError, PushbulletError try: pushb = PushBullet(args.notify) except InvalidKeyError: @@ -505,9 +495,8 @@ def list_events(): return -def monitor(): +def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): """List events realtime optionally filtered by type.""" - global auth_time if args.filter is None: url_monitor = URL_EVENTS + '?limit=10' else: @@ -516,7 +505,8 @@ def monitor(): mode = 'Domoticz mode' print rest(GET, url_domo + URL_LOG + 'Gigaset Elements - Command-line Interface: Domoticz mode started') - domoticz(status_data['system_health'].lower(), basestation_data[0]['id'].lower(), basestation_data[0]['friendly_name'].lower()) + domoticz(status_data['system_health'].lower(), basestation_data[0]['id'].lower(), basestation_data[0] + ['friendly_name'].lower(), basestation_data, url_domo, cfg_domo) else: mode = 'Monitor mode' log(mode.ljust(17) + ' | ' + color('started'.ljust(8)) + ' | ' + 'CTRL+C to exit') @@ -531,20 +521,19 @@ def monitor(): 'type'].ljust(8) + ' | ' + item['type'] + ' ' + item['o'].get('friendly_name', item['o']['type'])) if args.monitor > 1: if item['o']['type'] == 'ycam': - domoticz(item['type'][5:].lower(), item['source_id'].lower(), 'ycam') + domoticz(item['type'][5:].lower(), item['source_id'].lower(), 'ycam', basestation_data, url_domo, cfg_domo) else: - domoticz(item['type'].lower(), item['o']['id'].lower(), item['o'].get('friendly_name', 'basestation').lower()) + domoticz(item['type'].lower(), item['o']['id'].lower(), item['o'].get('friendly_name', 'basestation').lower(), + basestation_data, url_domo, cfg_domo) else: log(time.strftime('%m/%d/%y %H:%M:%S', time.localtime(int(item['ts']) / 1000)) + ' | ' + 'system'.ljust(8) + ' | ' + item['source_type'] + ' ' + item['type']) - domoticz(item['type'].lower(), basestation_data[0]['id'].lower(), item['source_type'].lower()) + domoticz(item['type'].lower(), basestation_data[0]['id'].lower(), item['source_type'].lower(), basestation_data, url_domo, cfg_domo) from_ts = str(int(item['ts']) + 1) except KeyError: continue if time.time() - auth_time >= AUTH_EXPIRE: - auth_time = time.time() - rest(GET, URL_AUTH) - log('Re-authentication'.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') + auth_time = authenticate(reauthenticate=True) else: time.sleep(1) except KeyboardInterrupt: @@ -554,25 +543,24 @@ def monitor(): return -def domoticz(event, sid, friendly): +def domoticz(event, sid, friendly, basestation_data, url_domo, cfg_domo): """Push events to domoticz server.""" - global status_data if event in ['open', 'close', 'sirenon', 'sirenoff', 'on', 'off', 'movement', 'motion', 'button1', 'button2', 'button3', 'button4']: if event in ['close', 'sirenoff', 'off']: cmd = 'off' else: cmd = 'on' - rest(GET, url_domo + URL_SWITCH + cmd.title() + '&idx=' + dconfig[sid]) + rest(GET, url_domo + URL_SWITCH + cmd.title() + '&idx=' + cfg_domo[sid]) else: status_data = rest(GET, URL_HEALTH) - rest(GET, url_domo + URL_ALERT + dconfig[basestation_data[0]['id'].lower()] + '&nvalue=' + + rest(GET, url_domo + URL_ALERT + cfg_domo[basestation_data[0]['id'].lower()] + '&nvalue=' + LEVEL.get(status_data['system_health'], '3') + '&svalue=' + friendly + ' | ' + event) sys.stdout.write("\033[F") sys.stdout.write("\033[K") return -def sensor(): +def sensor(basestation_data): """Show sensor details and current state.""" log(basestation_data[0]['friendly_name'].ljust(17) + ' | ' + color(basestation_data[0] ['status'].ljust(8)) + ' | firmware ' + color(basestation_data[0]['firmware_status'])) @@ -592,7 +580,7 @@ def sensor(): return -def rules(): +def rules(basestation_data): """List custom rule(s).""" ruleset = rest(GET, URL_BASE + '/' + basestation_data[0]['id'] + '/rules?rules=custom') for item in ruleset: @@ -621,7 +609,7 @@ def notifications(): return -def camera_info(): +def camera_info(camera_data, sensor_exist): """Show camera details and current state.""" if not sensor_exist['camera']: log('Camera'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) @@ -641,7 +629,7 @@ def camera_info(): return -def record(): +def record(camera_data, sensor_exist): """Start or stop camera recording based on current state.""" if not sensor_exist['camera']: log('Camera'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) @@ -655,19 +643,40 @@ def record(): return -def main(): - """Main program.""" +def start_logger(logfile): + """Setup log file handler.""" + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + logger.propagate = False + try: + filehandle = logging.FileHandler(logfile, 'a') + except IOError: + print FAIL + '[-] Unable to write log file ' + logfile + ENDC + print + sys.exit() + filehandle.setLevel(logging.INFO) + logger.addHandler(filehandle) + logger.info('[' + time.strftime("%c") + '] Gigaset Elements - Command-line Interface') + return - pb_body = None +def base(): + """Base program.""" + pb_body = None + print + print 'Gigaset Elements - Command-line Interface v' + _VERSION_ + print try: - configure() + if args.log: + start_logger(args.log) + + if args.daemon: + log('Run as background'.ljust(17) + ' | ' + color('daemon'.ljust(8)) + ' | ' + args.pid) - if not args.noupdate and random.randint(1, 10) == 1: - check_version() + url_domo, cfg_domo, credfromfile = configure() if args.cronjob is not None: - add_cron() + add_cron(credfromfile) print sys.exit() @@ -676,43 +685,47 @@ def main(): print sys.exit() - connect() + check_version() - collect_hw() + auth_time = authenticate() + + basestation_data, status_data, camera_data = systemstatus() + + sensor_id, sensor_exist = collect_hw(basestation_data, camera_data) if args.modus is not None and args.cronjob is None: - modus_switch() + modus_switch(basestation_data, status_data) if args.sensor is not True: pb_body = 'Status ' + status_data['system_health'].upper() + ' | Modus set from ' + \ basestation_data[0]['intrusion_settings']['active_mode'].upper() + ' to ' + args.modus.upper() if args.sensor: - sensor() + sensor(basestation_data) if status_data['status_msg_id'] == '': status_data['status_msg_id'] = u'\u2713' pb_body = 'Status ' + status_data['system_health'].upper() + ' | ' + status_data['status_msg_id'].upper() + \ ' | Modus ' + basestation_data[0]['intrusion_settings']['active_mode'].upper() if args.delay is not None: - set_delay() + set_delay(basestation_data) if args.camera: - camera_info() + camera_info(camera_data, sensor_exist) if args.record: - record() + record(camera_data, sensor_exist) if args.notifications: notifications() if args.rules: - rules() + rules(basestation_data) if args.siren: - siren() + siren(basestation_data, sensor_exist) if args.plug: - plug() + plug(basestation_data, sensor_exist, sensor_id) if pb_body is not None: pb_message(pb_body) @@ -723,15 +736,19 @@ def main(): list_events() if args.monitor: - if args.daemon and os.name != 'nt': - log('Run as background'.ljust(17) + ' | ' + color('daemon'.ljust(8)) + ' | ' + args.pid) - print - daemon = Daemonize(app="gigasetelements-cli", pid=args.pid, action=monitor, auto_close_fds=False) - daemon.start() - else: - monitor() + monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo) print except KeyboardInterrupt: log('Program'.ljust(17) + ' | ' + color('halted'.ljust(8)) + ' | ' + 'CTRL+C') + + +def main(): + """Main program.""" + if args.daemon and os.name != 'nt': + print + daemon = Daemonize(app="gigasetelements-cli", pid=args.pid, action=base, auto_close_fds=False) + daemon.start() + else: + base() From 0faeda3ee22fce1768a1e39c6422a656e985acee Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Wed, 13 Apr 2016 08:52:17 +0200 Subject: [PATCH 015/107] use pre-release version number for develop branch --- gigasetelements/gigasetelements.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 7bb181b..8e1defe 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -37,7 +37,7 @@ _AUTHOR_ = 'dynasticorpheus@gmail.com' -_VERSION_ = '1.4.0' +_VERSION_ = '1.5.0b1' OKGREEN = '\033[92m' WARN = '\033[93m' @@ -303,11 +303,11 @@ def systemstatus(): def check_version(): """Check if new version exists on pypi.""" - from distutils.version import LooseVersion + from distutils.version import StrictVersion remotedata = rest(GET, URL_RELEASE, None, False, 2, 0, True) if remotedata is not None: remoteversion = str(remotedata['info']['version']) - if LooseVersion(_VERSION_) < LooseVersion(remoteversion): + if StrictVersion(_VERSION_) < StrictVersion(remoteversion): log('Program'.ljust(17) + ' | ' + color('update'.ljust(8)) + ' | Version ' + remoteversion + ' is available. Run pip install --upgrade gigasetelements-cli') return From 776f17fb890aee3d729aac7ed8f951ff33f25d57 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Wed, 13 Apr 2016 09:13:59 +0200 Subject: [PATCH 016/107] report version number in logger and align text --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 8e1defe..6261bde 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -656,7 +656,7 @@ def start_logger(logfile): sys.exit() filehandle.setLevel(logging.INFO) logger.addHandler(filehandle) - logger.info('[' + time.strftime("%c") + '] Gigaset Elements - Command-line Interface') + logger.info('[' + time.strftime("%c") + '] ' + 'Gigaset Elements'.ljust(17) + ' | ' + _VERSION_.ljust(8) + ' | ' + 'Command-line Interface') return From d6d99c45d9840d37b6e300a9259fc50ffabdc5d6 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Wed, 13 Apr 2016 12:44:34 +0200 Subject: [PATCH 017/107] add cmd to install dependencies --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 8be75f2..65cfb6c 100644 --- a/README.rst +++ b/README.rst @@ -23,7 +23,7 @@ For easy installation including dependencies simply run below command (with elev [1] *git clone https://github.com/dynasticorpheus/gigaset-elements.git* -[2] install *dependencies*, see requirements.txt +[2] install *dependencies*, pip install -r requirements.txt (with elevated privileges if needed) [3] *python setup.py install* (or run from source using wrapper ./gigasetelements-cli.py) From 386c9945aa48142551239235d68037237dc509ee Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 14 Apr 2016 11:58:12 +0200 Subject: [PATCH 018/107] use variable unpacking --- gigasetelements/gigasetelements.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 6261bde..08c01c2 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -39,10 +39,7 @@ _AUTHOR_ = 'dynasticorpheus@gmail.com' _VERSION_ = '1.5.0b1' -OKGREEN = '\033[92m' -WARN = '\033[93m' -FAIL = '\033[91m' -ENDC = '\033[0m' +OKGREEN, WARN, FAIL, ENDC = '\033[92m', '\033[93m', '\033[91m', '\033[0m' LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4'} OPTDEF = {'username': None, 'password': None, 'modus': None, 'pbtoken': None, 'silent': 'False', 'noupdate': 'False', 'insecure': 'False'} @@ -118,9 +115,7 @@ s = requests.Session() s.mount("http://", requests.adapters.HTTPAdapter(max_retries=3)) s.mount("https://", requests.adapters.HTTPAdapter(max_retries=3)) - POST = s.post - GET = s.get - HEAD = s.head + POST, GET, HEAD = s.post, s.get, s.head else: args.noupdate = True @@ -194,10 +189,7 @@ def load_option(arg, section, option): def configure(): """Load variables based on command line arguments and config file.""" - cfg_domo = None - url_domo = None - credfromfile = False - authstring = '' + cfg_domo, url_domo, credfromfile, authstring = None, None, False, '' if args.config is None: locations = ['/opt/etc/gigasetelements-cli.conf', '/usr/local/etc/gigasetelements-cli.conf', '/usr/etc/gigasetelements-cli.conf', '/etc/gigasetelements-cli.conf', os.path.expanduser('~/.gigasetelements-cli/gigasetelements-cli.conf'), NTCONFIG, From 17abd2ed1ab43a7bd97d6f36652090facd64e52d Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 14 Apr 2016 13:04:51 +0200 Subject: [PATCH 019/107] use pre-release version number for develop branch and code cleanup --- setup.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index d411ff9..d752a6f 100755 --- a/setup.py +++ b/setup.py @@ -1,17 +1,23 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + + +"""Install routine for gigasetelements command-line interface.""" + +import os +import codecs from setuptools import setup, find_packages -from codecs import open -from os import path, name -here = path.abspath(path.dirname(__file__)) +HERE = os.path.abspath(os.path.dirname(__file__)) -with open(path.join(here, 'DESCRIPTION.rst'), encoding='utf-8') as f: +with codecs.open(os.path.join(HERE, 'DESCRIPTION.rst'), encoding='utf-8') as f: long_description = f.read() packagelist = ['requests', 'pushbullet.py', 'unidecode'] -if name == 'nt': +if os.name == 'nt': packagelist.append('colorama') else: packagelist.append('python-crontab') @@ -19,7 +25,7 @@ setup( name='gigasetelements-cli', - version='1.4.0', + version='1.5.0b1', description='gigasetelements-cli allows you to control your \ Gigaset Elements home security system from the command line.', long_description=long_description, From 8fed1e79fa43692b7f32885112b7e20a60b6481d Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 15 Apr 2016 11:09:01 +0200 Subject: [PATCH 020/107] single quotes until python 3 --- gigasetelements/gigasetelements.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 08c01c2..dae8faa 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -113,8 +113,8 @@ if args.cronjob is None and args.remove is False: s = requests.Session() - s.mount("http://", requests.adapters.HTTPAdapter(max_retries=3)) - s.mount("https://", requests.adapters.HTTPAdapter(max_retries=3)) + s.mount('http://', requests.adapters.HTTPAdapter(max_retries=3)) + s.mount('https://', requests.adapters.HTTPAdapter(max_retries=3)) POST, GET, HEAD = s.post, s.get, s.head else: args.noupdate = True @@ -133,7 +133,7 @@ def log(logme, rbg=0, exitnow=0, newline=1): logme = unidecode.unidecode(unicode(logme)) if args.log is not None: logger = logging.getLogger(__name__) - logger.info('[' + time.strftime("%c") + '] ' + unidecode.unidecode(unicode(logme))) + logger.info('[' + time.strftime('%c') + '] ' + unidecode.unidecode(unicode(logme))) if rbg == 1: print OKGREEN + '[-] ' + logme.encode('utf-8') + ENDC elif rbg == 2: @@ -547,8 +547,8 @@ def domoticz(event, sid, friendly, basestation_data, url_domo, cfg_domo): status_data = rest(GET, URL_HEALTH) rest(GET, url_domo + URL_ALERT + cfg_domo[basestation_data[0]['id'].lower()] + '&nvalue=' + LEVEL.get(status_data['system_health'], '3') + '&svalue=' + friendly + ' | ' + event) - sys.stdout.write("\033[F") - sys.stdout.write("\033[K") + sys.stdout.write('\033[F') + sys.stdout.write('\033[K') return @@ -648,7 +648,7 @@ def start_logger(logfile): sys.exit() filehandle.setLevel(logging.INFO) logger.addHandler(filehandle) - logger.info('[' + time.strftime("%c") + '] ' + 'Gigaset Elements'.ljust(17) + ' | ' + _VERSION_.ljust(8) + ' | ' + 'Command-line Interface') + logger.info('[' + time.strftime('%c') + '] ' + 'Gigaset Elements'.ljust(17) + ' | ' + _VERSION_.ljust(8) + ' | ' + 'Command-line Interface') return @@ -740,7 +740,7 @@ def main(): """Main program.""" if args.daemon and os.name != 'nt': print - daemon = Daemonize(app="gigasetelements-cli", pid=args.pid, action=base, auto_close_fds=False) + daemon = Daemonize(app='gigasetelements-cli', pid=args.pid, action=base, auto_close_fds=False) daemon.start() else: base() From 4b9d9db6ddd4454470a1ca5ebb2c0233d993494a Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 15 Apr 2016 14:34:52 +0200 Subject: [PATCH 021/107] use colorama classes for colorization on all platforms --- gigasetelements/gigasetelements.py | 29 +++++++++++------------------ requirements.txt | 8 ++++---- setup.py | 6 ++---- 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index dae8faa..39d78cf 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -16,18 +16,13 @@ import logging try: + from colorama import init, Fore from pushbullet import PushBullet, InvalidKeyError, PushbulletError import requests import unidecode except ImportError as error: sys.exit(str(error) + '. Please install from PyPI: pip install --upgrade ' + str(error).rsplit(None, 1)[-1]) -if os.name == 'nt': - try: - import colorama - except ImportError as error: - sys.exit(str(error) + '. Please install from PyPI: pip install --upgrade ' + str(error).rsplit(None, 1)[-1]) - if os.name == 'posix': try: from crontab import CronTab @@ -39,8 +34,6 @@ _AUTHOR_ = 'dynasticorpheus@gmail.com' _VERSION_ = '1.5.0b1' -OKGREEN, WARN, FAIL, ENDC = '\033[92m', '\033[93m', '\033[91m', '\033[0m' - LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4'} OPTDEF = {'username': None, 'password': None, 'modus': None, 'pbtoken': None, 'silent': 'False', 'noupdate': 'False', 'insecure': 'False'} AUTH_EXPIRE = 14400 @@ -93,9 +86,9 @@ args = parser.parse_args() config = ConfigParser.ConfigParser(defaults=OPTDEF) +init(autoreset=True) if os.name == 'nt': - colorama.init() args.cronjob = None args.remove = False NTCONFIG = os.path.join(os.environ['APPDATA'], os.path.normpath('gigasetelements-cli/gigasetelements-cli.conf')) @@ -107,7 +100,7 @@ target = open(args.pid, 'w') target.close() except IOError: - print FAIL + '[-] Unable to write pid file ' + args.pid + ENDC + print Fore.RED + '[-] Unable to write pid file ' + args.pid print sys.exit() @@ -135,11 +128,11 @@ def log(logme, rbg=0, exitnow=0, newline=1): logger = logging.getLogger(__name__) logger.info('[' + time.strftime('%c') + '] ' + unidecode.unidecode(unicode(logme))) if rbg == 1: - print OKGREEN + '[-] ' + logme.encode('utf-8') + ENDC + print Fore.GREEN + '[-] ' + logme.encode('utf-8') elif rbg == 2: - print WARN + '[-] ' + logme.encode('utf-8') + ENDC + print Fore.YELLOW + '[-] ' + logme.encode('utf-8') elif rbg == 3: - print FAIL + '[-] ' + logme.encode('utf-8') + ENDC + print Fore.RED + '[-] ' + logme.encode('utf-8') else: if newline == 1: print '[-] ' + logme.encode('utf-8') @@ -163,11 +156,11 @@ def color(txt): txt = txt.upper() else: if txt.lower().strip() in green: - txt = OKGREEN + txt.upper() + ENDC + txt = Fore.GREEN + txt.upper() + Fore.RESET elif txt.lower().strip() in orange: - txt = WARN + txt.upper() + ENDC + txt = Fore.YELLOW + txt.upper() + Fore.RESET else: - txt = FAIL + txt.upper() + ENDC + txt = Fore.RED + txt.upper() + Fore.RESET return txt @@ -612,7 +605,7 @@ def camera_info(camera_data, sensor_exist): color(camera_data[0]['settings']['nightmode']) + ' | mic ' + color(camera_data[0]['settings']['mic'])), print('| motion detection ' + color(camera_data[0]['motion_detection']['status']) + ' | connection ' + color(camera_data[0]['settings']['connection'])), if camera_data[0]['settings']['connection'] == 'wifi': - print '| ssid ' + OKGREEN + str(camera_data[0]['wifi_ssid']).upper() + ENDC + print '| ssid ' + Fore.GREEN + str(camera_data[0]['wifi_ssid']).upper() except KeyError: print stream_data = rest(GET, URL_CAMERA + '/' + camera_data[0]['id'] + '/liveview/start') @@ -643,7 +636,7 @@ def start_logger(logfile): try: filehandle = logging.FileHandler(logfile, 'a') except IOError: - print FAIL + '[-] Unable to write log file ' + logfile + ENDC + print Fore.RED + '[-] Unable to write log file ' + logfile print sys.exit() filehandle.setLevel(logging.INFO) diff --git a/requirements.txt b/requirements.txt index f8f7d2b..5a7f319 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ requests>=2.9.1 -colorama>=0.3.5 +colorama>=0.3.7 python-crontab>=2.0.1 -pushbullet.py>=0.9.0 -unidecode>=0.4.18 -daemonize>=2.4.2 +pushbullet.py>=0.10.0 +unidecode>=0.4.19 +daemonize>=2.4.6 diff --git a/setup.py b/setup.py index d752a6f..d969c5b 100755 --- a/setup.py +++ b/setup.py @@ -15,11 +15,9 @@ with codecs.open(os.path.join(HERE, 'DESCRIPTION.rst'), encoding='utf-8') as f: long_description = f.read() -packagelist = ['requests', 'pushbullet.py', 'unidecode'] +packagelist = ['requests', 'pushbullet.py', 'unidecode', 'colorama'] -if os.name == 'nt': - packagelist.append('colorama') -else: +if os.name == 'posix': packagelist.append('python-crontab') packagelist.append('daemonize') From 8f380f83d876230fe3c4310b48164ed9247b3d66 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 18 Apr 2016 08:30:17 +0200 Subject: [PATCH 022/107] add camera snapshot download support (option -A) --- gigasetelements/gigasetelements.py | 47 +++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 39d78cf..3125fb7 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -75,6 +75,7 @@ parser.add_argument('-g', '--plug', help='switch plug on/off', required=False, choices=('on', 'off')) parser.add_argument('-a', '--camera', help='show camera status', action='store_true', required=False) parser.add_argument('-r', '--record', help='switch camera recording on/off', action='store_true', required=False) +parser.add_argument('-A', '--snapshot', help='download camera snapshot', action='store_true', required=False) parser.add_argument('-t', '--monitor', help='show events using monitor mode (use -tt to activate domoticz mode)', action='count', default=0, required=False) parser.add_argument('-i', '--ignore', help='ignore configuration-file at predefined locations', action='store_true', required=False) parser.add_argument('-N', '--noupdate', help='do not periodically check for updates', action='store_true', required=False) @@ -95,15 +96,6 @@ else: NTCONFIG = '' -if args.daemon and os.name != 'nt': - try: - target = open(args.pid, 'w') - target.close() - except IOError: - print Fore.RED + '[-] Unable to write pid file ' + args.pid - print - sys.exit() - if args.cronjob is None and args.remove is False: s = requests.Session() s.mount('http://', requests.adapters.HTTPAdapter(max_retries=3)) @@ -147,10 +139,22 @@ def log(logme, rbg=0, exitnow=0, newline=1): return +def filewritable(filetype, fileloc, mustexit=1): + """Test if file can be opened for writing.""" + writable = False + try: + target = open(fileloc, 'w') + target.close() + writable = True + except IOError as error: + log(filetype.ljust(17) + ' | ' + color('error'.ljust(8)) + ' | ' + str(error), 0, mustexit) + return writable + + def color(txt): """Add color to string based on presence in list and return in uppercase.""" - green = ['ok', 'online', 'closed', 'up_to_date', 'home', 'auto', 'on', 'hd', 'cable', 'normal', 'daemon', - 'wifi', 'started', 'active', 'green', 'armed', 'pushed', 'verified', 'loaded', 'success'] + green = ['ok', 'online', 'closed', 'up_to_date', 'home', 'auto', 'on', 'hd', 'cable', 'normal', 'daemon', 'wifi', + 'started', 'active', 'green', 'armed', 'pushed', 'verified', 'loaded', 'success', 'download'] orange = ['orange', 'warning', 'update'] if args.log is not None: txt = txt.upper() @@ -628,6 +632,19 @@ def record(camera_data, sensor_exist): return +def getsnapshot(camera_data, sensor_exist): + """Download snapshot from camera.""" + if not sensor_exist['camera']: + log('Camera'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) + image_name = 'snapshot_' + time.strftime('%y%m%d') + '_' + time.strftime('%H%M%S') + '.jpg' + if filewritable('Snapshot image', image_name, 0): + log('Camera snapshot'.ljust(17) + ' | ' + color('download'.ljust(8)) + ' | ' + image_name) + snapshot = s.get(URL_CAMERA + '/' + str(camera_data[0]['id']) + '/snapshot') + with open(image_name, 'wb') as image: + image.write(snapshot.content) + return + + def start_logger(logfile): """Setup log file handler.""" logger = logging.getLogger(__name__) @@ -700,6 +717,9 @@ def base(): if args.record: record(camera_data, sensor_exist) + if args.snapshot: + getsnapshot(camera_data, sensor_exist) + if args.notifications: notifications() @@ -733,7 +753,8 @@ def main(): """Main program.""" if args.daemon and os.name != 'nt': print - daemon = Daemonize(app='gigasetelements-cli', pid=args.pid, action=base, auto_close_fds=False) - daemon.start() + if filewritable('PID file', args.pid): + daemon = Daemonize(app='gigasetelements-cli', pid=args.pid, action=base, auto_close_fds=False) + daemon.start() else: base() From fa23a5b1596535b27f4956062aee4cc83e5e02c9 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 19 Apr 2016 13:25:36 +0200 Subject: [PATCH 023/107] do full reauthentication --- gigasetelements/gigasetelements.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 3125fb7..1a1e6be 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -260,17 +260,16 @@ def rest(method, url, payload=None, header=False, timeout=90, end=1, silent=Fals def authenticate(reauthenticate=False): """Gigaset Elements API authentication.""" - if not reauthenticate: - payload = {'password': args.password, 'email': args.username} - commit_data = rest(POST, URL_IDENTITY, payload) - log('Identity'.ljust(17) + ' | ' + color('verified') + ' | ' + commit_data['message']) - rest(HEAD, URL_USAGE, None, False, 2, 0, True) auth_time = time.time() + payload = {'password': args.password, 'email': args.username} + commit_data = rest(POST, URL_IDENTITY, payload) rest(GET, URL_AUTH) - if not reauthenticate: - log('Authentication'.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') - else: + if reauthenticate: log('Re-authentication'.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') + else: + log('Identity'.ljust(17) + ' | ' + color('verified') + ' | ' + commit_data['message']) + rest(HEAD, URL_USAGE, None, False, 2, 0, True) + log('Authentication'.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') return auth_time From e96ceab30764266949eb6ca8cc535d819641e755 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 19 Apr 2016 21:12:27 +0200 Subject: [PATCH 024/107] log arguments passed --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 1a1e6be..b561c7f 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -657,7 +657,7 @@ def start_logger(logfile): sys.exit() filehandle.setLevel(logging.INFO) logger.addHandler(filehandle) - logger.info('[' + time.strftime('%c') + '] ' + 'Gigaset Elements'.ljust(17) + ' | ' + _VERSION_.ljust(8) + ' | ' + 'Command-line Interface') + logger.info('[' + time.strftime('%c') + '] ' + 'Gigaset Elements'.ljust(17) + ' | ' + 'CLI'.ljust(8) + ' | ' + _VERSION_ + ' | ' + ' '.join(sys.argv[1:])) return From 49fbaa87a246a2eb8e65ec955f437b56e10e85c1 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 21 Apr 2016 09:56:59 +0200 Subject: [PATCH 025/107] allow restart in daemon mode --- gigasetelements/gigasetelements.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index b561c7f..cb9c1d3 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -107,6 +107,8 @@ def restart_program(): """Restarts the current program.""" + if args.daemon: + os.remove(args.pid) python = sys.executable os.execl(python, python, * sys.argv) return @@ -753,7 +755,7 @@ def main(): if args.daemon and os.name != 'nt': print if filewritable('PID file', args.pid): - daemon = Daemonize(app='gigasetelements-cli', pid=args.pid, action=base, auto_close_fds=False) + daemon = Daemonize(app='gigasetelements-cli', pid=args.pid, action=base, auto_close_fds=False, chdir=os.path.dirname(os.path.abspath(sys.argv[0]))) daemon.start() else: base() From 642d677da233ce761ab567ecdf1ac788613b5d33 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 21 Apr 2016 09:59:43 +0200 Subject: [PATCH 026/107] add --force to facilitate develop branch based updates --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 65cfb6c..d965f32 100644 --- a/README.rst +++ b/README.rst @@ -25,7 +25,7 @@ For easy installation including dependencies simply run below command (with elev [2] install *dependencies*, pip install -r requirements.txt (with elevated privileges if needed) -[3] *python setup.py install* (or run from source using wrapper ./gigasetelements-cli.py) +[3] *python setup.py install --force* (or run from source using wrapper ./gigasetelements-cli.py) Features From 720967cf3c568294753f1ff957924b0ddb9d2326 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sun, 8 May 2016 09:04:46 +0200 Subject: [PATCH 027/107] point url to 1.5 release --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index cb9c1d3..015d418 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -46,7 +46,7 @@ URL_HEALTH = 'https://api.gigaset-elements.de/api/v2/me/health' URL_CHANNEL = 'https://api.gigaset-elements.de/api/v1/me/notifications/users/channels' URL_RELEASE = 'https://pypi.python.org/pypi/gigasetelements-cli/json' -URL_USAGE = 'https://goo.gl/wjLswA' +URL_USAGE = 'https://goo.gl/oHJ565' URL_SWITCH = '/json.htm?type=command¶m=switchlight&switchcmd=' URL_ALERT = '/json.htm?type=command¶m=udevice&idx=' From 6beca388d1b3468b50621eff440dfc7f234fa13b Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 10 May 2016 09:23:33 +0200 Subject: [PATCH 028/107] rework authentication flow --- gigasetelements/gigasetelements.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 015d418..4e1d542 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -263,15 +263,15 @@ def rest(method, url, payload=None, header=False, timeout=90, end=1, silent=Fals def authenticate(reauthenticate=False): """Gigaset Elements API authentication.""" auth_time = time.time() + auth_type = 'Re-authentication' payload = {'password': args.password, 'email': args.username} commit_data = rest(POST, URL_IDENTITY, payload) - rest(GET, URL_AUTH) - if reauthenticate: - log('Re-authentication'.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') - else: + if not reauthenticate: log('Identity'.ljust(17) + ' | ' + color('verified') + ' | ' + commit_data['message']) - rest(HEAD, URL_USAGE, None, False, 2, 0, True) - log('Authentication'.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') + auth_type = auth_type[3:].title() + rest(GET, URL_AUTH) + log(auth_type.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') + rest(HEAD, URL_USAGE, None, False, 2, 0, True) return auth_time From 7b005e7d33adfa3b8b00d4facccd96aaa93693e2 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 10 May 2016 09:37:45 +0200 Subject: [PATCH 029/107] add XDG Base Directory Specification as config file location --- gigasetelements/gigasetelements.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 4e1d542..a8fc84a 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -192,6 +192,7 @@ def configure(): if args.config is None: locations = ['/opt/etc/gigasetelements-cli.conf', '/usr/local/etc/gigasetelements-cli.conf', '/usr/etc/gigasetelements-cli.conf', '/etc/gigasetelements-cli.conf', os.path.expanduser('~/.gigasetelements-cli/gigasetelements-cli.conf'), NTCONFIG, + os.path.expanduser('~/.config/gigasetelements-cli/gigasetelements-cli.conf'), os.path.expanduser('~/.config/gigasetelements-cli.conf'), os.path.expanduser('~/Library/Application Support/gigasetelements-cli/gigasetelements-cli.conf')] for i in locations: if os.path.exists(i): From e5d87890823564f68f0df50ad44afd78d20c87f9 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 9 Jun 2016 11:25:21 +0200 Subject: [PATCH 030/107] delete crontab entries based on command --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index a8fc84a..4513432 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -432,10 +432,10 @@ def remove_cron(): for i in existing: log('Cronjob'.ljust(17) + ' | ' + color('removed'.ljust(8)) + ' | ' + str(i)) count += 1 - cron.remove_all(comment='gigasetelements-cli') if count == 0: log('Cronjob'.ljust(17) + ' | ' + color('warning'.ljust(8)) + ' | ' + 'No items found for removal') else: + cron.remove_all(command='gigasetelements-cli') cron.write() return From 8110e2aee9b7585297a39c32472737324d83d08d Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 9 Aug 2016 20:33:27 +0200 Subject: [PATCH 031/107] request fresh snapshot image --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 4513432..28b0afe 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -641,7 +641,7 @@ def getsnapshot(camera_data, sensor_exist): image_name = 'snapshot_' + time.strftime('%y%m%d') + '_' + time.strftime('%H%M%S') + '.jpg' if filewritable('Snapshot image', image_name, 0): log('Camera snapshot'.ljust(17) + ' | ' + color('download'.ljust(8)) + ' | ' + image_name) - snapshot = s.get(URL_CAMERA + '/' + str(camera_data[0]['id']) + '/snapshot') + snapshot = s.get(URL_CAMERA + '/' + str(camera_data[0]['id']) + '/snapshot?fresh=true') with open(image_name, 'wb') as image: image.write(snapshot.content) return From 348dbe619b9bf7ab80fa06d427f024636f208a0f Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 8 Nov 2016 17:12:52 +0100 Subject: [PATCH 032/107] use selector switch for button --- gigasetelements/gigasetelements.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 28b0afe..a1f21ea 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -32,7 +32,7 @@ _AUTHOR_ = 'dynasticorpheus@gmail.com' -_VERSION_ = '1.5.0b1' +_VERSION_ = '1.5.0b2' LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4'} OPTDEF = {'username': None, 'password': None, 'modus': None, 'pbtoken': None, 'silent': 'False', 'noupdate': 'False', 'insecure': 'False'} @@ -536,12 +536,14 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): def domoticz(event, sid, friendly, basestation_data, url_domo, cfg_domo): """Push events to domoticz server.""" - if event in ['open', 'close', 'sirenon', 'sirenoff', 'on', 'off', 'movement', 'motion', 'button1', 'button2', 'button3', 'button4']: + if event in ['open', 'close', 'sirenon', 'sirenoff', 'on', 'off', 'movement', 'motion']: if event in ['close', 'sirenoff', 'off']: cmd = 'off' else: cmd = 'on' rest(GET, url_domo + URL_SWITCH + cmd.title() + '&idx=' + cfg_domo[sid]) + elif event in ['button1', 'button2', 'button3', 'button4']: + rest(GET, url_domo + URL_ALERT + cfg_domo[sid] + '&nvalue=1' + '&svalue=' + event[-1:] + '0') else: status_data = rest(GET, URL_HEALTH) rest(GET, url_domo + URL_ALERT + cfg_domo[basestation_data[0]['id'].lower()] + '&nvalue=' + From 844f5324c1e48f89ba61ffebfb616eb9b092b010 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Wed, 9 Nov 2016 07:23:39 +0100 Subject: [PATCH 033/107] adjust version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d969c5b..77e51ea 100755 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setup( name='gigasetelements-cli', - version='1.5.0b1', + version='1.5.0b2', description='gigasetelements-cli allows you to control your \ Gigaset Elements home security system from the command line.', long_description=long_description, From 1fc345651c0469b404402ef3e627bc1693f2550f Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 11 Nov 2016 05:52:10 +0100 Subject: [PATCH 034/107] lazy load pushbullet.py --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index a1f21ea..3d470bb 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -17,7 +17,6 @@ try: from colorama import init, Fore - from pushbullet import PushBullet, InvalidKeyError, PushbulletError import requests import unidecode except ImportError as error: @@ -442,6 +441,7 @@ def remove_cron(): def pb_message(pbmsg): """Send message using pushbullet module.""" + from pushbullet import PushBullet, InvalidKeyError, PushbulletError if args.notify is not None and args.quiet is not True: try: pushb = PushBullet(args.notify) From 833b461f97d5b1ff4c9b2d5b99cfd739dbf2f020 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sat, 12 Nov 2016 09:29:57 +0100 Subject: [PATCH 035/107] check args outside function --- gigasetelements/gigasetelements.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 3d470bb..8da8467 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -442,16 +442,15 @@ def remove_cron(): def pb_message(pbmsg): """Send message using pushbullet module.""" from pushbullet import PushBullet, InvalidKeyError, PushbulletError - if args.notify is not None and args.quiet is not True: - try: - pushb = PushBullet(args.notify) - except InvalidKeyError: - log('Notification'.ljust(17) + ' | ' + color('token'.ljust(8)) + ' | ') - except PushbulletError: - log('Notification'.ljust(17) + ' | ' + color('error'.ljust(8)) + ' | ') - else: - pushb.push_note('Gigaset Elements', pbmsg) - log('Notification'.ljust(17) + ' | ' + color('pushed'.ljust(8)) + ' | ') + try: + pushb = PushBullet(args.notify) + except InvalidKeyError: + log('Notification'.ljust(17) + ' | ' + color('token'.ljust(8)) + ' | ') + except PushbulletError: + log('Notification'.ljust(17) + ' | ' + color('error'.ljust(8)) + ' | ') + else: + pushb.push_note('Gigaset Elements', pbmsg) + log('Notification'.ljust(17) + ' | ' + color('pushed'.ljust(8)) + ' | ') return @@ -736,7 +735,7 @@ def base(): if args.plug: plug(basestation_data, sensor_exist, sensor_id) - if pb_body is not None: + if not args.quiet and None not in (args.notify, pb_body): pb_message(pb_body) if args.events is None and args.date is None: From b7640a6cc90ccd5baa3efd22361d6d10c86bfed8 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sun, 13 Nov 2016 09:02:02 +0100 Subject: [PATCH 036/107] version bump to 1.5.0b3 --- gigasetelements/gigasetelements.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 8da8467..b1f02fb 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -31,7 +31,7 @@ _AUTHOR_ = 'dynasticorpheus@gmail.com' -_VERSION_ = '1.5.0b2' +_VERSION_ = '1.5.0b3' LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4'} OPTDEF = {'username': None, 'password': None, 'modus': None, 'pbtoken': None, 'silent': 'False', 'noupdate': 'False', 'insecure': 'False'} diff --git a/setup.py b/setup.py index 77e51ea..3b7a719 100755 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setup( name='gigasetelements-cli', - version='1.5.0b2', + version='1.5.0b3', description='gigasetelements-cli allows you to control your \ Gigaset Elements home security system from the command line.', long_description=long_description, From f115a6a44531e0eea53c35b81d5ad261553824a1 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 15 Nov 2016 10:02:00 +0100 Subject: [PATCH 037/107] periodically sync base selector and alert switch --- gigasetelements-cli.conf.template | 22 +++++++++++++--------- gigasetelements/gigasetelements.py | 26 +++++++++++++++++++++----- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/gigasetelements-cli.conf.template b/gigasetelements-cli.conf.template index 6ad1690..eaebf1d 100644 --- a/gigasetelements-cli.conf.template +++ b/gigasetelements-cli.conf.template @@ -61,19 +61,23 @@ password= # domoticz idx sensor pairing (required) -# 1. Go to Domoticz GUI -# 2. Add hardware - dummy -# 3. Create a virtual sensor - type Alert for basestation (remember the idx number) -# 4. Create a virtual sensor - type Manual Light/Switch for each motion/door/window/siren/plug/button/camera (remember their idx numbers) -# 5. Set Off Delay to 2 seconds for virtual sensors linked to motion, button or camera -# 6. Run "gigasetelements-cli -ss" to get sensor ids and match them with the their respective idx in domoticz -# 7. Run "gigasetelements-cli -a" to get camera sensor id. Use the 12 uppercase digits in stream url. This MAC address can also be found on the back of the camera. +# [01] Go to Domoticz GUI +# [02] Add hardware - dummy +# [03] Create a virtual sensor - type [General, Alert] for basestation (health) (remember the idx number) +# [04] Create a second virtual sensor - type [Light/Switch, Selector Switch] for basestation (modus) (remember the idx number) +# [05] Set level 10 = Home, 20 = Custom, 30 = Away and enable option Hide Off level +# [06] Create a virtual sensor - type [Light/Switch, Switch] for each motion/door/window/siren/plug/camera (remember their idx numbers) +# [07] Create a virtual sensor - type [Light/Switch, Selector Switch] for button (remember the idx number) +# [08] Set level 10 = Short, 20 = Double, 30 = Long, 40 = Very long and enable option Hide Off level +# [09] Set Off Delay to 2 seconds for virtual sensors linked to motion, button or camera +# [10] Run "gigasetelements-cli -ss" to get sensor ids and match them with the their respective idx in domoticz +# [11] Run "gigasetelements-cli -a" to get camera sensor id. Use the 12 uppercase digits in stream url. This MAC address can also be found on the back of the camera. # example motion/door/window/siren/plug # 123FC4577H=99 -# example basestation id -# F32A76C4DHJ1B743A0E0D74EFD2375D1=98 +# example basestation id (alert idx,selector idx) +# F32A76C4DHJ1B743A0E0D74EFD2375D1=98,96 # example camera id # 7C2G30873SED=97 diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index b1f02fb..582faba 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -33,7 +33,8 @@ _AUTHOR_ = 'dynasticorpheus@gmail.com' _VERSION_ = '1.5.0b3' -LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4'} +LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4', 'home': '10', + 'custom': '20', 'away': '30'} OPTDEF = {'username': None, 'password': None, 'modus': None, 'pbtoken': None, 'silent': 'False', 'noupdate': 'False', 'insecure': 'False'} AUTH_EXPIRE = 14400 @@ -487,22 +488,35 @@ def list_events(): def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): """List events realtime optionally filtered by type.""" + health = '' + modus = '' + epoch = time.time() - 60 if args.filter is None: url_monitor = URL_EVENTS + '?limit=10' else: url_monitor = URL_EVENTS + '?limit=10&group=' + args.filter if args.monitor > 1: mode = 'Domoticz mode' - print rest(GET, url_domo + URL_LOG + 'Gigaset Elements - Command-line Interface: Domoticz mode started') - domoticz(status_data['system_health'].lower(), basestation_data[0]['id'].lower(), basestation_data[0] - ['friendly_name'].lower(), basestation_data, url_domo, cfg_domo) else: mode = 'Monitor mode' log(mode.ljust(17) + ' | ' + color('started'.ljust(8)) + ' | ' + 'CTRL+C to exit') from_ts = str(int(time.time()) * 1000) + print '\n' try: while 1: + if args.monitor > 1 and time.time() - epoch > 59: + status_data = rest(GET, URL_HEALTH) + if health != status_data['system_health'].lower(): + domoticz(status_data['system_health'].lower(), basestation_data[0]['id'].lower(), + basestation_data[0]['friendly_name'].lower(), basestation_data, url_domo, cfg_domo) + health = status_data['system_health'].lower() + basestation_data = rest(GET, URL_BASE) + if modus != basestation_data[0]['intrusion_settings']['active_mode']: + domoticz(basestation_data[0]['intrusion_settings']['active_mode'].lower(), basestation_data[0]['id'].lower(), + basestation_data[0]['friendly_name'].lower(), basestation_data, url_domo, cfg_domo) + modus = basestation_data[0]['intrusion_settings']['active_mode'] + epoch = time.time() lastevents = rest(GET, url_monitor + '&from_ts=' + from_ts) for item in reversed(lastevents['events']): try: @@ -543,9 +557,11 @@ def domoticz(event, sid, friendly, basestation_data, url_domo, cfg_domo): rest(GET, url_domo + URL_SWITCH + cmd.title() + '&idx=' + cfg_domo[sid]) elif event in ['button1', 'button2', 'button3', 'button4']: rest(GET, url_domo + URL_ALERT + cfg_domo[sid] + '&nvalue=1' + '&svalue=' + event[-1:] + '0') + elif event in ['home', 'custom', 'away']: + rest(GET, url_domo + URL_ALERT + cfg_domo[basestation_data[0]['id'].lower()].split(',')[1] + '&nvalue=1' + '&svalue=' + LEVEL.get(event)) else: status_data = rest(GET, URL_HEALTH) - rest(GET, url_domo + URL_ALERT + cfg_domo[basestation_data[0]['id'].lower()] + '&nvalue=' + + rest(GET, url_domo + URL_ALERT + cfg_domo[basestation_data[0]['id'].lower()].split(',')[0] + '&nvalue=' + LEVEL.get(status_data['system_health'], '3') + '&svalue=' + friendly + ' | ' + event) sys.stdout.write('\033[F') sys.stdout.write('\033[K') From e133e63e64cf801f4f88540799cb4a46d454858f Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 8 Sep 2017 13:04:42 +0200 Subject: [PATCH 038/107] add night modus --- gigasetelements-cli.conf.template | 2 +- gigasetelements/gigasetelements.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gigasetelements-cli.conf.template b/gigasetelements-cli.conf.template index eaebf1d..a6ddbfb 100644 --- a/gigasetelements-cli.conf.template +++ b/gigasetelements-cli.conf.template @@ -65,7 +65,7 @@ password= # [02] Add hardware - dummy # [03] Create a virtual sensor - type [General, Alert] for basestation (health) (remember the idx number) # [04] Create a second virtual sensor - type [Light/Switch, Selector Switch] for basestation (modus) (remember the idx number) -# [05] Set level 10 = Home, 20 = Custom, 30 = Away and enable option Hide Off level +# [05] Set level 10 = Home, 20 = Custom, 30 = Away, 40 = Night and enable option Hide Off level # [06] Create a virtual sensor - type [Light/Switch, Switch] for each motion/door/window/siren/plug/camera (remember their idx numbers) # [07] Create a virtual sensor - type [Light/Switch, Selector Switch] for button (remember the idx number) # [08] Set level 10 = Short, 20 = Double, 30 = Long, 40 = Very long and enable option Hide Off level diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 582faba..712528e 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -34,7 +34,7 @@ _VERSION_ = '1.5.0b3' LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4', 'home': '10', - 'custom': '20', 'away': '30'} + 'custom': '20', 'away': '30', 'night': '40'} OPTDEF = {'username': None, 'password': None, 'modus': None, 'pbtoken': None, 'silent': 'False', 'noupdate': 'False', 'insecure': 'False'} AUTH_EXPIRE = 14400 @@ -63,7 +63,7 @@ parser.add_argument('-x', '--remove', help='remove all cron jobs linked to this program', action='store_true', required=False) parser.add_argument('-f', '--filter', help='filter events on type', required=False, choices=( 'door', 'window', 'motion', 'siren', 'plug', 'button', 'homecoming', 'intrusion', 'systemhealth', 'camera', 'phone', 'smoke')) -parser.add_argument('-m', '--modus', help='set modus', required=False, choices=('home', 'away', 'custom')) +parser.add_argument('-m', '--modus', help='set modus', required=False, choices=('home', 'away', 'custom', 'night')) parser.add_argument('-k', '--delay', help='set alarm timer delay in seconds (use 0 to disable)', type=int, required=False) parser.add_argument('-D', '--daemon', help='daemonize during monitor/domoticz mode', action='store_true', required=False) parser.add_argument('-z', '--notifications', help='show notification status', action='store_true', required=False) @@ -363,7 +363,7 @@ def siren(basestation_data, sensor_exist): """Dis(arm) siren.""" if not sensor_exist['indoor_siren']: log('Siren'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) - moduslist = ['home', 'away', 'custom'] + moduslist = ['home', 'away', 'custom', 'night'] if args.siren == 'disarm': for modus in moduslist: switch = {"intrusion_settings": {"modes": [{modus: {"sirens_on": False}}]}} @@ -557,7 +557,7 @@ def domoticz(event, sid, friendly, basestation_data, url_domo, cfg_domo): rest(GET, url_domo + URL_SWITCH + cmd.title() + '&idx=' + cfg_domo[sid]) elif event in ['button1', 'button2', 'button3', 'button4']: rest(GET, url_domo + URL_ALERT + cfg_domo[sid] + '&nvalue=1' + '&svalue=' + event[-1:] + '0') - elif event in ['home', 'custom', 'away']: + elif event in ['home', 'custom', 'away', 'night']: rest(GET, url_domo + URL_ALERT + cfg_domo[basestation_data[0]['id'].lower()].split(',')[1] + '&nvalue=1' + '&svalue=' + LEVEL.get(event)) else: status_data = rest(GET, URL_HEALTH) From 547172a0e7ddd5504b08dfe98f33e14b19496d61 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 8 Sep 2017 15:06:14 +0200 Subject: [PATCH 039/107] default to monitoring mode in case of invalid domoticz configuration --- gigasetelements/gigasetelements.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 712528e..87841e3 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -212,6 +212,7 @@ def configure(): url_domo = 'http://' + authstring + cfg_domo['ip'] + ':' + cfg_domo['port'] except Exception: log('Configuration'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Domoticz setting(s) incorrect and/or missing', 3, 1) + cfg_domo = False log('Configuration'.ljust(17) + ' | ' + color('loaded'.ljust(8)) + ' | ' + args.config) args.noupdate, credfromfile = load_option(args.noupdate, 'options', 'noupdate') args.silent, credfromfile = load_option(args.silent, 'options', 'silent') @@ -495,11 +496,12 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): url_monitor = URL_EVENTS + '?limit=10' else: url_monitor = URL_EVENTS + '?limit=10&group=' + args.filter - if args.monitor > 1: + if cfg_domo and args.monitor > 1: mode = 'Domoticz mode' rest(GET, url_domo + URL_LOG + 'Gigaset Elements - Command-line Interface: Domoticz mode started') else: mode = 'Monitor mode' + args.monitor = 1 log(mode.ljust(17) + ' | ' + color('started'.ljust(8)) + ' | ' + 'CTRL+C to exit') from_ts = str(int(time.time()) * 1000) print '\n' From e048b2965325fa768c658bde02fe401ffaf35ea2 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 11 Sep 2017 14:14:03 +0200 Subject: [PATCH 040/107] python 2-3 compatible code --- gigasetelements/gigasetelements.py | 101 +++++++++++++++-------------- requirements.txt | 2 + setup.py | 11 +++- 3 files changed, 61 insertions(+), 53 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 87841e3..2ff223f 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -4,17 +4,22 @@ """Main code for gigasetelements command-line interface.""" +from __future__ import (absolute_import, division, print_function, unicode_literals) import os import sys import time import datetime -import urlparse import argparse import json -import ConfigParser import logging +from builtins import (dict, int, str, open) +from future.moves.urllib.parse import urlparse + +import configparser + + try: from colorama import init, Fore import requests @@ -31,7 +36,7 @@ _AUTHOR_ = 'dynasticorpheus@gmail.com' -_VERSION_ = '1.5.0b3' +_VERSION_ = '1.5.0b4' LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4', 'home': '10', 'custom': '20', 'away': '30', 'night': '40'} @@ -86,7 +91,7 @@ parser.add_argument('-v', '--version', help='show version', action='version', version='%(prog)s version ' + str(_VERSION_)) args = parser.parse_args() -config = ConfigParser.ConfigParser(defaults=OPTDEF) +config = configparser.ConfigParser(defaults=OPTDEF) init(autoreset=True) if os.name == 'nt': @@ -116,27 +121,29 @@ def restart_program(): def log(logme, rbg=0, exitnow=0, newline=1): """Print output in selected color and provide program exit on critical error.""" - if os.name == 'nt': - logme = unidecode.unidecode(unicode(logme)) + if sys.version_info[0] < 3: + logme = unicode(logme) + if os.name == 'nt' or args.log is not None: + logme = unidecode.unidecode(logme) if args.log is not None: logger = logging.getLogger(__name__) - logger.info('[' + time.strftime('%c') + '] ' + unidecode.unidecode(unicode(logme))) + logger.info('[' + time.strftime('%c') + '] ' + logme) if rbg == 1: - print Fore.GREEN + '[-] ' + logme.encode('utf-8') + print(Fore.GREEN + '[-] ' + logme) elif rbg == 2: - print Fore.YELLOW + '[-] ' + logme.encode('utf-8') + print(Fore.YELLOW + '[-] ' + logme) elif rbg == 3: - print Fore.RED + '[-] ' + logme.encode('utf-8') + print(Fore.RED + '[-] ' + logme) else: if newline == 1: - print '[-] ' + logme.encode('utf-8') + print('[-] ' + logme) else: - print '[-] ' + logme.encode('utf-8'), + print('[-] ' + logme, end=' ') if exitnow == 1 and args.restart: - print + print() restart_program() if exitnow == 1: - print + print() sys.exit() return @@ -253,7 +260,7 @@ def rest(method, url, payload=None, header=False, timeout=90, end=1, silent=Fals if request is not None: if not silent: if request.status_code != requests.codes.ok: # pylint: disable=no-member - urlsplit = urlparse.urlparse(request.url) + urlsplit = urlparse(request.url) log('HTTP ERROR'.ljust(17) + ' | ' + str(request.status_code).ljust(8) + ' | ' + request.reason + ' ' + str(urlsplit.path), 3, end) try: data = request.json() @@ -346,17 +353,12 @@ def modus_switch(basestation_data, status_data): def set_delay(basestation_data): """Set alarm trigger delay.""" - linfo = '' - delay = str(args.delay * 1000) + switch = {"intrusion_settings": {"modes": [{"away": {"trigger_delay": str(args.delay * 1000)}}]}} + rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) if args.delay > 0: - sinfo = 'delayed' - linfo = str(args.delay) + ' seconds' + log('Alarm timer'.ljust(17) + ' | ' + color(('delayed').ljust(8)) + ' | ' + str(args.delay) + ' seconds') else: - sinfo = 'normal' - linfo = 'No delay' - switch = {"intrusion_settings": {"modes": [{"away": {"trigger_delay": delay}}]}} - rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) - log('Alarm timer'.ljust(17) + ' | ' + color((sinfo).ljust(8)) + ' | ' + linfo) + log('Alarm timer'.ljust(17) + ' | ' + color(('normal').ljust(8)) + ' | ' + 'No delay') return @@ -504,7 +506,7 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): args.monitor = 1 log(mode.ljust(17) + ' | ' + color('started'.ljust(8)) + ' | ' + 'CTRL+C to exit') from_ts = str(int(time.time()) * 1000) - print '\n' + print('\n') try: while 1: if args.monitor > 1 and time.time() - epoch > 59: @@ -578,14 +580,14 @@ def sensor(basestation_data): try: log(item['friendly_name'].ljust(17) + ' | ' + color(item['status'].ljust(8)) + ' | firmware ' + color(item['firmware_status']), 0, 0, 0) if item['type'] not in ['is01', 'sp01']: - print '| battery ' + color(item['battery']['state']), + print('| battery ' + color(item['battery']['state']), end=' ') if item['type'] in ['ds02', 'ds01']: - print '| position ' + color(item['position_status']), + print('| position ' + color(item['position_status']), end=' ') if args.sensor > 1: - print '| ' + item['id'].upper(), - print + print('| ' + item['id'].upper(), end=' ') + print() except KeyError: - print + print() continue return @@ -610,10 +612,10 @@ def notifications(): channels = rest(GET, URL_CHANNEL) for item in channels.get('gcm', ''): try: - print('[-] ' + item['friendlyName'].ljust(17) + ' | ' + color(item['status'].ljust(8)) + ' |'), + print(('[-] ' + item['friendlyName'].ljust(17) + ' | ' + color(item['status'].ljust(8)) + ' |'), end=' ') for item2 in item['notificationGroups']: - print item2, - print + print(item2, end=' ') + print() except KeyError: continue return @@ -624,15 +626,16 @@ def camera_info(camera_data, sensor_exist): if not sensor_exist['camera']: log('Camera'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) try: - print '[-] ' + camera_data[0]['friendly_name'].ljust( - 17) + ' | ' + color(camera_data[0]['status'].ljust(8)) + ' | firmware ' + color(camera_data[0]['firmware_status']), - print('| quality ' + color(camera_data[0]['settings']['quality']) + ' | nightmode ' + - color(camera_data[0]['settings']['nightmode']) + ' | mic ' + color(camera_data[0]['settings']['mic'])), - print('| motion detection ' + color(camera_data[0]['motion_detection']['status']) + ' | connection ' + color(camera_data[0]['settings']['connection'])), + print('[-] ' + camera_data[0]['friendly_name'].ljust( + 17) + ' | ' + color(camera_data[0]['status'].ljust(8)) + ' | firmware ' + color(camera_data[0]['firmware_status']), end=' ') + print(('| quality ' + color(camera_data[0]['settings']['quality']) + ' | nightmode ' + + color(camera_data[0]['settings']['nightmode']) + ' | mic ' + color(camera_data[0]['settings']['mic'])), end=' ') + print(('| motion detection ' + color(camera_data[0]['motion_detection']['status']) + ' | connection ' + + color(camera_data[0]['settings']['connection'])), end=' ') if camera_data[0]['settings']['connection'] == 'wifi': - print '| ssid ' + Fore.GREEN + str(camera_data[0]['wifi_ssid']).upper() + print('| ssid ' + Fore.GREEN + str(camera_data[0]['wifi_ssid']).upper()) except KeyError: - print + print() stream_data = rest(GET, URL_CAMERA + '/' + camera_data[0]['id'] + '/liveview/start') for stream in ('m3u8', 'rtmp', 'rtsp'): log('Camera stream'.ljust(17) + ' | ' + stream.upper().ljust(8) + ' | ' + stream_data['uri'][stream]) @@ -674,8 +677,8 @@ def start_logger(logfile): try: filehandle = logging.FileHandler(logfile, 'a') except IOError: - print Fore.RED + '[-] Unable to write log file ' + logfile - print + print(Fore.RED + '[-] Unable to write log file ' + logfile) + print() sys.exit() filehandle.setLevel(logging.INFO) logger.addHandler(filehandle) @@ -686,9 +689,7 @@ def start_logger(logfile): def base(): """Base program.""" pb_body = None - print - print 'Gigaset Elements - Command-line Interface v' + _VERSION_ - print + print('\n' + 'Gigaset Elements - Command-line Interface v' + _VERSION_ + '\n') try: if args.log: start_logger(args.log) @@ -700,12 +701,12 @@ def base(): if args.cronjob is not None: add_cron(credfromfile) - print + print() sys.exit() if args.remove and args.cronjob is None: remove_cron() - print + print() sys.exit() check_version() @@ -725,7 +726,7 @@ def base(): if args.sensor: sensor(basestation_data) if status_data['status_msg_id'] == '': - status_data['status_msg_id'] = u'\u2713' + status_data['status_msg_id'] = '\u2713' pb_body = 'Status ' + status_data['system_health'].upper() + ' | ' + status_data['status_msg_id'].upper() + \ ' | Modus ' + basestation_data[0]['intrusion_settings']['active_mode'].upper() @@ -764,7 +765,7 @@ def base(): if args.monitor: monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo) - print + print() except KeyboardInterrupt: log('Program'.ljust(17) + ' | ' + color('halted'.ljust(8)) + ' | ' + 'CTRL+C') @@ -773,7 +774,7 @@ def base(): def main(): """Main program.""" if args.daemon and os.name != 'nt': - print + print() if filewritable('PID file', args.pid): daemon = Daemonize(app='gigasetelements-cli', pid=args.pid, action=base, auto_close_fds=False, chdir=os.path.dirname(os.path.abspath(sys.argv[0]))) daemon.start() diff --git a/requirements.txt b/requirements.txt index 5a7f319..7141057 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ +configparser>=3.5.0 +future>=0.15.2 requests>=2.9.1 colorama>=0.3.7 python-crontab>=2.0.1 diff --git a/setup.py b/setup.py index 3b7a719..8d63d60 100755 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ with codecs.open(os.path.join(HERE, 'DESCRIPTION.rst'), encoding='utf-8') as f: long_description = f.read() -packagelist = ['requests', 'pushbullet.py', 'unidecode', 'colorama'] +packagelist = ['future', 'requests', 'pushbullet.py', 'unidecode', 'colorama', 'configparser'] if os.name == 'posix': packagelist.append('python-crontab') @@ -23,7 +23,7 @@ setup( name='gigasetelements-cli', - version='1.5.0b3', + version='1.5.0b4', description='gigasetelements-cli allows you to control your \ Gigaset Elements home security system from the command line.', long_description=long_description, @@ -37,8 +37,13 @@ 'Intended Audience :: End Users/Desktop', 'Topic :: Software Development :: Build Tools', 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', - 'Programming Language :: Python :: 2', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6' ], keywords='Home Automation, Home Security, Internet of Things (IoT)', From 38374f9eb569a8f499b025d767c6b45802ff47c9 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 11 Sep 2017 20:01:52 +0200 Subject: [PATCH 041/107] reduce nesting log function --- gigasetelements/gigasetelements.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 2ff223f..c3386db 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -13,13 +13,11 @@ import argparse import json import logging +import configparser from builtins import (dict, int, str, open) from future.moves.urllib.parse import urlparse -import configparser - - try: from colorama import init, Fore import requests @@ -38,6 +36,7 @@ _AUTHOR_ = 'dynasticorpheus@gmail.com' _VERSION_ = '1.5.0b4' +LOGCL = {0: Fore.RESET, 1: Fore.GREEN, 2: Fore.YELLOW, 3: Fore.RED} LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4', 'home': '10', 'custom': '20', 'away': '30', 'night': '40'} OPTDEF = {'username': None, 'password': None, 'modus': None, 'pbtoken': None, 'silent': 'False', 'noupdate': 'False', 'insecure': 'False'} @@ -128,17 +127,10 @@ def log(logme, rbg=0, exitnow=0, newline=1): if args.log is not None: logger = logging.getLogger(__name__) logger.info('[' + time.strftime('%c') + '] ' + logme) - if rbg == 1: - print(Fore.GREEN + '[-] ' + logme) - elif rbg == 2: - print(Fore.YELLOW + '[-] ' + logme) - elif rbg == 3: - print(Fore.RED + '[-] ' + logme) + if newline == 1: + print(LOGCL[rbg] + '[-] ' + logme) else: - if newline == 1: - print('[-] ' + logme) - else: - print('[-] ' + logme, end=' ') + print(LOGCL[rbg] + '[-] ' + logme, end=' ') if exitnow == 1 and args.restart: print() restart_program() From c8c12b07d15acde75effb0bf9521ab98eae459a3 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 11 Sep 2017 20:08:32 +0200 Subject: [PATCH 042/107] requests no longer has vendored modules in request.package --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index c3386db..fb18e3a 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -224,7 +224,7 @@ def configure(): log('Configuration'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Username and/or password missing', 3, 1) if args.silent: try: - requests.packages.urllib3.disable_warnings() + requests.urllib3.disable_warnings() except Exception: pass return url_domo, cfg_domo, credfromfile From 7fccb7061ecbd8f8383146a0de283914c1f2b4ba Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 12 Sep 2017 10:15:48 +0200 Subject: [PATCH 043/107] ensure --silent works for older requests versions --- gigasetelements/gigasetelements.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index fb18e3a..76164fd 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -17,6 +17,7 @@ from builtins import (dict, int, str, open) from future.moves.urllib.parse import urlparse +from requests.packages.urllib3 import disable_warnings try: from colorama import init, Fore @@ -224,7 +225,7 @@ def configure(): log('Configuration'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Username and/or password missing', 3, 1) if args.silent: try: - requests.urllib3.disable_warnings() + disable_warnings() except Exception: pass return url_domo, cfg_domo, credfromfile From 1d305988501801d1f8380ea9c2aee93c732fe244 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 12 Sep 2017 20:28:37 +0200 Subject: [PATCH 044/107] proost --- README.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 7721953..dec1321 100644 --- a/README.rst +++ b/README.rst @@ -136,9 +136,9 @@ Notes Been ages since I have coded and python is new for me so in other words be kind :) -Donate +Donation Hall of Fame ------ -A lot of time & effort goes into making gigasetelements-cli so if you like it you might want to consider buying me a beer :) +A lot of time & effort goes into making gigasetelements-cli so if you like it you might want to consider buying me a :beer: :) .. image:: http://www.paypal.com/en_US/i/btn/x-click-but04.gif :target: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=FETZ23LK5UH2J&item_number=gigasetelements%2dcli¤cy_code=EUR @@ -151,6 +151,7 @@ A lot of time & effort goes into making gigasetelements-cli so if you like it yo * *Joshua T* * *Auke C* * *RPC B* +* *Silke H* License ------- From d1db74ad819fc74f11de60e68195eed3d1bcb5b8 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sat, 23 Sep 2017 12:55:55 +0200 Subject: [PATCH 045/107] show info of all connected camera's --- gigasetelements/gigasetelements.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 76164fd..4fe0323 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -619,19 +619,18 @@ def camera_info(camera_data, sensor_exist): if not sensor_exist['camera']: log('Camera'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) try: - print('[-] ' + camera_data[0]['friendly_name'].ljust( - 17) + ' | ' + color(camera_data[0]['status'].ljust(8)) + ' | firmware ' + color(camera_data[0]['firmware_status']), end=' ') - print(('| quality ' + color(camera_data[0]['settings']['quality']) + ' | nightmode ' + - color(camera_data[0]['settings']['nightmode']) + ' | mic ' + color(camera_data[0]['settings']['mic'])), end=' ') - print(('| motion detection ' + color(camera_data[0]['motion_detection']['status']) + ' | connection ' + - color(camera_data[0]['settings']['connection'])), end=' ') - if camera_data[0]['settings']['connection'] == 'wifi': - print('| ssid ' + Fore.GREEN + str(camera_data[0]['wifi_ssid']).upper()) + for cam in camera_data: + print('[-] ' + cam['friendly_name'].ljust(17) + ' | ' + color(cam['status'].ljust(8)) + ' | firmware ' + color(cam['firmware_status']), end=' ') + print(('| quality ' + color(cam['settings']['quality']) + ' | nightmode ' + color(cam['settings']['nightmode']) + ' | mic ' + + color(cam['settings']['mic'])), end=' ') + print(('| motion detection ' + color(cam['motion_detection']['status']) + ' | connection ' + color(cam['settings']['connection'])), end=' ') + if cam['settings']['connection'] == 'wifi': + print('| ssid ' + Fore.GREEN + str(cam['wifi_ssid']).upper()) + stream_data = rest(GET, URL_CAMERA + '/' + cam['id'] + '/liveview/start') + for stream in ('m3u8', 'rtmp', 'rtsp'): + log('Camera stream'.ljust(17) + ' | ' + stream.upper().ljust(8) + ' | ' + stream_data['uri'][stream]) except KeyError: print() - stream_data = rest(GET, URL_CAMERA + '/' + camera_data[0]['id'] + '/liveview/start') - for stream in ('m3u8', 'rtmp', 'rtsp'): - log('Camera stream'.ljust(17) + ' | ' + stream.upper().ljust(8) + ' | ' + stream_data['uri'][stream]) return From ba3db4290e8301957c059b0509795636df8f4735 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sat, 23 Sep 2017 13:00:04 +0200 Subject: [PATCH 046/107] fix import and compact code --- gigasetelements/gigasetelements.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 4fe0323..d370584 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -17,10 +17,10 @@ from builtins import (dict, int, str, open) from future.moves.urllib.parse import urlparse -from requests.packages.urllib3 import disable_warnings try: from colorama import init, Fore + from requests.packages.urllib3 import disable_warnings import requests import unidecode except ImportError as error: @@ -95,8 +95,7 @@ init(autoreset=True) if os.name == 'nt': - args.cronjob = None - args.remove = False + args.cronjob, args.remove = None, False NTCONFIG = os.path.join(os.environ['APPDATA'], os.path.normpath('gigasetelements-cli/gigasetelements-cli.conf')) else: NTCONFIG = '' @@ -119,7 +118,7 @@ def restart_program(): return -def log(logme, rbg=0, exitnow=0, newline=1): +def log(logme, rbg=0, exitnow=0, newline=None): """Print output in selected color and provide program exit on critical error.""" if sys.version_info[0] < 3: logme = unicode(logme) @@ -128,15 +127,13 @@ def log(logme, rbg=0, exitnow=0, newline=1): if args.log is not None: logger = logging.getLogger(__name__) logger.info('[' + time.strftime('%c') + '] ' + logme) - if newline == 1: - print(LOGCL[rbg] + '[-] ' + logme) - else: - print(LOGCL[rbg] + '[-] ' + logme, end=' ') - if exitnow == 1 and args.restart: - print() - restart_program() + if newline is not None: + newline = ' ' + print(LOGCL[rbg] + '[-] ' + logme, end=newline) if exitnow == 1: print() + if args.restart: + restart_program() sys.exit() return @@ -181,8 +178,7 @@ def load_option(arg, section, option): fromfile = True elif isinstance(arg, bool): if config.getboolean(section, option): - arg = True - fromfile = True + arg = fromfile = True return arg, fromfile @@ -226,7 +222,7 @@ def configure(): if args.silent: try: disable_warnings() - except Exception: + except NameError: pass return url_domo, cfg_domo, credfromfile @@ -463,7 +459,7 @@ def list_events(): try: from_ts = str(int(time.mktime(time.strptime(args.date[0], '%d/%m/%Y'))) * 1000) to_ts = str(int(time.mktime(time.strptime(args.date[1], '%d/%m/%Y'))) * 1000) - except Exception: + except ValueError: log('Event(s)'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | ' + 'Date(s) filter not in DD/MM/YYYY format', 3, 1) if args.filter is None and args.date is not None: log('Event(s)'.ljust(17) + ' | ' + 'DATE'.ljust(8) + ' | ' + args.date[0] + ' - ' + args.date[1]) @@ -484,8 +480,7 @@ def list_events(): def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): """List events realtime optionally filtered by type.""" - health = '' - modus = '' + health = modus = '' epoch = time.time() - 60 if args.filter is None: url_monitor = URL_EVENTS + '?limit=10' From de7a9831d3411a3206d007f594cbd5811dc006c9 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sun, 24 Sep 2017 09:58:20 +0200 Subject: [PATCH 047/107] move camera info to sensor function and separate cloud stream start --- gigasetelements/gigasetelements.py | 34 ++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index d370584..a7eaba8 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -78,7 +78,7 @@ parser.add_argument('-s', '--sensor', help='''show sensor status (use -ss to include sensor id's)''', action='count', default=0, required=False) parser.add_argument('-b', '--siren', help='arm/disarm siren', required=False, choices=('arm', 'disarm')) parser.add_argument('-g', '--plug', help='switch plug on/off', required=False, choices=('on', 'off')) -parser.add_argument('-a', '--camera', help='show camera status', action='store_true', required=False) +parser.add_argument('-a', '--stream', help='start camera cloud based streams', action='store_true', required=False) parser.add_argument('-r', '--record', help='switch camera recording on/off', action='store_true', required=False) parser.add_argument('-A', '--snapshot', help='download camera snapshot', action='store_true', required=False) parser.add_argument('-t', '--monitor', help='show events using monitor mode (use -tt to activate domoticz mode)', action='count', default=0, required=False) @@ -560,7 +560,7 @@ def domoticz(event, sid, friendly, basestation_data, url_domo, cfg_domo): return -def sensor(basestation_data): +def sensor(basestation_data, sensor_exist, camera_data): """Show sensor details and current state.""" log(basestation_data[0]['friendly_name'].ljust(17) + ' | ' + color(basestation_data[0] ['status'].ljust(8)) + ' | firmware ' + color(basestation_data[0]['firmware_status'])) @@ -577,6 +577,20 @@ def sensor(basestation_data): except KeyError: print() continue + if sensor_exist['camera']: + try: + for cam in camera_data: + print('[-] ' + cam['friendly_name'].ljust(17) + ' | ' + color(cam['status'].ljust(8)) + ' | firmware ' + color(cam['firmware_status']), end=' ') + print(('| quality ' + color(cam['settings']['quality']) + ' | nightmode ' + color(cam['settings']['nightmode']) + ' | mic ' + + color(cam['settings']['mic'])), end=' ') + print(('| motion detection ' + color(cam['motion_detection']['status']) + ' | connection ' + color(cam['settings']['connection'])), end=' ') + if cam['settings']['connection'] == 'wifi': + print('| ssid ' + Fore.GREEN + str(cam['wifi_ssid']).upper(), end=' ') + if args.sensor > 1: + print('| ' + cam['id'].upper(), end=' ') + print() + except KeyError: + print() return @@ -609,21 +623,15 @@ def notifications(): return -def camera_info(camera_data, sensor_exist): +def camera_stream(camera_data, sensor_exist): """Show camera details and current state.""" if not sensor_exist['camera']: log('Camera'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) try: for cam in camera_data: - print('[-] ' + cam['friendly_name'].ljust(17) + ' | ' + color(cam['status'].ljust(8)) + ' | firmware ' + color(cam['firmware_status']), end=' ') - print(('| quality ' + color(cam['settings']['quality']) + ' | nightmode ' + color(cam['settings']['nightmode']) + ' | mic ' + - color(cam['settings']['mic'])), end=' ') - print(('| motion detection ' + color(cam['motion_detection']['status']) + ' | connection ' + color(cam['settings']['connection'])), end=' ') - if cam['settings']['connection'] == 'wifi': - print('| ssid ' + Fore.GREEN + str(cam['wifi_ssid']).upper()) stream_data = rest(GET, URL_CAMERA + '/' + cam['id'] + '/liveview/start') for stream in ('m3u8', 'rtmp', 'rtsp'): - log('Camera stream'.ljust(17) + ' | ' + stream.upper().ljust(8) + ' | ' + stream_data['uri'][stream]) + log(cam['friendly_name'].ljust(17) + ' | ' + stream.upper().ljust(8) + ' | ' + stream_data['uri'][stream]) except KeyError: print() return @@ -711,7 +719,7 @@ def base(): basestation_data[0]['intrusion_settings']['active_mode'].upper() + ' to ' + args.modus.upper() if args.sensor: - sensor(basestation_data) + sensor(basestation_data, sensor_exist, camera_data) if status_data['status_msg_id'] == '': status_data['status_msg_id'] = '\u2713' pb_body = 'Status ' + status_data['system_health'].upper() + ' | ' + status_data['status_msg_id'].upper() + \ @@ -720,8 +728,8 @@ def base(): if args.delay is not None: set_delay(basestation_data) - if args.camera: - camera_info(camera_data, sensor_exist) + if args.stream: + camera_stream(camera_data, sensor_exist) if args.record: record(camera_data, sensor_exist) From eb10f0c33fb74cd3da056784c852ad741d04806c Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sun, 24 Sep 2017 10:12:02 +0200 Subject: [PATCH 048/107] add umos sensor support --- gigasetelements/gigasetelements.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index a7eaba8..6bd49c5 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -67,7 +67,7 @@ parser.add_argument('-o', '--cronjob', help='schedule cron job at HH:MM (requires -m option)', required=False, metavar='HH:MM') parser.add_argument('-x', '--remove', help='remove all cron jobs linked to this program', action='store_true', required=False) parser.add_argument('-f', '--filter', help='filter events on type', required=False, choices=( - 'door', 'window', 'motion', 'siren', 'plug', 'button', 'homecoming', 'intrusion', 'systemhealth', 'camera', 'phone', 'smoke')) + 'door', 'window', 'motion', 'siren', 'plug', 'button', 'homecoming', 'intrusion', 'systemhealth', 'camera', 'phone', 'smoke', 'umos')) parser.add_argument('-m', '--modus', help='set modus', required=False, choices=('home', 'away', 'custom', 'night')) parser.add_argument('-k', '--delay', help='set alarm timer delay in seconds (use 0 to disable)', type=int, required=False) parser.add_argument('-D', '--daemon', help='daemonize during monitor/domoticz mode', action='store_true', required=False) @@ -304,7 +304,7 @@ def check_version(): def collect_hw(basestation_data, camera_data): """Retrieve sensor list and details.""" sensor_id = {} - sensor_exist = dict.fromkeys(['button', 'camera', 'door_sensor', 'indoor_siren', 'presence_sensor', 'smart_plug', 'smoke'], False) + sensor_exist = dict.fromkeys(['button', 'camera', 'door_sensor', 'indoor_siren', 'presence_sensor', 'smart_plug', 'smoke', 'umos'], False) for item in basestation_data[0]['sensors']: sensor_id.setdefault(item['type'], []).append(item['id']) try: @@ -322,6 +322,8 @@ def collect_hw(basestation_data, camera_data): sensor_exist.update(dict.fromkeys(['camera'], True)) if 'sd01' in sensor_id: sensor_exist.update(dict.fromkeys(['smoke'], True)) + if 'um01' in sensor_id: + sensor_exist.update(dict.fromkeys(['umos'], True)) if 'ws02' in sensor_id: sensor_exist.update(dict.fromkeys(['window_sensor'], True)) if 'ps01' in sensor_id or 'ps02' in sensor_id: From fd9adb008ab163a3d8ba58a4136d6c4f10c1c9cb Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 26 Sep 2017 18:57:09 +0200 Subject: [PATCH 049/107] add privacy mode support --- gigasetelements/gigasetelements.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 6bd49c5..e2ee6c4 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -78,6 +78,7 @@ parser.add_argument('-s', '--sensor', help='''show sensor status (use -ss to include sensor id's)''', action='count', default=0, required=False) parser.add_argument('-b', '--siren', help='arm/disarm siren', required=False, choices=('arm', 'disarm')) parser.add_argument('-g', '--plug', help='switch plug on/off', required=False, choices=('on', 'off')) +parser.add_argument('-y', '--privacy', help='switch privacy mode on/off', required=False, choices=('on', 'off')) parser.add_argument('-a', '--stream', help='start camera cloud based streams', action='store_true', required=False) parser.add_argument('-r', '--record', help='switch camera recording on/off', action='store_true', required=False) parser.add_argument('-A', '--snapshot', help='download camera snapshot', action='store_true', required=False) @@ -353,6 +354,13 @@ def set_delay(basestation_data): return +def set_privacy(basestation_data): + """Set privacy mode.""" + switch = {"intrusion_settings": {"modes": [{"home": {"privacy_mode": str(args.privacy in "on").lower()}}]}} + rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) + log('Privacy mode'.ljust(17) + ' | ' + color(args.privacy.ljust(8)) + ' | ') + + def siren(basestation_data, sensor_exist): """Dis(arm) siren.""" if not sensor_exist['indoor_siren']: @@ -730,6 +738,9 @@ def base(): if args.delay is not None: set_delay(basestation_data) + if args.privacy is not None: + set_privacy(basestation_data) + if args.stream: camera_stream(camera_data, sensor_exist) From cbf02fc40857d51122dbd9157a13688318b4c348 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 26 Sep 2017 19:22:35 +0200 Subject: [PATCH 050/107] set privacy mode globally and reapply simplied code for (dis)arm function --- gigasetelements/gigasetelements.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index e2ee6c4..cb1d11c 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -356,9 +356,12 @@ def set_delay(basestation_data): def set_privacy(basestation_data): """Set privacy mode.""" - switch = {"intrusion_settings": {"modes": [{"home": {"privacy_mode": str(args.privacy in "on").lower()}}]}} - rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) + moduslist = ['home', 'custom', 'night'] + for modus in moduslist: + switch = {"intrusion_settings": {"modes": [{modus: {"privacy_mode": str(args.privacy in "on").lower()}}]}} + rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) log('Privacy mode'.ljust(17) + ' | ' + color(args.privacy.ljust(8)) + ' | ') + return def siren(basestation_data, sensor_exist): @@ -366,14 +369,9 @@ def siren(basestation_data, sensor_exist): if not sensor_exist['indoor_siren']: log('Siren'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) moduslist = ['home', 'away', 'custom', 'night'] - if args.siren == 'disarm': - for modus in moduslist: - switch = {"intrusion_settings": {"modes": [{modus: {"sirens_on": False}}]}} - rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) - else: - for modus in moduslist: - switch = {"intrusion_settings": {"modes": [{modus: {"sirens_on": True}}]}} - rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) + for modus in moduslist: + switch = {"intrusion_settings": {"modes": [{modus: {"sirens_on": str(args.siren in "arm").lower()}}]}} + rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) log('Siren'.ljust(17) + ' | ' + color((args.siren + 'ED').ljust(8)) + ' | ') return From 728c6baefcab6e8ede991f97b6bb2c7260b96ccf Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Wed, 27 Sep 2017 20:15:33 +0200 Subject: [PATCH 051/107] use content-type header value allowing rest function to be used in getsnapshot --- gigasetelements/gigasetelements.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index cb1d11c..59e2fbd 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -252,11 +252,14 @@ def rest(method, url, payload=None, header=False, timeout=90, end=1, silent=Fals if request.status_code != requests.codes.ok: # pylint: disable=no-member urlsplit = urlparse(request.url) log('HTTP ERROR'.ljust(17) + ' | ' + str(request.status_code).ljust(8) + ' | ' + request.reason + ' ' + str(urlsplit.path), 3, end) - try: + contenttype = request.headers.get('Content-Type', default='').split(';')[0] + if contenttype == 'application/json': data = request.json() - except ValueError: + elif contenttype == 'image/jpeg': + data = request.content + else: data = request.text - return data + return data def authenticate(reauthenticate=False): @@ -666,9 +669,8 @@ def getsnapshot(camera_data, sensor_exist): image_name = 'snapshot_' + time.strftime('%y%m%d') + '_' + time.strftime('%H%M%S') + '.jpg' if filewritable('Snapshot image', image_name, 0): log('Camera snapshot'.ljust(17) + ' | ' + color('download'.ljust(8)) + ' | ' + image_name) - snapshot = s.get(URL_CAMERA + '/' + str(camera_data[0]['id']) + '/snapshot?fresh=true') with open(image_name, 'wb') as image: - image.write(snapshot.content) + image.write(rest(GET, URL_CAMERA + '/' + str(camera_data[0]['id']) + '/snapshot?fresh=true')) return From e63e5abd10ac4a822a1db948a83ae10ea63b68cb Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sun, 1 Oct 2017 11:58:03 +0200 Subject: [PATCH 052/107] add badges --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index dec1321..64a31f3 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,8 @@ Gigaset Elements API command-line interface =========================================== +|Version Status| |Downloads| + gigasetelements-cli is a python based program which allows you to control your Gigaset Elements home security system. It comes with an easy to use CLI (command-line interface) suitable for direct use or cron jobs. @@ -156,3 +158,8 @@ A lot of time & effort goes into making gigasetelements-cli so if you like it yo License ------- GPL2 + +.. |Version Status| image:: https://img.shields.io/pypi/v/gigasetelements-cli.svg + :target: https://pypi.python.org/pypi/gigasetelements-cli/ +.. |Downloads| image:: https://img.shields.io/pypi/dm/gigasetelements-cli.svg + :target: https://pypi.python.org/pypi/gigasetelements-cli/ From d27da0327127d0129e2465a9a28e7be280ad4b46 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 6 Oct 2017 08:07:22 +0200 Subject: [PATCH 053/107] detect if maintenance is in progress --- gigasetelements/gigasetelements.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 59e2fbd..7cd72f9 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -43,6 +43,7 @@ OPTDEF = {'username': None, 'password': None, 'modus': None, 'pbtoken': None, 'silent': 'False', 'noupdate': 'False', 'insecure': 'False'} AUTH_EXPIRE = 14400 +URL_STATUS = 'https://status.gigaset-elements.de/api/v1/status' URL_IDENTITY = 'https://im.gigaset-elements.de/identity/api/v1/user/login' URL_AUTH = 'https://api.gigaset-elements.de/api/v1/auth/openid/begin?op=gigaset' URL_EVENTS = 'https://api.gigaset-elements.de/api/v2/me/events' @@ -246,14 +247,14 @@ def rest(method, url, payload=None, header=False, timeout=90, end=1, silent=Fals request = method(url, timeout=timeout, headers=header, allow_redirects=True, verify=pem) except requests.exceptions.RequestException as error: if not silent: - log('ERROR'.ljust(17) + ' | ' + 'UNKNOWN'.ljust(8) + ' | ' + str(error.message), 3, end) + log('ERROR'.ljust(17) + ' | ' + 'UNKNOWN'.ljust(8) + ' | ' + str(error), 3, end) if request is not None: if not silent: if request.status_code != requests.codes.ok: # pylint: disable=no-member urlsplit = urlparse(request.url) log('HTTP ERROR'.ljust(17) + ' | ' + str(request.status_code).ljust(8) + ' | ' + request.reason + ' ' + str(urlsplit.path), 3, end) contenttype = request.headers.get('Content-Type', default='').split(';')[0] - if contenttype == 'application/json': + if contenttype == 'application/json' or request.url == URL_STATUS: data = request.json() elif contenttype == 'image/jpeg': data = request.content @@ -264,6 +265,9 @@ def rest(method, url, payload=None, header=False, timeout=90, end=1, silent=Fals def authenticate(reauthenticate=False): """Gigaset Elements API authentication.""" + status_maintenance = rest(GET, URL_STATUS) + if status_maintenance['isMaintenance']: + log('Maintenance'.ljust(17) + ' | ' + 'DETECTED'.ljust(8) + ' | Please try later', 2, 1) auth_time = time.time() auth_type = 'Re-authentication' payload = {'password': args.password, 'email': args.username} From d0e9d323be0910abc52c0f18ecab80913891fe1a Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sun, 8 Oct 2017 11:44:25 +0200 Subject: [PATCH 054/107] simplify code by leveraging configargparse --- gigasetelements-cli.conf.template | 42 +++---- gigasetelements/gigasetelements.py | 182 ++++++++++------------------- requirements.txt | 2 +- setup.py | 4 +- 4 files changed, 77 insertions(+), 153 deletions(-) diff --git a/gigasetelements-cli.conf.template b/gigasetelements-cli.conf.template index a6ddbfb..8439881 100644 --- a/gigasetelements-cli.conf.template +++ b/gigasetelements-cli.conf.template @@ -35,31 +35,29 @@ password=mybigsecret # set modus {home,away,custom} (optional) # modus=home -# disable SSL/TLS certificate verification {true,false} (optional) -# insecure=false +# disable SSL/TLS certificate verification {yes,no} (optional) +# insecure=no -# suppress urllib3 warnings {true,false} (optional) -# silent=false +# suppress urllib3 warnings {yes,no} (optional) +# silent=no -# Don't periodically check PyPI to determine whether a new version of gigasetelements-cli is available for download {true,false} (optional) -# noupdate= false +# Don't periodically check PyPI to determine whether a new version of gigasetelements-cli is available for download {yes,no} (optional) +# noupdate=no [domoticz] -# domoticz server ip address (required) -ip=127.0.0.1 +# http(s) server url including basic authentication credentials if applicable (required) +# url=http://user:password@ip:port +# url=http://ip:port +# url=http://user:pwd@127.0.0.1:8080 +# url=http://127.0.0.1:8080 -# domoticz server port (required) -port=8080 +# delimeted sensor / idx pair list (required) (Note: base needs 2 (comma separated) idx values, see step 3 and 4 below) +# sensorpairs=basesensorid:idx,idx;sensorid:idx;sensorid:idx +# sensorpairs=F32A76C4DHJ1B743A0E0D74EFD2375D1:10,11;123FC4577H:12;7C2G30873SED:13 -# domoticz user name (optional) -username= - -# domoticz password (optional) -password= - -# domoticz idx sensor pairing (required) +# Instructions # [01] Go to Domoticz GUI # [02] Add hardware - dummy @@ -71,13 +69,3 @@ password= # [08] Set level 10 = Short, 20 = Double, 30 = Long, 40 = Very long and enable option Hide Off level # [09] Set Off Delay to 2 seconds for virtual sensors linked to motion, button or camera # [10] Run "gigasetelements-cli -ss" to get sensor ids and match them with the their respective idx in domoticz -# [11] Run "gigasetelements-cli -a" to get camera sensor id. Use the 12 uppercase digits in stream url. This MAC address can also be found on the back of the camera. - -# example motion/door/window/siren/plug -# 123FC4577H=99 - -# example basestation id (alert idx,selector idx) -# F32A76C4DHJ1B743A0E0D74EFD2375D1=98,96 - -# example camera id -# 7C2G30873SED=97 diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 59e2fbd..412ce05 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -10,10 +10,8 @@ import sys import time import datetime -import argparse import json import logging -import configparser from builtins import (dict, int, str, open) from future.moves.urllib.parse import urlparse @@ -22,6 +20,7 @@ from colorama import init, Fore from requests.packages.urllib3 import disable_warnings import requests + import configargparse import unidecode except ImportError as error: sys.exit(str(error) + '. Please install from PyPI: pip install --upgrade ' + str(error).rsplit(None, 1)[-1]) @@ -33,14 +32,26 @@ except ImportError as error: sys.exit(str(error) + '. Please install from PyPI: pip install --upgrade ' + str(error).rsplit(None, 1)[-1]) +if any(arg in sys.argv for arg in ['-i', '--ignore']): + CONFPATH = [] +elif os.name == 'nt': + CONFPATH = [os.path.join(os.environ['APPDATA'], os.path.normpath('gigasetelements-cli/gigasetelements-cli.conf'))] +else: + CONFPATH = ['/opt/etc/gigasetelements-cli.conf', '/usr/local/etc/gigasetelements-cli.conf', '/usr/etc/gigasetelements-cli.conf', + '/etc/gigasetelements-cli.conf', os.path.expanduser('~/.gigasetelements-cli/gigasetelements-cli.conf'), + os.path.expanduser('~/.config/gigasetelements-cli/gigasetelements-cli.conf'), + os.path.expanduser('~/Library/Application Support/gigasetelements-cli/gigasetelements-cli.conf')] _AUTHOR_ = 'dynasticorpheus@gmail.com' -_VERSION_ = '1.5.0b4' +_VERSION_ = '1.5.0b5' LOGCL = {0: Fore.RESET, 1: Fore.GREEN, 2: Fore.YELLOW, 3: Fore.RED} LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4', 'home': '10', 'custom': '20', 'away': '30', 'night': '40'} -OPTDEF = {'username': None, 'password': None, 'modus': None, 'pbtoken': None, 'silent': 'False', 'noupdate': 'False', 'insecure': 'False'} + +SENSOR_FRIENDLY = {'ws02': 'window_sensor', 'ps01': 'presence_sensor', 'ps02': 'presence_sensor', 'ds01': 'door_sensor', 'ds02': 'door_sensor', + 'is01': 'indoor_siren', 'sp01': 'smart_plug', 'bn01': 'button', 'yc01': 'camera', 'sd01': 'smoke', 'um01': 'umos'} + AUTH_EXPIRE = 14400 URL_IDENTITY = 'https://im.gigaset-elements.de/identity/api/v1/user/login' @@ -57,10 +68,10 @@ URL_ALERT = '/json.htm?type=command¶m=udevice&idx=' URL_LOG = '/json.htm?type=command¶m=addlogmessage&message=' -parser = argparse.ArgumentParser(description='Gigaset Elements - Command-line Interface by dynasticorpheus@gmail.com') -parser.add_argument('-c', '--config', help='fully qualified name of configuration-file', required=False) -parser.add_argument('-u', '--username', help='username (email) in use with my.gigaset-elements.com', required=False) -parser.add_argument('-p', '--password', help='password in use with my.gigaset-elements.com', required=False) +parser = configargparse.ArgParser(description='Gigaset Elements - Command-line Interface by dynasticorpheus@gmail.com', default_config_files=CONFPATH) +parser.add_argument('-c', '--config', help='fully qualified name of configuration-file', required=False, is_config_file=True) +parser.add_argument('-u', '--username', help='username (email) in use with my.gigaset-elements.com', required=True) +parser.add_argument('-p', '--password', help='password in use with my.gigaset-elements.com', required=True) parser.add_argument('-n', '--notify', help='pushbullet token', required=False, metavar='TOKEN') parser.add_argument('-e', '--events', help='show last of events', type=int, required=False) parser.add_argument('-d', '--date', help='filter events on begin date - end date', required=False, nargs=2, metavar='DD/MM/YYYY') @@ -89,25 +100,23 @@ parser.add_argument('-q', '--quiet', help='do not send pushbullet message', action='store_true', required=False) parser.add_argument('-I', '--insecure', help='disable SSL/TLS certificate verification', action='store_true', required=False) parser.add_argument('-S', '--silent', help='suppress urllib3 warnings', action='store_true', required=False) +parser.add_argument('-U', '--url', help='url (domoticz)', required=False) +parser.add_argument('-X', '--sensorpairs', help='idx keypairs (domoticz)', required=False, action='append') parser.add_argument('-v', '--version', help='show version', action='version', version='%(prog)s version ' + str(_VERSION_)) args = parser.parse_args() -config = configparser.ConfigParser(defaults=OPTDEF) init(autoreset=True) +s = requests.Session() +s.mount('http://', requests.adapters.HTTPAdapter(max_retries=3)) +s.mount('https://', requests.adapters.HTTPAdapter(max_retries=3)) +POST, GET, HEAD = s.post, s.get, s.head -if os.name == 'nt': - args.cronjob, args.remove = None, False - NTCONFIG = os.path.join(os.environ['APPDATA'], os.path.normpath('gigasetelements-cli/gigasetelements-cli.conf')) -else: - NTCONFIG = '' -if args.cronjob is None and args.remove is False: - s = requests.Session() - s.mount('http://', requests.adapters.HTTPAdapter(max_retries=3)) - s.mount('https://', requests.adapters.HTTPAdapter(max_retries=3)) - POST, GET, HEAD = s.post, s.get, s.head -else: - args.noupdate = True +if args.silent: + try: + disable_warnings() + except NameError: + pass def restart_program(): @@ -168,66 +177,6 @@ def color(txt): return txt -def load_option(arg, section, option): - """Load options safely from conf file.""" - fromfile = False - if arg is None: - arg = config.get(section, option) - if arg == '' or arg is None: - arg = None - else: - fromfile = True - elif isinstance(arg, bool): - if config.getboolean(section, option): - arg = fromfile = True - return arg, fromfile - - -def configure(): - """Load variables based on command line arguments and config file.""" - cfg_domo, url_domo, credfromfile, authstring = None, None, False, '' - if args.config is None: - locations = ['/opt/etc/gigasetelements-cli.conf', '/usr/local/etc/gigasetelements-cli.conf', '/usr/etc/gigasetelements-cli.conf', - '/etc/gigasetelements-cli.conf', os.path.expanduser('~/.gigasetelements-cli/gigasetelements-cli.conf'), NTCONFIG, - os.path.expanduser('~/.config/gigasetelements-cli/gigasetelements-cli.conf'), os.path.expanduser('~/.config/gigasetelements-cli.conf'), - os.path.expanduser('~/Library/Application Support/gigasetelements-cli/gigasetelements-cli.conf')] - for i in locations: - if os.path.exists(i): - args.config = i - if args.ignore: - args.config = None - else: - if not os.path.exists(args.config): - log('Configuration'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | File does not exist ' + args.config, 3, 1) - if args.config is not None: - config.read(args.config) - if args.monitor > 1: - try: - cfg_domo = dict(config.items('domoticz')) - if cfg_domo['username'] != '': - authstring = cfg_domo['username'] + ':' + cfg_domo['password'] + '@' - url_domo = 'http://' + authstring + cfg_domo['ip'] + ':' + cfg_domo['port'] - except Exception: - log('Configuration'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Domoticz setting(s) incorrect and/or missing', 3, 1) - cfg_domo = False - log('Configuration'.ljust(17) + ' | ' + color('loaded'.ljust(8)) + ' | ' + args.config) - args.noupdate, credfromfile = load_option(args.noupdate, 'options', 'noupdate') - args.silent, credfromfile = load_option(args.silent, 'options', 'silent') - args.insecure, credfromfile = load_option(args.insecure, 'options', 'insecure') - args.modus, credfromfile = load_option(args.modus, 'options', 'modus') - args.notify, credfromfile = load_option(args.notify, 'accounts', 'pbtoken') - args.username, credfromfile = load_option(args.username, 'accounts', 'username') - args.password, credfromfile = load_option(args.password, 'accounts', 'password') - if None in (args.username, args.password): - log('Configuration'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Username and/or password missing', 3, 1) - if args.silent: - try: - disable_warnings() - except NameError: - pass - return url_domo, cfg_domo, credfromfile - - def rest(method, url, payload=None, header=False, timeout=90, end=1, silent=False): """REST interaction using requests module.""" request = None @@ -246,7 +195,7 @@ def rest(method, url, payload=None, header=False, timeout=90, end=1, silent=Fals request = method(url, timeout=timeout, headers=header, allow_redirects=True, verify=pem) except requests.exceptions.RequestException as error: if not silent: - log('ERROR'.ljust(17) + ' | ' + 'UNKNOWN'.ljust(8) + ' | ' + str(error.message), 3, end) + log('ERROR'.ljust(17) + ' | ' + 'UNKNOWN'.ljust(8) + ' | ' + str(error), 3, end) if request is not None: if not silent: if request.status_code != requests.codes.ok: # pylint: disable=no-member @@ -308,7 +257,7 @@ def check_version(): def collect_hw(basestation_data, camera_data): """Retrieve sensor list and details.""" sensor_id = {} - sensor_exist = dict.fromkeys(['button', 'camera', 'door_sensor', 'indoor_siren', 'presence_sensor', 'smart_plug', 'smoke', 'umos'], False) + sensor_exist = dict.fromkeys(list(SENSOR_FRIENDLY.values()), False) for item in basestation_data[0]['sensors']: sensor_id.setdefault(item['type'], []).append(item['id']) try: @@ -316,24 +265,8 @@ def collect_hw(basestation_data, camera_data): sensor_id.update(dict.fromkeys(['yc01'], camera_data[0]['id'])) except IndexError: pass - if 'is01' in sensor_id: - sensor_exist.update(dict.fromkeys(['indoor_siren'], True)) - if 'sp01' in sensor_id: - sensor_exist.update(dict.fromkeys(['smart_plug'], True)) - if 'bn01' in sensor_id: - sensor_exist.update(dict.fromkeys(['button'], True)) - if 'yc01' in sensor_id: - sensor_exist.update(dict.fromkeys(['camera'], True)) - if 'sd01' in sensor_id: - sensor_exist.update(dict.fromkeys(['smoke'], True)) - if 'um01' in sensor_id: - sensor_exist.update(dict.fromkeys(['umos'], True)) - if 'ws02' in sensor_id: - sensor_exist.update(dict.fromkeys(['window_sensor'], True)) - if 'ps01' in sensor_id or 'ps02' in sensor_id: - sensor_exist.update(dict.fromkeys(['presence_sensor'], True)) - if 'ds01' in sensor_id or 'ds02' in sensor_id: - sensor_exist.update(dict.fromkeys(['door_sensor'], True)) + for item in sensor_id: + sensor_exist.update(dict.fromkeys([SENSOR_FRIENDLY[item]], True)) return sensor_id, sensor_exist @@ -398,19 +331,18 @@ def istimeformat(timestr): return False -def add_cron(credfromfile): +def add_cron(): """Add job to crontab to set alarm modus.""" if args.modus is None: log('Cronjob'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Specify modus using -m option', 3, 1) + elif os.name == 'nt': + log('Cronjob'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not supported on windows OS', 3, 1) if istimeformat(args.cronjob): cron = CronTab(user=True) now = datetime.datetime.now() timer = now.replace(hour=time.strptime(args.cronjob, '%H:%M')[3], minute=time.strptime(args.cronjob, '%H:%M')[4], second=0, microsecond=0) - if credfromfile: - job = cron.new('gigasetelements-cli -m ' + args.modus, comment='added by gigasetelements-cli on ' + str(now)[:16]) - else: - job = cron.new('gigasetelements-cli -u ' + args.username + ' -p ' + args.password + ' -m ' + - args.modus, comment='added by gigasetelements-cli on ' + str(now)[:16]) + job = cron.new('gigasetelements-cli -u ' + args.username + ' -p ' + args.password + ' -m ' + + args.modus, comment='added by gigasetelements-cli on ' + str(now)[:16]) job.month.on(datetime.datetime.now().strftime('%-m')) if now < timer: job.day.on(datetime.datetime.now().strftime('%-d')) @@ -421,7 +353,7 @@ def add_cron(credfromfile): job.hour.on(time.strptime(args.cronjob, '%H:%M')[3]) job.minute.on(time.strptime(args.cronjob, '%H:%M')[4]) cron.write() - log('Cronjob'.ljust(17) + ' | ' + color(args.modus.ljust(8)) + ' | ' + 'Modus on ' + timer.strftime('%A %d %B %Y %H:%M')) + log('Cronjob'.ljust(17) + ' | ' + color(args.modus.ljust(8)) + ' | ' + 'Modus on ' + timer.strftime('%A %d %B %Y %H:%M'), 0, 1) else: log('Cronjob'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Use valid time (00:00 - 23:59)', 3, 1) return @@ -429,6 +361,8 @@ def add_cron(credfromfile): def remove_cron(): """Remove all jobs from crontab setting alarm modus.""" + if os.name == 'nt': + log('Cronjob'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not supported on windows OS', 3, 1) cron = CronTab(user=True) existing = cron.find_command('gigasetelements-cli') count = 0 @@ -436,10 +370,11 @@ def remove_cron(): log('Cronjob'.ljust(17) + ' | ' + color('removed'.ljust(8)) + ' | ' + str(i)) count += 1 if count == 0: - log('Cronjob'.ljust(17) + ' | ' + color('warning'.ljust(8)) + ' | ' + 'No items found for removal') + log('Cronjob'.ljust(17) + ' | ' + color('warning'.ljust(8)) + ' | ' + 'No items found for removal', 0, 1) else: cron.remove_all(command='gigasetelements-cli') cron.write() + sys.exit('\n') return @@ -702,19 +637,14 @@ def base(): if args.daemon: log('Run as background'.ljust(17) + ' | ' + color('daemon'.ljust(8)) + ' | ' + args.pid) - url_domo, cfg_domo, credfromfile = configure() - - if args.cronjob is not None: - add_cron(credfromfile) - print() - sys.exit() - - if args.remove and args.cronjob is None: + if args.remove: remove_cron() - print() - sys.exit() - check_version() + if args.cronjob: + add_cron() + + if not args.noupdate: + check_version() auth_time = authenticate() @@ -771,10 +701,16 @@ def base(): list_events() if args.monitor: - monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo) - + if args.monitor > 1 and args.sensorpairs: + try: + for keypair in args.sensorpairs: + cfg_domo = {key: value for key, value in (rule.split(":") for rule in keypair.lower().split(';'))} + except ValueError: + log('Config'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | check sensor pairing value format', 3, 1) + else: + cfg_domo = None + monitor(auth_time, basestation_data, status_data, args.url, cfg_domo) print() - except KeyboardInterrupt: log('Program'.ljust(17) + ' | ' + color('halted'.ljust(8)) + ' | ' + 'CTRL+C') diff --git a/requirements.txt b/requirements.txt index 7141057..87ef88d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -configparser>=3.5.0 +configargparse>=0.12.0 future>=0.15.2 requests>=2.9.1 colorama>=0.3.7 diff --git a/setup.py b/setup.py index 8d63d60..e81d982 100755 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ with codecs.open(os.path.join(HERE, 'DESCRIPTION.rst'), encoding='utf-8') as f: long_description = f.read() -packagelist = ['future', 'requests', 'pushbullet.py', 'unidecode', 'colorama', 'configparser'] +packagelist = ['future', 'requests', 'pushbullet.py', 'unidecode', 'colorama', 'configargparse'] if os.name == 'posix': packagelist.append('python-crontab') @@ -23,7 +23,7 @@ setup( name='gigasetelements-cli', - version='1.5.0b4', + version='1.5.0b5', description='gigasetelements-cli allows you to control your \ Gigaset Elements home security system from the command line.', long_description=long_description, From 73923f828362cbbfc36b4617f5c4dc3c6a50c4ae Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sun, 8 Oct 2017 20:22:20 +0200 Subject: [PATCH 055/107] tidy up screen output monitor mode and CTRL+C exit --- gigasetelements/gigasetelements.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 0b9ab8a..6d02748 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -444,7 +444,6 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): args.monitor = 1 log(mode.ljust(17) + ' | ' + color('started'.ljust(8)) + ' | ' + 'CTRL+C to exit') from_ts = str(int(time.time()) * 1000) - print('\n') try: while 1: if args.monitor > 1 and time.time() - epoch > 59: @@ -463,8 +462,8 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): for item in reversed(lastevents['events']): try: if 'type' in item['o']: - log(time.strftime('%m/%d/%y %H:%M:%S', time.localtime(int(item['ts']) / 1000)) + ' | ' + item['o'][ - 'type'].ljust(8) + ' | ' + item['type'] + ' ' + item['o'].get('friendly_name', item['o']['type'])) + log('\r\x1b[K[-] ' + time.strftime('%m/%d/%y %H:%M:%S', time.localtime(int(item['ts']) / 1000)) + ' | ' + item['o'][ + 'type'].ljust(8) + ' | ' + item['type'] + ' ' + item['o'].get('friendly_name', item['o']['type']), 0, 0, 1) if args.monitor > 1: if item['o']['type'] == 'ycam': domoticz(item['type'][5:].lower(), item['source_id'].lower(), 'ycam', basestation_data, url_domo, cfg_domo) @@ -472,8 +471,8 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): domoticz(item['type'].lower(), item['o']['id'].lower(), item['o'].get('friendly_name', 'basestation').lower(), basestation_data, url_domo, cfg_domo) else: - log(time.strftime('%m/%d/%y %H:%M:%S', time.localtime(int(item['ts']) / 1000)) + - ' | ' + 'system'.ljust(8) + ' | ' + item['source_type'] + ' ' + item['type']) + log('\r\x1b[K[-] ' + time.strftime('%m/%d/%y %H:%M:%S', time.localtime(int(item['ts']) / 1000)) + + ' | ' + 'system'.ljust(8) + ' | ' + item['source_type'] + ' ' + item['type'], 0, 0, 1) domoticz(item['type'].lower(), basestation_data[0]['id'].lower(), item['source_type'].lower(), basestation_data, url_domo, cfg_domo) from_ts = str(int(item['ts']) + 1) except KeyError: @@ -485,7 +484,7 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): except KeyboardInterrupt: if args.monitor > 1: rest(GET, url_domo + URL_LOG + 'Gigaset Elements - Command-line Interface: Domoticz mode halted') - log('Program'.ljust(17) + ' | ' + color('halted'.ljust(8)) + ' | ' + 'CTRL+C') + log('\r\x1b[K[-] ' + 'Program'.ljust(17) + ' | ' + color('halted'.ljust(8)) + ' | ' + 'CTRL+C', 0, 1) return @@ -505,8 +504,6 @@ def domoticz(event, sid, friendly, basestation_data, url_domo, cfg_domo): status_data = rest(GET, URL_HEALTH) rest(GET, url_domo + URL_ALERT + cfg_domo[basestation_data[0]['id'].lower()].split(',')[0] + '&nvalue=' + LEVEL.get(status_data['system_health'], '3') + '&svalue=' + friendly + ' | ' + event) - sys.stdout.write('\033[F') - sys.stdout.write('\033[K') return @@ -716,7 +713,7 @@ def base(): monitor(auth_time, basestation_data, status_data, args.url, cfg_domo) print() except KeyboardInterrupt: - log('Program'.ljust(17) + ' | ' + color('halted'.ljust(8)) + ' | ' + 'CTRL+C') + log('\r\x1b[K[-] ' + 'Program'.ljust(17) + ' | ' + color('halted'.ljust(8)) + ' | ' + 'CTRL+C', 0, 1) def main(): From 9abf9041ff806c926e3f6c609b6730c9b75dd076 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 9 Oct 2017 10:42:05 +0200 Subject: [PATCH 056/107] add clear line to function avoiding log file conflict --- gigasetelements/gigasetelements.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 6d02748..76164b4 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -23,7 +23,7 @@ import configargparse import unidecode except ImportError as error: - sys.exit(str(error) + '. Please install from PyPI: pip install --upgrade ' + str(error).rsplit(None, 1)[-1]) + sys.exit(str(error) + '. Please install from PyPI: pip install --upgrade ' + str(error).rsplit(None, 1)[-1] + '\n') if os.name == 'posix': try: @@ -138,14 +138,15 @@ def log(logme, rbg=0, exitnow=0, newline=None): if args.log is not None: logger = logging.getLogger(__name__) logger.info('[' + time.strftime('%c') + '] ' + logme) + if newline == 2: + print('\r\x1b[K', end='') if newline is not None: newline = ' ' print(LOGCL[rbg] + '[-] ' + logme, end=newline) if exitnow == 1: - print() if args.restart: restart_program() - sys.exit() + sys.exit('\n') return @@ -432,10 +433,9 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): """List events realtime optionally filtered by type.""" health = modus = '' epoch = time.time() - 60 - if args.filter is None: - url_monitor = URL_EVENTS + '?limit=10' - else: - url_monitor = URL_EVENTS + '?limit=10&group=' + args.filter + url_monitor = URL_EVENTS + '?limit=10' + if args.filter is not None: + url_monitor = url_monitor + '&group=' + args.filter if cfg_domo and args.monitor > 1: mode = 'Domoticz mode' rest(GET, url_domo + URL_LOG + 'Gigaset Elements - Command-line Interface: Domoticz mode started') @@ -462,8 +462,8 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): for item in reversed(lastevents['events']): try: if 'type' in item['o']: - log('\r\x1b[K[-] ' + time.strftime('%m/%d/%y %H:%M:%S', time.localtime(int(item['ts']) / 1000)) + ' | ' + item['o'][ - 'type'].ljust(8) + ' | ' + item['type'] + ' ' + item['o'].get('friendly_name', item['o']['type']), 0, 0, 1) + log(time.strftime('%m/%d/%y %H:%M:%S', time.localtime(int(item['ts']) / 1000)) + ' | ' + item['o'][ + 'type'].ljust(8) + ' | ' + item['type'] + ' ' + item['o'].get('friendly_name', item['o']['type']), 0, 0, 2) if args.monitor > 1: if item['o']['type'] == 'ycam': domoticz(item['type'][5:].lower(), item['source_id'].lower(), 'ycam', basestation_data, url_domo, cfg_domo) @@ -471,8 +471,8 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): domoticz(item['type'].lower(), item['o']['id'].lower(), item['o'].get('friendly_name', 'basestation').lower(), basestation_data, url_domo, cfg_domo) else: - log('\r\x1b[K[-] ' + time.strftime('%m/%d/%y %H:%M:%S', time.localtime(int(item['ts']) / 1000)) + - ' | ' + 'system'.ljust(8) + ' | ' + item['source_type'] + ' ' + item['type'], 0, 0, 1) + log(time.strftime('%m/%d/%y %H:%M:%S', time.localtime(int(item['ts']) / 1000)) + + ' | ' + 'system'.ljust(8) + ' | ' + item['source_type'] + ' ' + item['type'], 0, 0, 2) domoticz(item['type'].lower(), basestation_data[0]['id'].lower(), item['source_type'].lower(), basestation_data, url_domo, cfg_domo) from_ts = str(int(item['ts']) + 1) except KeyError: @@ -484,7 +484,7 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): except KeyboardInterrupt: if args.monitor > 1: rest(GET, url_domo + URL_LOG + 'Gigaset Elements - Command-line Interface: Domoticz mode halted') - log('\r\x1b[K[-] ' + 'Program'.ljust(17) + ' | ' + color('halted'.ljust(8)) + ' | ' + 'CTRL+C', 0, 1) + log('Program'.ljust(17) + ' | ' + color('halted'.ljust(8)) + ' | ' + 'CTRL+C', 0, 1, 2) return @@ -523,7 +523,6 @@ def sensor(basestation_data, sensor_exist, camera_data): print() except KeyError: print() - continue if sensor_exist['camera']: try: for cam in camera_data: @@ -619,8 +618,7 @@ def start_logger(logfile): filehandle = logging.FileHandler(logfile, 'a') except IOError: print(Fore.RED + '[-] Unable to write log file ' + logfile) - print() - sys.exit() + sys.exit('\n') filehandle.setLevel(logging.INFO) logger.addHandler(filehandle) logger.info('[' + time.strftime('%c') + '] ' + 'Gigaset Elements'.ljust(17) + ' | ' + 'CLI'.ljust(8) + ' | ' + _VERSION_ + ' | ' + ' '.join(sys.argv[1:])) @@ -713,7 +711,7 @@ def base(): monitor(auth_time, basestation_data, status_data, args.url, cfg_domo) print() except KeyboardInterrupt: - log('\r\x1b[K[-] ' + 'Program'.ljust(17) + ' | ' + color('halted'.ljust(8)) + ' | ' + 'CTRL+C', 0, 1) + log('Program'.ljust(17) + ' | ' + color('halted'.ljust(8)) + ' | ' + 'CTRL+C', 0, 1, 2) def main(): From 21f871413efc62a905550c45298812870900ae25 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 9 Oct 2017 20:28:23 +0200 Subject: [PATCH 057/107] add multi-camera support to snapshot function using mac --- gigasetelements/gigasetelements.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 76164b4..e207c04 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -93,7 +93,7 @@ parser.add_argument('-y', '--privacy', help='switch privacy mode on/off', required=False, choices=('on', 'off')) parser.add_argument('-a', '--stream', help='start camera cloud based streams', action='store_true', required=False) parser.add_argument('-r', '--record', help='switch camera recording on/off', action='store_true', required=False) -parser.add_argument('-A', '--snapshot', help='download camera snapshot', action='store_true', required=False) +parser.add_argument('-A', '--snapshot', help='download camera snapshot', type=str, required=False, metavar='MAC address') parser.add_argument('-t', '--monitor', help='show events using monitor mode (use -tt to activate domoticz mode)', action='count', default=0, required=False) parser.add_argument('-i', '--ignore', help='ignore configuration-file at predefined locations', action='store_true', required=False) parser.add_argument('-N', '--noupdate', help='do not periodically check for updates', action='store_true', required=False) @@ -266,8 +266,8 @@ def collect_hw(basestation_data, camera_data): for item in basestation_data[0]['sensors']: sensor_id.setdefault(item['type'], []).append(item['id']) try: - if 'id' in camera_data[0] and len(camera_data[0]['id']) == 12: - sensor_id.update(dict.fromkeys(['yc01'], camera_data[0]['id'])) + for mac in camera_data: + sensor_id.setdefault('yc01', []).append(mac['id']) except IndexError: pass for item in sensor_id: @@ -597,15 +597,19 @@ def record(camera_data, sensor_exist): return -def getsnapshot(camera_data, sensor_exist): +def getsnapshot(sensor_id, sensor_exist): """Download snapshot from camera.""" if not sensor_exist['camera']: log('Camera'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) - image_name = 'snapshot_' + time.strftime('%y%m%d') + '_' + time.strftime('%H%M%S') + '.jpg' + if args.snapshot.upper() in sensor_id['yc01']: + mac = args.snapshot.upper() + else: + mac = sensor_id['yc01'][0] + image_name = mac + '_' + time.strftime('%y%m%d') + '_' + time.strftime('%H%M%S') + '.jpg' if filewritable('Snapshot image', image_name, 0): log('Camera snapshot'.ljust(17) + ' | ' + color('download'.ljust(8)) + ' | ' + image_name) with open(image_name, 'wb') as image: - image.write(rest(GET, URL_CAMERA + '/' + str(camera_data[0]['id']) + '/snapshot?fresh=true')) + image.write(rest(GET, URL_CAMERA + '/' + mac + '/snapshot?fresh=true')) return @@ -677,7 +681,7 @@ def base(): record(camera_data, sensor_exist) if args.snapshot: - getsnapshot(camera_data, sensor_exist) + getsnapshot(sensor_id, sensor_exist) if args.notifications: notifications() From 04c688aae10f8d0a47cb7e62b3bc2fb70f3ef664 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 9 Oct 2017 20:44:01 +0200 Subject: [PATCH 058/107] add multi-camera support to record function using mac --- gigasetelements/gigasetelements.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index e207c04..640470d 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -92,7 +92,7 @@ parser.add_argument('-g', '--plug', help='switch plug on/off', required=False, choices=('on', 'off')) parser.add_argument('-y', '--privacy', help='switch privacy mode on/off', required=False, choices=('on', 'off')) parser.add_argument('-a', '--stream', help='start camera cloud based streams', action='store_true', required=False) -parser.add_argument('-r', '--record', help='switch camera recording on/off', action='store_true', required=False) +parser.add_argument('-r', '--record', help='switch camera recording on/off', type=str, required=False, metavar='MAC address') parser.add_argument('-A', '--snapshot', help='download camera snapshot', type=str, required=False, metavar='MAC address') parser.add_argument('-t', '--monitor', help='show events using monitor mode (use -tt to activate domoticz mode)', action='count', default=0, required=False) parser.add_argument('-i', '--ignore', help='ignore configuration-file at predefined locations', action='store_true', required=False) @@ -583,17 +583,21 @@ def camera_stream(camera_data, sensor_exist): return -def record(camera_data, sensor_exist): +def record(sensor_id, sensor_exist): """Start or stop camera recording based on current state.""" if not sensor_exist['camera']: log('Camera'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) - camera_status = rest(GET, URL_CAMERA + '/' + str(camera_data[0]['id']) + '/recording/status') + if args.record.upper() in sensor_id['yc01']: + mac = args.record.upper() + else: + mac = sensor_id['yc01'][0] + camera_status = rest(GET, URL_CAMERA + '/' + mac + '/recording/status') if camera_status['description'] == 'Recording not started': - rest(GET, URL_CAMERA + '/' + str(camera_data[0]['id']) + '/recording/start') - log('Camera recording'.ljust(17) + ' | ' + color('started'.ljust(8)) + ' | ') + rest(GET, URL_CAMERA + '/' + mac + '/recording/start') + log('Camera recording'.ljust(17) + ' | ' + color('started'.ljust(8)) + ' | ' + mac) if camera_status['description'] == 'Recording already started': - rest(GET, URL_CAMERA + '/' + str(camera_data[0]['id']) + '/recording/stop') - log('Camera recording'.ljust(17) + ' | ' + color('stopped'.ljust(8)) + ' | ') + rest(GET, URL_CAMERA + '/' + mac + '/recording/stop') + log('Camera recording'.ljust(17) + ' | ' + color('stopped'.ljust(8)) + ' | ' + mac) return @@ -678,7 +682,7 @@ def base(): camera_stream(camera_data, sensor_exist) if args.record: - record(camera_data, sensor_exist) + record(sensor_id, sensor_exist) if args.snapshot: getsnapshot(sensor_id, sensor_exist) From c0ba6553b5b6a27a1e60f4388ecb410b5fad52e6 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 9 Oct 2017 21:11:31 +0200 Subject: [PATCH 059/107] add multi-camera support to stream function using mac --- gigasetelements/gigasetelements.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 640470d..017589d 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -91,7 +91,7 @@ parser.add_argument('-b', '--siren', help='arm/disarm siren', required=False, choices=('arm', 'disarm')) parser.add_argument('-g', '--plug', help='switch plug on/off', required=False, choices=('on', 'off')) parser.add_argument('-y', '--privacy', help='switch privacy mode on/off', required=False, choices=('on', 'off')) -parser.add_argument('-a', '--stream', help='start camera cloud based streams', action='store_true', required=False) +parser.add_argument('-a', '--stream', help='start camera cloud based streams', type=str, required=False, metavar='MAC address') parser.add_argument('-r', '--record', help='switch camera recording on/off', type=str, required=False, metavar='MAC address') parser.add_argument('-A', '--snapshot', help='download camera snapshot', type=str, required=False, metavar='MAC address') parser.add_argument('-t', '--monitor', help='show events using monitor mode (use -tt to activate domoticz mode)', action='count', default=0, required=False) @@ -569,17 +569,17 @@ def notifications(): return -def camera_stream(camera_data, sensor_exist): +def camera_stream(sensor_id, sensor_exist): """Show camera details and current state.""" if not sensor_exist['camera']: log('Camera'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) - try: - for cam in camera_data: - stream_data = rest(GET, URL_CAMERA + '/' + cam['id'] + '/liveview/start') - for stream in ('m3u8', 'rtmp', 'rtsp'): - log(cam['friendly_name'].ljust(17) + ' | ' + stream.upper().ljust(8) + ' | ' + stream_data['uri'][stream]) - except KeyError: - print() + if args.stream.upper() in sensor_id['yc01']: + mac = args.stream.upper() + else: + mac = sensor_id['yc01'][0] + stream_data = rest(GET, URL_CAMERA + '/' + mac + '/liveview/start') + for stream in ('m3u8', 'rtmp', 'rtsp'): + log('Stream'.ljust(17) + ' | ' + stream.upper().ljust(8) + ' | ' + stream_data['uri'][stream]) return @@ -679,7 +679,7 @@ def base(): set_privacy(basestation_data) if args.stream: - camera_stream(camera_data, sensor_exist) + camera_stream(sensor_id, sensor_exist) if args.record: record(sensor_id, sensor_exist) From 1332e94e8f3ee4f33f2c4188189edcab49541280 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 10 Oct 2017 10:33:17 +0200 Subject: [PATCH 060/107] add camera recording option to --cronjob --- gigasetelements/gigasetelements.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 017589d..0a6b552 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -76,7 +76,7 @@ parser.add_argument('-n', '--notify', help='pushbullet token', required=False, metavar='TOKEN') parser.add_argument('-e', '--events', help='show last of events', type=int, required=False) parser.add_argument('-d', '--date', help='filter events on begin date - end date', required=False, nargs=2, metavar='DD/MM/YYYY') -parser.add_argument('-o', '--cronjob', help='schedule cron job at HH:MM (requires -m option)', required=False, metavar='HH:MM') +parser.add_argument('-o', '--cronjob', help='schedule cron job at HH:MM (requires --modus or --record)', required=False, metavar='HH:MM') parser.add_argument('-x', '--remove', help='remove all cron jobs linked to this program', action='store_true', required=False) parser.add_argument('-f', '--filter', help='filter events on type', required=False, choices=( 'door', 'window', 'motion', 'siren', 'plug', 'button', 'homecoming', 'intrusion', 'systemhealth', 'camera', 'phone', 'smoke', 'umos')) @@ -165,7 +165,7 @@ def filewritable(filetype, fileloc, mustexit=1): def color(txt): """Add color to string based on presence in list and return in uppercase.""" green = ['ok', 'online', 'closed', 'up_to_date', 'home', 'auto', 'on', 'hd', 'cable', 'normal', 'daemon', 'wifi', - 'started', 'active', 'green', 'armed', 'pushed', 'verified', 'loaded', 'success', 'download'] + 'started', 'active', 'green', 'armed', 'pushed', 'verified', 'loaded', 'success', 'download', 'scheduled'] orange = ['orange', 'warning', 'update'] if args.log is not None: txt = txt.upper() @@ -337,17 +337,21 @@ def istimeformat(timestr): def add_cron(): - """Add job to crontab to set alarm modus.""" - if args.modus is None: - log('Cronjob'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Specify modus using -m option', 3, 1) - elif os.name == 'nt': + """Add job to crontab to set alarm modus or trigger recording.""" + if os.name == 'nt': log('Cronjob'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not supported on windows OS', 3, 1) + elif args.modus is None and args.record is None: + log('Cronjob'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Requires --modus or --record', 3, 1) + if args.modus: + action = ' --modus ' + args.modus + ' ' + else: + action = ' --record ' + args.record + ' ' if istimeformat(args.cronjob): cron = CronTab(user=True) now = datetime.datetime.now() timer = now.replace(hour=time.strptime(args.cronjob, '%H:%M')[3], minute=time.strptime(args.cronjob, '%H:%M')[4], second=0, microsecond=0) - job = cron.new('gigasetelements-cli -u ' + args.username + ' -p ' + args.password + ' -m ' + - args.modus, comment='added by gigasetelements-cli on ' + str(now)[:16]) + job = cron.new('gigasetelements-cli -u ' + args.username + ' -p ' + args.password + + action, comment='added by gigasetelements-cli on ' + str(now)[:16]) job.month.on(datetime.datetime.now().strftime('%-m')) if now < timer: job.day.on(datetime.datetime.now().strftime('%-d')) @@ -358,7 +362,7 @@ def add_cron(): job.hour.on(time.strptime(args.cronjob, '%H:%M')[3]) job.minute.on(time.strptime(args.cronjob, '%H:%M')[4]) cron.write() - log('Cronjob'.ljust(17) + ' | ' + color(args.modus.ljust(8)) + ' | ' + 'Modus on ' + timer.strftime('%A %d %B %Y %H:%M'), 0, 1) + log('Cronjob'.ljust(17) + ' | ' + color('scheduled'.ljust(8)) + ' |' + action + '| ' + timer.strftime('%A %d %B %Y %H:%M'), 0, 1) else: log('Cronjob'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Use valid time (00:00 - 23:59)', 3, 1) return From 86995d97509050873e9b2fa376824b0b8f7f50c9 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 26 Oct 2017 20:31:32 +0200 Subject: [PATCH 061/107] add hue bridge to sensor list --- gigasetelements/gigasetelements.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 0a6b552..6ba976b 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -50,7 +50,8 @@ 'custom': '20', 'away': '30', 'night': '40'} SENSOR_FRIENDLY = {'ws02': 'window_sensor', 'ps01': 'presence_sensor', 'ps02': 'presence_sensor', 'ds01': 'door_sensor', 'ds02': 'door_sensor', - 'is01': 'indoor_siren', 'sp01': 'smart_plug', 'bn01': 'button', 'yc01': 'camera', 'sd01': 'smoke', 'um01': 'umos'} + 'is01': 'indoor_siren', 'sp01': 'smart_plug', 'bn01': 'button', 'yc01': 'camera', 'sd01': 'smoke', 'um01': 'umos', + 'hb01': 'hue_bridge', 'bs01': 'base_station'} AUTH_EXPIRE = 14400 From a21361d4d4cea4ed45afe290e7a7c99e761f5b75 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 27 Oct 2017 14:31:45 +0200 Subject: [PATCH 062/107] add water to sensor list --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 6ba976b..36f8ad6 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -51,7 +51,7 @@ SENSOR_FRIENDLY = {'ws02': 'window_sensor', 'ps01': 'presence_sensor', 'ps02': 'presence_sensor', 'ds01': 'door_sensor', 'ds02': 'door_sensor', 'is01': 'indoor_siren', 'sp01': 'smart_plug', 'bn01': 'button', 'yc01': 'camera', 'sd01': 'smoke', 'um01': 'umos', - 'hb01': 'hue_bridge', 'bs01': 'base_station'} + 'hb01': 'hue_bridge', 'bs01': 'base_station', 'wd01': 'water'} AUTH_EXPIRE = 14400 From bbaac8f8eaf84ed8ee6b0978c54e3f6527b7f337 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 27 Oct 2017 15:59:48 +0200 Subject: [PATCH 063/107] add hue light to sensor list --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 36f8ad6..6920c3b 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -51,7 +51,7 @@ SENSOR_FRIENDLY = {'ws02': 'window_sensor', 'ps01': 'presence_sensor', 'ps02': 'presence_sensor', 'ds01': 'door_sensor', 'ds02': 'door_sensor', 'is01': 'indoor_siren', 'sp01': 'smart_plug', 'bn01': 'button', 'yc01': 'camera', 'sd01': 'smoke', 'um01': 'umos', - 'hb01': 'hue_bridge', 'bs01': 'base_station', 'wd01': 'water'} + 'hb01': 'hue_bridge', 'hb01.hl01': 'hue_light', 'bs01': 'base_station', 'wd01': 'water'} AUTH_EXPIRE = 14400 From 2014950b23e4c1cf503936aef966af133b1626df Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 27 Oct 2017 16:02:05 +0200 Subject: [PATCH 064/107] correct argument key for pushbullet token --- gigasetelements-cli.conf.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements-cli.conf.template b/gigasetelements-cli.conf.template index 8439881..0390a40 100644 --- a/gigasetelements-cli.conf.template +++ b/gigasetelements-cli.conf.template @@ -27,7 +27,7 @@ username=first.last@domain.com password=mybigsecret # access token to enable pushbullet notifications (optional) -# pbtoken=z9FaKeSCKQDi2cmPUSHB62aiXx5I57eiujTOKENfS34 +# notify=z9FaKeSCKQDi2cmPUSHB62aiXx5I57eiujTOKENfS34 [options] From bcc86e01d796b93e0c67269833c50ea936c993a1 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 29 Jan 2018 18:54:46 +0100 Subject: [PATCH 065/107] update key system_health to systemHealth --- gigasetelements/gigasetelements.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 6920c3b..3a1fc85 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -238,12 +238,12 @@ def systemstatus(): log('Basestation'.ljust(17) + ' | ' + color(basestation_data[0]['status'].ljust(8)) + ' | ' + basestation_data[0]['id']) camera_data = rest(GET, URL_CAMERA) status_data = rest(GET, URL_HEALTH) - if status_data['system_health'] == 'green': + if status_data['systemHealth'] == 'green': status_data['status_msg_id'] = '' else: status_data['status_msg_id'] = ' | ' + status_data['status_msg_id'] if args.modus is None: - log('Status'.ljust(17) + ' | ' + color(status_data['system_health'].ljust(8)) + + log('Status'.ljust(17) + ' | ' + color(status_data['systemHealth'].ljust(8)) + status_data['status_msg_id'].upper() + ' | Modus ' + color(basestation_data[0]['intrusion_settings']['active_mode'])) return basestation_data, status_data, camera_data @@ -280,7 +280,7 @@ def modus_switch(basestation_data, status_data): """Switch alarm modus.""" switch = {'intrusion_settings': {'active_mode': args.modus}} rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) - log('Status'.ljust(17) + ' | ' + color(status_data['system_health'].ljust(8)) + status_data['status_msg_id'].upper() + + log('Status'.ljust(17) + ' | ' + color(status_data['systemHealth'].ljust(8)) + status_data['status_msg_id'].upper() + ' | Modus set from ' + color(basestation_data[0]['intrusion_settings']['active_mode']) + ' to ' + color(args.modus)) return @@ -453,10 +453,10 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): while 1: if args.monitor > 1 and time.time() - epoch > 59: status_data = rest(GET, URL_HEALTH) - if health != status_data['system_health'].lower(): - domoticz(status_data['system_health'].lower(), basestation_data[0]['id'].lower(), + if health != status_data['systemHealth'].lower(): + domoticz(status_data['systemHealth'].lower(), basestation_data[0]['id'].lower(), basestation_data[0]['friendly_name'].lower(), basestation_data, url_domo, cfg_domo) - health = status_data['system_health'].lower() + health = status_data['systemHealth'].lower() basestation_data = rest(GET, URL_BASE) if modus != basestation_data[0]['intrusion_settings']['active_mode']: domoticz(basestation_data[0]['intrusion_settings']['active_mode'].lower(), basestation_data[0]['id'].lower(), @@ -508,7 +508,7 @@ def domoticz(event, sid, friendly, basestation_data, url_domo, cfg_domo): else: status_data = rest(GET, URL_HEALTH) rest(GET, url_domo + URL_ALERT + cfg_domo[basestation_data[0]['id'].lower()].split(',')[0] + '&nvalue=' + - LEVEL.get(status_data['system_health'], '3') + '&svalue=' + friendly + ' | ' + event) + LEVEL.get(status_data['systemHealth'], '3') + '&svalue=' + friendly + ' | ' + event) return @@ -667,14 +667,14 @@ def base(): if args.modus is not None and args.cronjob is None: modus_switch(basestation_data, status_data) if args.sensor is not True: - pb_body = 'Status ' + status_data['system_health'].upper() + ' | Modus set from ' + \ + pb_body = 'Status ' + status_data['systemHealth'].upper() + ' | Modus set from ' + \ basestation_data[0]['intrusion_settings']['active_mode'].upper() + ' to ' + args.modus.upper() if args.sensor: sensor(basestation_data, sensor_exist, camera_data) if status_data['status_msg_id'] == '': status_data['status_msg_id'] = '\u2713' - pb_body = 'Status ' + status_data['system_health'].upper() + ' | ' + status_data['status_msg_id'].upper() + \ + pb_body = 'Status ' + status_data['systemHealth'].upper() + ' | ' + status_data['status_msg_id'].upper() + \ ' | Modus ' + basestation_data[0]['intrusion_settings']['active_mode'].upper() if args.delay is not None: From 477cd7cbded4c55914bdea8289b4e7ac8acd8c88 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 29 Jan 2018 20:52:33 +0100 Subject: [PATCH 066/107] update key status_msg_id to StatusMsgId --- gigasetelements/gigasetelements.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 3a1fc85..893bf2f 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -239,12 +239,12 @@ def systemstatus(): camera_data = rest(GET, URL_CAMERA) status_data = rest(GET, URL_HEALTH) if status_data['systemHealth'] == 'green': - status_data['status_msg_id'] = '' + status_data['statusMsgId'] = '' else: - status_data['status_msg_id'] = ' | ' + status_data['status_msg_id'] + status_data['statusMsgId'] = ' | ' + status_data['statusMsgId'] if args.modus is None: log('Status'.ljust(17) + ' | ' + color(status_data['systemHealth'].ljust(8)) + - status_data['status_msg_id'].upper() + ' | Modus ' + color(basestation_data[0]['intrusion_settings']['active_mode'])) + status_data['statusMsgId'].upper() + ' | Modus ' + color(basestation_data[0]['intrusion_settings']['active_mode'])) return basestation_data, status_data, camera_data @@ -280,7 +280,7 @@ def modus_switch(basestation_data, status_data): """Switch alarm modus.""" switch = {'intrusion_settings': {'active_mode': args.modus}} rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) - log('Status'.ljust(17) + ' | ' + color(status_data['systemHealth'].ljust(8)) + status_data['status_msg_id'].upper() + + log('Status'.ljust(17) + ' | ' + color(status_data['systemHealth'].ljust(8)) + status_data['statusMsgId'].upper() + ' | Modus set from ' + color(basestation_data[0]['intrusion_settings']['active_mode']) + ' to ' + color(args.modus)) return @@ -672,9 +672,9 @@ def base(): if args.sensor: sensor(basestation_data, sensor_exist, camera_data) - if status_data['status_msg_id'] == '': - status_data['status_msg_id'] = '\u2713' - pb_body = 'Status ' + status_data['systemHealth'].upper() + ' | ' + status_data['status_msg_id'].upper() + \ + if status_data['statusMsgId'] == '': + status_data['statusMsgId'] = '\u2713' + pb_body = 'Status ' + status_data['systemHealth'].upper() + ' | ' + status_data['statusMsgId'].upper() + \ ' | Modus ' + basestation_data[0]['intrusion_settings']['active_mode'].upper() if args.delay is not None: From f9c24531f4e33a9a61b7b6206dac3a099135c13c Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 30 Jan 2018 18:49:03 +0100 Subject: [PATCH 067/107] Revert "update key status_msg_id to StatusMsgId" This reverts commit 477cd7cbded4c55914bdea8289b4e7ac8acd8c88. --- gigasetelements/gigasetelements.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 893bf2f..3a1fc85 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -239,12 +239,12 @@ def systemstatus(): camera_data = rest(GET, URL_CAMERA) status_data = rest(GET, URL_HEALTH) if status_data['systemHealth'] == 'green': - status_data['statusMsgId'] = '' + status_data['status_msg_id'] = '' else: - status_data['statusMsgId'] = ' | ' + status_data['statusMsgId'] + status_data['status_msg_id'] = ' | ' + status_data['status_msg_id'] if args.modus is None: log('Status'.ljust(17) + ' | ' + color(status_data['systemHealth'].ljust(8)) + - status_data['statusMsgId'].upper() + ' | Modus ' + color(basestation_data[0]['intrusion_settings']['active_mode'])) + status_data['status_msg_id'].upper() + ' | Modus ' + color(basestation_data[0]['intrusion_settings']['active_mode'])) return basestation_data, status_data, camera_data @@ -280,7 +280,7 @@ def modus_switch(basestation_data, status_data): """Switch alarm modus.""" switch = {'intrusion_settings': {'active_mode': args.modus}} rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) - log('Status'.ljust(17) + ' | ' + color(status_data['systemHealth'].ljust(8)) + status_data['statusMsgId'].upper() + + log('Status'.ljust(17) + ' | ' + color(status_data['systemHealth'].ljust(8)) + status_data['status_msg_id'].upper() + ' | Modus set from ' + color(basestation_data[0]['intrusion_settings']['active_mode']) + ' to ' + color(args.modus)) return @@ -672,9 +672,9 @@ def base(): if args.sensor: sensor(basestation_data, sensor_exist, camera_data) - if status_data['statusMsgId'] == '': - status_data['statusMsgId'] = '\u2713' - pb_body = 'Status ' + status_data['systemHealth'].upper() + ' | ' + status_data['statusMsgId'].upper() + \ + if status_data['status_msg_id'] == '': + status_data['status_msg_id'] = '\u2713' + pb_body = 'Status ' + status_data['systemHealth'].upper() + ' | ' + status_data['status_msg_id'].upper() + \ ' | Modus ' + basestation_data[0]['intrusion_settings']['active_mode'].upper() if args.delay is not None: From c47483bb03553a6de408a33bd5a1aab69c414caa Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 30 Jan 2018 18:49:20 +0100 Subject: [PATCH 068/107] Revert "update key system_health to systemHealth" This reverts commit bcc86e01d796b93e0c67269833c50ea936c993a1. --- gigasetelements/gigasetelements.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 3a1fc85..6920c3b 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -238,12 +238,12 @@ def systemstatus(): log('Basestation'.ljust(17) + ' | ' + color(basestation_data[0]['status'].ljust(8)) + ' | ' + basestation_data[0]['id']) camera_data = rest(GET, URL_CAMERA) status_data = rest(GET, URL_HEALTH) - if status_data['systemHealth'] == 'green': + if status_data['system_health'] == 'green': status_data['status_msg_id'] = '' else: status_data['status_msg_id'] = ' | ' + status_data['status_msg_id'] if args.modus is None: - log('Status'.ljust(17) + ' | ' + color(status_data['systemHealth'].ljust(8)) + + log('Status'.ljust(17) + ' | ' + color(status_data['system_health'].ljust(8)) + status_data['status_msg_id'].upper() + ' | Modus ' + color(basestation_data[0]['intrusion_settings']['active_mode'])) return basestation_data, status_data, camera_data @@ -280,7 +280,7 @@ def modus_switch(basestation_data, status_data): """Switch alarm modus.""" switch = {'intrusion_settings': {'active_mode': args.modus}} rest(POST, URL_BASE + '/' + basestation_data[0]['id'], json.dumps(switch)) - log('Status'.ljust(17) + ' | ' + color(status_data['systemHealth'].ljust(8)) + status_data['status_msg_id'].upper() + + log('Status'.ljust(17) + ' | ' + color(status_data['system_health'].ljust(8)) + status_data['status_msg_id'].upper() + ' | Modus set from ' + color(basestation_data[0]['intrusion_settings']['active_mode']) + ' to ' + color(args.modus)) return @@ -453,10 +453,10 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): while 1: if args.monitor > 1 and time.time() - epoch > 59: status_data = rest(GET, URL_HEALTH) - if health != status_data['systemHealth'].lower(): - domoticz(status_data['systemHealth'].lower(), basestation_data[0]['id'].lower(), + if health != status_data['system_health'].lower(): + domoticz(status_data['system_health'].lower(), basestation_data[0]['id'].lower(), basestation_data[0]['friendly_name'].lower(), basestation_data, url_domo, cfg_domo) - health = status_data['systemHealth'].lower() + health = status_data['system_health'].lower() basestation_data = rest(GET, URL_BASE) if modus != basestation_data[0]['intrusion_settings']['active_mode']: domoticz(basestation_data[0]['intrusion_settings']['active_mode'].lower(), basestation_data[0]['id'].lower(), @@ -508,7 +508,7 @@ def domoticz(event, sid, friendly, basestation_data, url_domo, cfg_domo): else: status_data = rest(GET, URL_HEALTH) rest(GET, url_domo + URL_ALERT + cfg_domo[basestation_data[0]['id'].lower()].split(',')[0] + '&nvalue=' + - LEVEL.get(status_data['systemHealth'], '3') + '&svalue=' + friendly + ' | ' + event) + LEVEL.get(status_data['system_health'], '3') + '&svalue=' + friendly + ' | ' + event) return @@ -667,14 +667,14 @@ def base(): if args.modus is not None and args.cronjob is None: modus_switch(basestation_data, status_data) if args.sensor is not True: - pb_body = 'Status ' + status_data['systemHealth'].upper() + ' | Modus set from ' + \ + pb_body = 'Status ' + status_data['system_health'].upper() + ' | Modus set from ' + \ basestation_data[0]['intrusion_settings']['active_mode'].upper() + ' to ' + args.modus.upper() if args.sensor: sensor(basestation_data, sensor_exist, camera_data) if status_data['status_msg_id'] == '': status_data['status_msg_id'] = '\u2713' - pb_body = 'Status ' + status_data['systemHealth'].upper() + ' | ' + status_data['status_msg_id'].upper() + \ + pb_body = 'Status ' + status_data['system_health'].upper() + ' | ' + status_data['status_msg_id'].upper() + \ ' | Modus ' + basestation_data[0]['intrusion_settings']['active_mode'].upper() if args.delay is not None: From 2c6d2d5af7b8a1224a6d311ca5ac25599c041f9e Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sun, 11 Feb 2018 18:45:37 +0100 Subject: [PATCH 069/107] add ~/.gigasetelements-cli as default config location --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 6920c3b..fe28ed2 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -39,7 +39,7 @@ else: CONFPATH = ['/opt/etc/gigasetelements-cli.conf', '/usr/local/etc/gigasetelements-cli.conf', '/usr/etc/gigasetelements-cli.conf', '/etc/gigasetelements-cli.conf', os.path.expanduser('~/.gigasetelements-cli/gigasetelements-cli.conf'), - os.path.expanduser('~/.config/gigasetelements-cli/gigasetelements-cli.conf'), + os.path.expanduser('~/.config/gigasetelements-cli/gigasetelements-cli.conf'), os.path.expanduser('~/.gigasetelements-cli'), os.path.expanduser('~/Library/Application Support/gigasetelements-cli/gigasetelements-cli.conf')] _AUTHOR_ = 'dynasticorpheus@gmail.com' From 91108f9f61f0d9caef4e57a459f37b4f90d38921 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sun, 11 Feb 2018 19:33:30 +0100 Subject: [PATCH 070/107] added pip oneliner github installation --- README.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 64a31f3..eb1e0bf 100644 --- a/README.rst +++ b/README.rst @@ -21,14 +21,18 @@ For easy installation including dependencies simply run below command (with elev [-] *pip install gigasetelements-cli* -**GITHUB** - [https://github.com/dynasticorpheus/gigaset-elements] +**GITHUB** - [https://github.com/dynasticorpheus/gigasetelements-cli] -[1] *git clone https://github.com/dynasticorpheus/gigaset-elements.git* +[1] *git clone -b develop https://github.com/dynasticorpheus/gigasetelements-cli* [2] install *dependencies*, pip install -r requirements.txt (with elevated privileges if needed) [3] *python setup.py install --force* (or run from source using wrapper ./gigasetelements-cli.py) +**GITHUB** - [https://github.com/dynasticorpheus/gigasetelements-cli] [RECOMMENDED] + +[1] *pip install git+https://github.com/dynasticorpheus/gigasetelements-cli@develop* + Features ------------ From ebbd869fe7c21a5843d8e579fc748db47975ba91 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 14 Jun 2018 21:09:20 +0200 Subject: [PATCH 071/107] #27 avoid high CPU utilization --- gigasetelements/gigasetelements.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index fe28ed2..c50655e 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -146,6 +146,7 @@ def log(logme, rbg=0, exitnow=0, newline=None): print(LOGCL[rbg] + '[-] ' + logme, end=newline) if exitnow == 1: if args.restart: + time.sleep(6) restart_program() sys.exit('\n') return From 04eef9c794d7a5c9417b596f8ccf4c0f04729e08 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 14 Jun 2018 21:21:35 +0200 Subject: [PATCH 072/107] update usage url --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index c50655e..84b1a6b 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -64,7 +64,7 @@ URL_HEALTH = 'https://api.gigaset-elements.de/api/v2/me/health' URL_CHANNEL = 'https://api.gigaset-elements.de/api/v1/me/notifications/users/channels' URL_RELEASE = 'https://pypi.python.org/pypi/gigasetelements-cli/json' -URL_USAGE = 'https://goo.gl/oHJ565' +URL_USAGE = 'https://goo.gl/Pt6RmK' URL_SWITCH = '/json.htm?type=command¶m=switchlight&switchcmd=' URL_ALERT = '/json.htm?type=command¶m=udevice&idx=' From 8b437cb7c508f861430e7e9da21218cbe5a1bdb5 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sat, 16 Jun 2018 10:57:32 +0200 Subject: [PATCH 073/107] allow setting restart delay duration via command-line option --- gigasetelements/gigasetelements.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 84b1a6b..0b1d751 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -99,6 +99,7 @@ parser.add_argument('-i', '--ignore', help='ignore configuration-file at predefined locations', action='store_true', required=False) parser.add_argument('-N', '--noupdate', help='do not periodically check for updates', action='store_true', required=False) parser.add_argument('-j', '--restart', help='automatically restart program in case of a connection error', action='store_true', required=False) +parser.add_argument('-J', '--restartdelay', help='set restart delay in seconds', type=int, required=False, default=60) parser.add_argument('-q', '--quiet', help='do not send pushbullet message', action='store_true', required=False) parser.add_argument('-I', '--insecure', help='disable SSL/TLS certificate verification', action='store_true', required=False) parser.add_argument('-S', '--silent', help='suppress urllib3 warnings', action='store_true', required=False) @@ -146,7 +147,7 @@ def log(logme, rbg=0, exitnow=0, newline=None): print(LOGCL[rbg] + '[-] ' + logme, end=newline) if exitnow == 1: if args.restart: - time.sleep(6) + time.sleep(args.restartdelay) restart_program() sys.exit('\n') return From 92f1bf9a4bdf0944ad319a1070250dfed07f79ed Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 2 Oct 2018 09:21:46 +0200 Subject: [PATCH 074/107] prevent UnboundLocalError --- gigasetelements/gigasetelements.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 0b1d751..9cc3715 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -185,6 +185,7 @@ def color(txt): def rest(method, url, payload=None, header=False, timeout=90, end=1, silent=False): """REST interaction using requests module.""" request = None + data = '' if args.insecure: pem = False else: From 737839a7df2956dee798d0e74d24454ffacc2d58 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sat, 19 Jan 2019 13:54:04 +0100 Subject: [PATCH 075/107] add lgtm badges --- README.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index eb1e0bf..9bf88f2 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Gigaset Elements API command-line interface =========================================== -|Version Status| |Downloads| +|Version status| |Language grade: Python| |Total alerts| |Downloads| gigasetelements-cli is a python based program which allows you to control your Gigaset Elements home security system. It comes with an easy to use CLI (command-line interface) suitable for direct use or cron jobs. @@ -163,7 +163,11 @@ License ------- GPL2 -.. |Version Status| image:: https://img.shields.io/pypi/v/gigasetelements-cli.svg +.. |Version status| image:: https://img.shields.io/pypi/v/gigasetelements-cli.svg :target: https://pypi.python.org/pypi/gigasetelements-cli/ .. |Downloads| image:: https://img.shields.io/pypi/dm/gigasetelements-cli.svg :target: https://pypi.python.org/pypi/gigasetelements-cli/ +.. |Language grade: Python| image:: https://img.shields.io/lgtm/grade/python/g/dynasticorpheus/gigasetelements-cli.svg + :target: https://lgtm.com/projects/g/dynasticorpheus/gigasetelements-cli/context:python +.. |Total alerts| image:: https://img.shields.io/lgtm/alerts/g/dynasticorpheus/gigasetelements-cli.svg + :target: https://lgtm.com/projects/g/dynasticorpheus/gigasetelements-cli/alerts/ From 24a62a1a2ad0769c8a02c1f2b7b2d5f65ceb3f11 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sat, 19 Jan 2019 14:03:00 +0100 Subject: [PATCH 076/107] add github badges --- README.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9bf88f2..2c30a90 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Gigaset Elements API command-line interface =========================================== -|Version status| |Language grade: Python| |Total alerts| |Downloads| +|Version status| |Github stars| |Github forks| |Language grade: Python| |Total alerts| |Downloads| gigasetelements-cli is a python based program which allows you to control your Gigaset Elements home security system. It comes with an easy to use CLI (command-line interface) suitable for direct use or cron jobs. @@ -171,3 +171,8 @@ GPL2 :target: https://lgtm.com/projects/g/dynasticorpheus/gigasetelements-cli/context:python .. |Total alerts| image:: https://img.shields.io/lgtm/alerts/g/dynasticorpheus/gigasetelements-cli.svg :target: https://lgtm.com/projects/g/dynasticorpheus/gigasetelements-cli/alerts/ +.. |Github forks| image:: https://img.shields.io/github/forks/dynasticorpheus/gigasetelements-cli.svg + :target: https://github.com/dynasticorpheus/gigasetelements-cli/network/members/ +.. |Github stars| image:: https://img.shields.io/github/stars/dynasticorpheus/gigasetelements-cli.svg + :target: https://github.com/dynasticorpheus/gigasetelements-cli/stargazers/ + From ee057a35bc2d2a7bd1a179798e5448626a60f85a Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sat, 2 Feb 2019 17:56:24 +0100 Subject: [PATCH 077/107] update picture --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 2c30a90..d7c7c1a 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ Gigaset Elements API command-line interface gigasetelements-cli is a python based program which allows you to control your Gigaset Elements home security system. It comes with an easy to use CLI (command-line interface) suitable for direct use or cron jobs. -.. image:: https://weblog.bol.com/wp-content/uploads/2015/04/gigaset-elements.jpg +.. image:: https://asset.conrad.com/media10/isa/160267/c1/-/nl/1650392_BB_00_FB/image.jpg :target: https://www.gigaset-elements.com .. image:: https://lh3.googleusercontent.com/k8pmHPby3b76PmNXTyhhK26fp64xJjGwjVo4PmTpp4FotIcE12Na6eRBNgdKFhvlx-uI=w1920-h1080-rw-no From a89541f2fd142ecfd8c9413bfc968e3006cdadda Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 15 Jul 2019 09:26:49 +0200 Subject: [PATCH 078/107] Remove Google+ link --- README.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.rst b/README.rst index d7c7c1a..006d6b1 100644 --- a/README.rst +++ b/README.rst @@ -9,9 +9,6 @@ It comes with an easy to use CLI (command-line interface) suitable for direct us .. image:: https://asset.conrad.com/media10/isa/160267/c1/-/nl/1650392_BB_00_FB/image.jpg :target: https://www.gigaset-elements.com -.. image:: https://lh3.googleusercontent.com/k8pmHPby3b76PmNXTyhhK26fp64xJjGwjVo4PmTpp4FotIcE12Na6eRBNgdKFhvlx-uI=w1920-h1080-rw-no - :target: https://plus.google.com/communities/108042802009267082650 - Installation ------------ From a29a23e1bac1d04ee9c27dd60a15639cbdc46730 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Wed, 17 Jul 2019 08:37:19 +0200 Subject: [PATCH 079/107] #29 add mode check before updating domoticz --- gigasetelements/gigasetelements.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 9cc3715..ac9c1be 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -481,7 +481,8 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): else: log(time.strftime('%m/%d/%y %H:%M:%S', time.localtime(int(item['ts']) / 1000)) + ' | ' + 'system'.ljust(8) + ' | ' + item['source_type'] + ' ' + item['type'], 0, 0, 2) - domoticz(item['type'].lower(), basestation_data[0]['id'].lower(), item['source_type'].lower(), basestation_data, url_domo, cfg_domo) + if args.monitor > 1: + domoticz(item['type'].lower(), basestation_data[0]['id'].lower(), item['source_type'].lower(), basestation_data, url_domo, cfg_domo) from_ts = str(int(item['ts']) + 1) except KeyError: continue From ae2c90135285c87308ba5c41218d07d7bef25207 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 19 Jul 2019 10:48:10 +0200 Subject: [PATCH 080/107] remove stats url --- gigasetelements/gigasetelements.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index ac9c1be..9a541bf 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -64,7 +64,6 @@ URL_HEALTH = 'https://api.gigaset-elements.de/api/v2/me/health' URL_CHANNEL = 'https://api.gigaset-elements.de/api/v1/me/notifications/users/channels' URL_RELEASE = 'https://pypi.python.org/pypi/gigasetelements-cli/json' -URL_USAGE = 'https://goo.gl/Pt6RmK' URL_SWITCH = '/json.htm?type=command¶m=switchlight&switchcmd=' URL_ALERT = '/json.htm?type=command¶m=udevice&idx=' @@ -112,7 +111,7 @@ s = requests.Session() s.mount('http://', requests.adapters.HTTPAdapter(max_retries=3)) s.mount('https://', requests.adapters.HTTPAdapter(max_retries=3)) -POST, GET, HEAD = s.post, s.get, s.head +POST, GET = s.post, s.get if args.silent: @@ -231,7 +230,6 @@ def authenticate(reauthenticate=False): auth_type = auth_type[3:].title() rest(GET, URL_AUTH) log(auth_type.ljust(17) + ' | ' + color('success'.ljust(8)) + ' | ') - rest(HEAD, URL_USAGE, None, False, 2, 0, True) return auth_time From f09d684a21300de571129093caee0dee911a80b1 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sat, 14 Sep 2019 08:31:25 +0200 Subject: [PATCH 081/107] add climate to sensor list --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 9a541bf..ecb700c 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -51,7 +51,7 @@ SENSOR_FRIENDLY = {'ws02': 'window_sensor', 'ps01': 'presence_sensor', 'ps02': 'presence_sensor', 'ds01': 'door_sensor', 'ds02': 'door_sensor', 'is01': 'indoor_siren', 'sp01': 'smart_plug', 'bn01': 'button', 'yc01': 'camera', 'sd01': 'smoke', 'um01': 'umos', - 'hb01': 'hue_bridge', 'hb01.hl01': 'hue_light', 'bs01': 'base_station', 'wd01': 'water'} + 'hb01': 'hue_bridge', 'hb01.hl01': 'hue_light', 'bs01': 'base_station', 'wd01': 'water_sensor', 'cl01': 'climate_sensor'} AUTH_EXPIRE = 14400 From 9c6d6107f066d301e9c11fc6528002652201958a Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 28 Nov 2019 20:30:08 +0100 Subject: [PATCH 082/107] fix #31 by adding sp02 support --- gigasetelements/gigasetelements.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index ecb700c..ff857f0 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -49,9 +49,10 @@ LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4', 'home': '10', 'custom': '20', 'away': '30', 'night': '40'} -SENSOR_FRIENDLY = {'ws02': 'window_sensor', 'ps01': 'presence_sensor', 'ps02': 'presence_sensor', 'ds01': 'door_sensor', 'ds02': 'door_sensor', - 'is01': 'indoor_siren', 'sp01': 'smart_plug', 'bn01': 'button', 'yc01': 'camera', 'sd01': 'smoke', 'um01': 'umos', - 'hb01': 'hue_bridge', 'hb01.hl01': 'hue_light', 'bs01': 'base_station', 'wd01': 'water_sensor', 'cl01': 'climate_sensor'} +SENSOR_FRIENDLY = {'ws02': 'window_sensor', 'ps01': 'presence_sensor', 'ps02': 'presence_sensor', 'ds01': 'door_sensor', + 'ds02': 'door_sensor', 'is01': 'indoor_siren', 'sp01': 'smart_plug', 'sp02': 'smart_plug', 'bn01': 'button', + 'yc01': 'camera', 'sd01': 'smoke', 'um01': 'umos', 'hb01': 'hue_bridge', 'hb01.hl01': 'hue_light', + 'bs01': 'base_station', 'wd01': 'water_sensor', 'cl01': 'climate_sensor'} AUTH_EXPIRE = 14400 @@ -324,7 +325,11 @@ def plug(basestation_data, sensor_exist, sensor_id): if not sensor_exist['smart_plug']: log('Plug'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) switch = {"name": args.plug} - rest(POST, URL_BASE + '/' + basestation_data[0]['id'] + '/endnodes/' + sensor_id['sp01'][0] + '/cmd', json.dumps(switch), True) + if 'sp02' in sensor_id: + plugid = sensor_id['sp02'][0] + else: + plugid = sensor_id['sp01'][0] + rest(POST, URL_BASE + '/' + basestation_data[0]['id'] + '/endnodes/' + plugid + '/cmd', json.dumps(switch), True) log('Plug'.ljust(17) + ' | ' + color(args.plug.ljust(8)) + ' | ') return @@ -521,7 +526,7 @@ def sensor(basestation_data, sensor_exist, camera_data): for item in basestation_data[0]['sensors']: try: log(item['friendly_name'].ljust(17) + ' | ' + color(item['status'].ljust(8)) + ' | firmware ' + color(item['firmware_status']), 0, 0, 0) - if item['type'] not in ['is01', 'sp01']: + if item['type'] not in ['is01', 'sp01', 'sp02']: print('| battery ' + color(item['battery']['state']), end=' ') if item['type'] in ['ds02', 'ds01']: print('| position ' + color(item['position_status']), end=' ') From 427e8367661c261d4cd014c17522827786a77f6a Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 5 Dec 2019 12:59:54 +0100 Subject: [PATCH 083/107] add --sensorid option --- gigasetelements/gigasetelements.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index ff857f0..8d86e81 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -90,6 +90,7 @@ parser.add_argument('-P', '--pid', help='fully qualified name of pid file', default='/tmp/gigasetelements-cli.pid', required=False) parser.add_argument('-s', '--sensor', help='''show sensor status (use -ss to include sensor id's)''', action='count', default=0, required=False) parser.add_argument('-b', '--siren', help='arm/disarm siren', required=False, choices=('arm', 'disarm')) +parser.add_argument('-B', '--sensorid', help='select sensor', type=str, required=False, metavar='sensor id') parser.add_argument('-g', '--plug', help='switch plug on/off', required=False, choices=('on', 'off')) parser.add_argument('-y', '--privacy', help='switch privacy mode on/off', required=False, choices=('on', 'off')) parser.add_argument('-a', '--stream', help='start camera cloud based streams', type=str, required=False, metavar='MAC address') @@ -325,7 +326,9 @@ def plug(basestation_data, sensor_exist, sensor_id): if not sensor_exist['smart_plug']: log('Plug'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | Not found', 3, 1) switch = {"name": args.plug} - if 'sp02' in sensor_id: + if args.sensorid is not None: + plugid = args.sensorid.lower() + elif 'sp02' in sensor_id: plugid = sensor_id['sp02'][0] else: plugid = sensor_id['sp01'][0] From 291d9a3759b87d9d9c7b8f5e50ef38bb2b8cf03f Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Tue, 12 May 2020 14:58:59 +0200 Subject: [PATCH 084/107] remove rtmp stream url --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 8d86e81..7785f8a 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -593,7 +593,7 @@ def camera_stream(sensor_id, sensor_exist): else: mac = sensor_id['yc01'][0] stream_data = rest(GET, URL_CAMERA + '/' + mac + '/liveview/start') - for stream in ('m3u8', 'rtmp', 'rtsp'): + for stream in ('m3u8', 'rtsp'): log('Stream'.ljust(17) + ' | ' + stream.upper().ljust(8) + ' | ' + stream_data['uri'][stream]) return From 878d5b76c84e5082770ec8ae7da1b44d64260dc8 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sat, 20 Jun 2020 13:25:08 +0200 Subject: [PATCH 085/107] Add thermostat --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 7785f8a..4624875 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -52,7 +52,7 @@ SENSOR_FRIENDLY = {'ws02': 'window_sensor', 'ps01': 'presence_sensor', 'ps02': 'presence_sensor', 'ds01': 'door_sensor', 'ds02': 'door_sensor', 'is01': 'indoor_siren', 'sp01': 'smart_plug', 'sp02': 'smart_plug', 'bn01': 'button', 'yc01': 'camera', 'sd01': 'smoke', 'um01': 'umos', 'hb01': 'hue_bridge', 'hb01.hl01': 'hue_light', - 'bs01': 'base_station', 'wd01': 'water_sensor', 'cl01': 'climate_sensor'} + 'bs01': 'base_station', 'wd01': 'water_sensor', 'cl01': 'climate_sensor', 'ts01': 'thermostat'} AUTH_EXPIRE = 14400 From 3acd858627b84da051616197258073a38ea2d1a6 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 17 Jul 2020 09:27:27 +0200 Subject: [PATCH 086/107] Update README.rst --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 006d6b1..3c880cf 100644 --- a/README.rst +++ b/README.rst @@ -155,6 +155,7 @@ A lot of time & effort goes into making gigasetelements-cli so if you like it yo * *Auke C* * *RPC B* * *Silke H* +* *Frank M* License ------- From 63d3ee049fa0429fae86a8b2a67c533766b2e266 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 17 Jul 2020 14:19:12 +0200 Subject: [PATCH 087/107] add option to write elements json object to file --- gigasetelements/gigasetelements.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 4624875..820a7c8 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -65,6 +65,7 @@ URL_HEALTH = 'https://api.gigaset-elements.de/api/v2/me/health' URL_CHANNEL = 'https://api.gigaset-elements.de/api/v1/me/notifications/users/channels' URL_RELEASE = 'https://pypi.python.org/pypi/gigasetelements-cli/json' +URL_ELEMENTS = 'https://api.gigaset-elements.de/api/v2/me/elements' URL_SWITCH = '/json.htm?type=command¶m=switchlight&switchcmd=' URL_ALERT = '/json.htm?type=command¶m=udevice&idx=' @@ -106,6 +107,7 @@ parser.add_argument('-S', '--silent', help='suppress urllib3 warnings', action='store_true', required=False) parser.add_argument('-U', '--url', help='url (domoticz)', required=False) parser.add_argument('-X', '--sensorpairs', help='idx keypairs (domoticz)', required=False, action='append') +parser.add_argument('-E', '--elements', help='write elements json object to file', nargs='?', const='/tmp/gigasetelements-cli.json', type=str, required=False) parser.add_argument('-v', '--version', help='show version', action='version', version='%(prog)s version ' + str(_VERSION_)) args = parser.parse_args() @@ -169,7 +171,7 @@ def filewritable(filetype, fileloc, mustexit=1): def color(txt): """Add color to string based on presence in list and return in uppercase.""" green = ['ok', 'online', 'closed', 'up_to_date', 'home', 'auto', 'on', 'hd', 'cable', 'normal', 'daemon', 'wifi', - 'started', 'active', 'green', 'armed', 'pushed', 'verified', 'loaded', 'success', 'download', 'scheduled'] + 'started', 'active', 'green', 'armed', 'pushed', 'verified', 'loaded', 'success', 'download', 'scheduled', 'write'] orange = ['orange', 'warning', 'update'] if args.log is not None: txt = txt.upper() @@ -648,6 +650,16 @@ def start_logger(logfile): return +def get_elements(): + """Write elements json object.""" + elements = rest(GET, URL_ELEMENTS) + if filewritable('JSON file', args.elements, 0): + log('JSON file'.ljust(17) + ' | ' + color('write'.ljust(8)) + ' | ' + args.elements) + with open(args.elements, 'w') as outfile: + json.dump(elements, outfile) + return + + def base(): """Base program.""" pb_body = None @@ -722,6 +734,9 @@ def base(): else: list_events() + if args.elements: + get_elements() + if args.monitor: if args.monitor > 1 and args.sensorpairs: try: From 02fdb5edde30d114a3b4ad4b25e958acb1ebb3c4 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sat, 18 Jul 2020 09:44:19 +0200 Subject: [PATCH 088/107] fix unicode --- gigasetelements/gigasetelements.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 820a7c8..c9b74fb 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -653,10 +653,12 @@ def start_logger(logfile): def get_elements(): """Write elements json object.""" elements = rest(GET, URL_ELEMENTS) + if sys.version_info[0] < 3 or os.name == 'nt': + elements = unicode(elements) if filewritable('JSON file', args.elements, 0): log('JSON file'.ljust(17) + ' | ' + color('write'.ljust(8)) + ' | ' + args.elements) with open(args.elements, 'w') as outfile: - json.dump(elements, outfile) + json.dump(elements, outfile, indent=4, sort_keys=False) return From ad2ec0f0b8214478e2b9df00466cb09acdbcde80 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sun, 19 Jul 2020 09:11:14 +0200 Subject: [PATCH 089/107] add support for thermostat and climate --- gigasetelements/gigasetelements.py | 50 ++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index c9b74fb..e94af95 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -243,6 +243,7 @@ def systemstatus(): log('Basestation'.ljust(17) + ' | ' + color(basestation_data[0]['status'].ljust(8)) + ' | ' + basestation_data[0]['id']) camera_data = rest(GET, URL_CAMERA) status_data = rest(GET, URL_HEALTH) + elements_data = rest(GET, URL_ELEMENTS) if status_data['system_health'] == 'green': status_data['status_msg_id'] = '' else: @@ -250,7 +251,7 @@ def systemstatus(): if args.modus is None: log('Status'.ljust(17) + ' | ' + color(status_data['system_health'].ljust(8)) + status_data['status_msg_id'].upper() + ' | Modus ' + color(basestation_data[0]['intrusion_settings']['active_mode'])) - return basestation_data, status_data, camera_data + return basestation_data, status_data, camera_data, elements_data def check_version(): @@ -524,20 +525,21 @@ def domoticz(event, sid, friendly, basestation_data, url_domo, cfg_domo): return -def sensor(basestation_data, sensor_exist, camera_data): +def sensor(basestation_data, sensor_exist, camera_data, elements_data): """Show sensor details and current state.""" log(basestation_data[0]['friendly_name'].ljust(17) + ' | ' + color(basestation_data[0] ['status'].ljust(8)) + ' | firmware ' + color(basestation_data[0]['firmware_status'])) for item in basestation_data[0]['sensors']: try: - log(item['friendly_name'].ljust(17) + ' | ' + color(item['status'].ljust(8)) + ' | firmware ' + color(item['firmware_status']), 0, 0, 0) - if item['type'] not in ['is01', 'sp01', 'sp02']: - print('| battery ' + color(item['battery']['state']), end=' ') - if item['type'] in ['ds02', 'ds01']: - print('| position ' + color(item['position_status']), end=' ') - if args.sensor > 1: - print('| ' + item['id'].upper(), end=' ') - print() + if item['type'] not in ['cl01', 'ts01']: + log(item['friendly_name'].ljust(17) + ' | ' + color(item['status'].ljust(8)) + ' | firmware ' + color(item['firmware_status']), 0, 0, 0) + if item['type'] not in ['is01', 'sp01', 'sp02']: + print('| battery ' + color(item['battery']['state']), end=' ') + if item['type'] in ['ds02', 'ds01']: + print('| position ' + color(item['position_status']), end=' ') + if args.sensor > 1: + print('| ' + item['id'].upper(), end=' ') + print() except KeyError: print() if sensor_exist['camera']: @@ -554,6 +556,21 @@ def sensor(basestation_data, sensor_exist, camera_data): print() except KeyError: print() + if sensor_exist['thermostat']: + try: + for clm in elements_data["bs01"][0]["subelements"]: + if clm['type'] in ['bs01.ts01', 'bs01.cl01']: + print('[-] ' + clm['friendlyName'].ljust(17) + ' | ' + color(clm['connectionStatus'].ljust(8)) + ' | firmware ' + color(clm['firmwareStatus']) + + ' | battery ' + color(clm['batteryStatus']) + ' | temperature ' +str(round(clm['states']['temperature'], 1)) , end=' ') + if clm['type'] == 'bs01.ts01': + print('| setpoint ' +str(int(clm['states']['setPoint'])) , end=' ') + else: + print('| humidity ' +str(round(clm['states']['humidity'], 1)) , end=' ') + if args.sensor > 1: + print('| ' + clm['id'].rsplit(".", 1)[1].upper(), end=' ') + print() + except KeyError: + print() return @@ -650,15 +667,14 @@ def start_logger(logfile): return -def get_elements(): +def get_elements(elements_data): """Write elements json object.""" - elements = rest(GET, URL_ELEMENTS) if sys.version_info[0] < 3 or os.name == 'nt': - elements = unicode(elements) + elements_data = unicode(elements) if filewritable('JSON file', args.elements, 0): log('JSON file'.ljust(17) + ' | ' + color('write'.ljust(8)) + ' | ' + args.elements) with open(args.elements, 'w') as outfile: - json.dump(elements, outfile, indent=4, sort_keys=False) + json.dump(elements_data, outfile, indent=4, sort_keys=False) return @@ -684,7 +700,7 @@ def base(): auth_time = authenticate() - basestation_data, status_data, camera_data = systemstatus() + basestation_data, status_data, camera_data, elements_data = systemstatus() sensor_id, sensor_exist = collect_hw(basestation_data, camera_data) @@ -695,7 +711,7 @@ def base(): basestation_data[0]['intrusion_settings']['active_mode'].upper() + ' to ' + args.modus.upper() if args.sensor: - sensor(basestation_data, sensor_exist, camera_data) + sensor(basestation_data, sensor_exist, camera_data, elements_data) if status_data['status_msg_id'] == '': status_data['status_msg_id'] = '\u2713' pb_body = 'Status ' + status_data['system_health'].upper() + ' | ' + status_data['status_msg_id'].upper() + \ @@ -737,7 +753,7 @@ def base(): list_events() if args.elements: - get_elements() + get_elements(elements_data) if args.monitor: if args.monitor > 1 and args.sensorpairs: From adb68df7b7472b165d401268ba5bef438d46fe6e Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sun, 19 Jul 2020 09:14:12 +0200 Subject: [PATCH 090/107] add support climate --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index e94af95..8ceec45 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -556,7 +556,7 @@ def sensor(basestation_data, sensor_exist, camera_data, elements_data): print() except KeyError: print() - if sensor_exist['thermostat']: + if sensor_exist['thermostat'] or sensor_exist['climate_sensor']: try: for clm in elements_data["bs01"][0]["subelements"]: if clm['type'] in ['bs01.ts01', 'bs01.cl01']: From 475a49f9bcdde2348b00ca8322fb4c18e7725fcb Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sun, 19 Jul 2020 09:16:51 +0200 Subject: [PATCH 091/107] fix json dump --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 8ceec45..ab73771 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -674,7 +674,7 @@ def get_elements(elements_data): if filewritable('JSON file', args.elements, 0): log('JSON file'.ljust(17) + ' | ' + color('write'.ljust(8)) + ' | ' + args.elements) with open(args.elements, 'w') as outfile: - json.dump(elements_data, outfile, indent=4, sort_keys=False) + json.dump(elements_data, outfile, indent=4, sort_keys=False, ensure_ascii=False) return From 3b728c32ad0b9798a1865ba89dbf6985bef6229a Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sun, 19 Jul 2020 13:03:41 +0200 Subject: [PATCH 092/107] fix json for python2 and/or non linux --- gigasetelements/gigasetelements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index ab73771..99cfc70 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -568,7 +568,7 @@ def sensor(basestation_data, sensor_exist, camera_data, elements_data): print('| humidity ' +str(round(clm['states']['humidity'], 1)) , end=' ') if args.sensor > 1: print('| ' + clm['id'].rsplit(".", 1)[1].upper(), end=' ') - print() + print() except KeyError: print() return @@ -670,7 +670,7 @@ def start_logger(logfile): def get_elements(elements_data): """Write elements json object.""" if sys.version_info[0] < 3 or os.name == 'nt': - elements_data = unicode(elements) + elements_data = unicode(elements_data) if filewritable('JSON file', args.elements, 0): log('JSON file'.ljust(17) + ' | ' + color('write'.ljust(8)) + ' | ' + args.elements) with open(args.elements, 'w') as outfile: From 8c290a5f4ac2c469a129a8a4b16143da1d7bb536 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 20 Jul 2020 09:40:47 +0200 Subject: [PATCH 093/107] add window sensor position status --- gigasetelements/gigasetelements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 99cfc70..62c0c50 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -535,7 +535,7 @@ def sensor(basestation_data, sensor_exist, camera_data, elements_data): log(item['friendly_name'].ljust(17) + ' | ' + color(item['status'].ljust(8)) + ' | firmware ' + color(item['firmware_status']), 0, 0, 0) if item['type'] not in ['is01', 'sp01', 'sp02']: print('| battery ' + color(item['battery']['state']), end=' ') - if item['type'] in ['ds02', 'ds01']: + if item['type'] in ['ds02', 'ds01', 'ws02']: print('| position ' + color(item['position_status']), end=' ') if args.sensor > 1: print('| ' + item['id'].upper(), end=' ') From bf832395d4ba148736b72e1a6a519c6b886fc15c Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Fri, 24 Jul 2020 08:56:29 +0200 Subject: [PATCH 094/107] allow pipe / redirect --- gigasetelements/gigasetelements.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 62c0c50..ed21629 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -140,6 +140,8 @@ def log(logme, rbg=0, exitnow=0, newline=None): logme = unicode(logme) if os.name == 'nt' or args.log is not None: logme = unidecode.unidecode(logme) + if os.name == 'posix' and args.log is None and sys.version_info[0] < 3 and sys.stdout.encoding is None: + logme = unidecode.unidecode(logme) if args.log is not None: logger = logging.getLogger(__name__) logger.info('[' + time.strftime('%c') + '] ' + logme) From aa0239f41caf12a5a3dc0167b065fef71b75cfd4 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 30 Jul 2020 10:12:09 +0200 Subject: [PATCH 095/107] add support universal --- gigasetelements/gigasetelements.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index ed21629..ed717b8 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -558,16 +558,20 @@ def sensor(basestation_data, sensor_exist, camera_data, elements_data): print() except KeyError: print() - if sensor_exist['thermostat'] or sensor_exist['climate_sensor']: + if sensor_exist['thermostat'] or sensor_exist['climate_sensor'] or sensor_exist['umos']: try: for clm in elements_data["bs01"][0]["subelements"]: - if clm['type'] in ['bs01.ts01', 'bs01.cl01']: + if clm['type'] in ['bs01.ts01', 'bs01.cl01', 'bs01.um01']: print('[-] ' + clm['friendlyName'].ljust(17) + ' | ' + color(clm['connectionStatus'].ljust(8)) + ' | firmware ' + color(clm['firmwareStatus']) + ' | battery ' + color(clm['batteryStatus']) + ' | temperature ' +str(round(clm['states']['temperature'], 1)) , end=' ') if clm['type'] == 'bs01.ts01': print('| setpoint ' +str(int(clm['states']['setPoint'])) , end=' ') - else: + elif clm['type'] == 'bs01.um01': + print('| pressure ' +str(int(clm['states']['pressure'])) , end=' ') + elif clm['type'] == 'bs01.cl01': print('| humidity ' +str(round(clm['states']['humidity'], 1)) , end=' ') + else: + pass if args.sensor > 1: print('| ' + clm['id'].rsplit(".", 1)[1].upper(), end=' ') print() From 4e2b23fd7e60207bc8a75df6fc1cf6eb6226ceb8 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 24 Aug 2020 09:17:23 +0200 Subject: [PATCH 096/107] remove domoticz support --- README.rst | 2 - gigasetelements-cli.conf.template | 26 ----------- gigasetelements/gigasetelements.py | 75 +++--------------------------- 3 files changed, 6 insertions(+), 97 deletions(-) diff --git a/README.rst b/README.rst index 3c880cf..de38f35 100644 --- a/README.rst +++ b/README.rst @@ -46,7 +46,6 @@ Features * Show custom rules (button/plug) * Switch plug on/off * Set alarm trigger delay - * Domoticz home automation system support Usage ----- @@ -131,7 +130,6 @@ To do * Improve overall code * Replicate all functionality from app and/or website ... a long list * Support for gigaset elements button -* Support for domoticz home automation system Notes diff --git a/gigasetelements-cli.conf.template b/gigasetelements-cli.conf.template index 0390a40..a26f1d2 100644 --- a/gigasetelements-cli.conf.template +++ b/gigasetelements-cli.conf.template @@ -43,29 +43,3 @@ password=mybigsecret # Don't periodically check PyPI to determine whether a new version of gigasetelements-cli is available for download {yes,no} (optional) # noupdate=no - - -[domoticz] - -# http(s) server url including basic authentication credentials if applicable (required) -# url=http://user:password@ip:port -# url=http://ip:port -# url=http://user:pwd@127.0.0.1:8080 -# url=http://127.0.0.1:8080 - -# delimeted sensor / idx pair list (required) (Note: base needs 2 (comma separated) idx values, see step 3 and 4 below) -# sensorpairs=basesensorid:idx,idx;sensorid:idx;sensorid:idx -# sensorpairs=F32A76C4DHJ1B743A0E0D74EFD2375D1:10,11;123FC4577H:12;7C2G30873SED:13 - -# Instructions - -# [01] Go to Domoticz GUI -# [02] Add hardware - dummy -# [03] Create a virtual sensor - type [General, Alert] for basestation (health) (remember the idx number) -# [04] Create a second virtual sensor - type [Light/Switch, Selector Switch] for basestation (modus) (remember the idx number) -# [05] Set level 10 = Home, 20 = Custom, 30 = Away, 40 = Night and enable option Hide Off level -# [06] Create a virtual sensor - type [Light/Switch, Switch] for each motion/door/window/siren/plug/camera (remember their idx numbers) -# [07] Create a virtual sensor - type [Light/Switch, Selector Switch] for button (remember the idx number) -# [08] Set level 10 = Short, 20 = Double, 30 = Long, 40 = Very long and enable option Hide Off level -# [09] Set Off Delay to 2 seconds for virtual sensors linked to motion, button or camera -# [10] Run "gigasetelements-cli -ss" to get sensor ids and match them with the their respective idx in domoticz diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index ed717b8..0b778a0 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -43,7 +43,7 @@ os.path.expanduser('~/Library/Application Support/gigasetelements-cli/gigasetelements-cli.conf')] _AUTHOR_ = 'dynasticorpheus@gmail.com' -_VERSION_ = '1.5.0b5' +_VERSION_ = '1.6.0b0' LOGCL = {0: Fore.RESET, 1: Fore.GREEN, 2: Fore.YELLOW, 3: Fore.RED} LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4', 'home': '10', @@ -67,10 +67,6 @@ URL_RELEASE = 'https://pypi.python.org/pypi/gigasetelements-cli/json' URL_ELEMENTS = 'https://api.gigaset-elements.de/api/v2/me/elements' -URL_SWITCH = '/json.htm?type=command¶m=switchlight&switchcmd=' -URL_ALERT = '/json.htm?type=command¶m=udevice&idx=' -URL_LOG = '/json.htm?type=command¶m=addlogmessage&message=' - parser = configargparse.ArgParser(description='Gigaset Elements - Command-line Interface by dynasticorpheus@gmail.com', default_config_files=CONFPATH) parser.add_argument('-c', '--config', help='fully qualified name of configuration-file', required=False, is_config_file=True) parser.add_argument('-u', '--username', help='username (email) in use with my.gigaset-elements.com', required=True) @@ -84,7 +80,7 @@ 'door', 'window', 'motion', 'siren', 'plug', 'button', 'homecoming', 'intrusion', 'systemhealth', 'camera', 'phone', 'smoke', 'umos')) parser.add_argument('-m', '--modus', help='set modus', required=False, choices=('home', 'away', 'custom', 'night')) parser.add_argument('-k', '--delay', help='set alarm timer delay in seconds (use 0 to disable)', type=int, required=False) -parser.add_argument('-D', '--daemon', help='daemonize during monitor/domoticz mode', action='store_true', required=False) +parser.add_argument('-D', '--daemon', help='daemonize during monitor mode', action='store_true', required=False) parser.add_argument('-z', '--notifications', help='show notification status', action='store_true', required=False) parser.add_argument('-l', '--log', help='fully qualified name of log file', required=False) parser.add_argument('-R', '--rules', help='show custom rules', action='store_true', required=False) @@ -97,7 +93,7 @@ parser.add_argument('-a', '--stream', help='start camera cloud based streams', type=str, required=False, metavar='MAC address') parser.add_argument('-r', '--record', help='switch camera recording on/off', type=str, required=False, metavar='MAC address') parser.add_argument('-A', '--snapshot', help='download camera snapshot', type=str, required=False, metavar='MAC address') -parser.add_argument('-t', '--monitor', help='show events using monitor mode (use -tt to activate domoticz mode)', action='count', default=0, required=False) +parser.add_argument('-t', '--monitor', help='show events using monitor mode', action='store_true', required=False) parser.add_argument('-i', '--ignore', help='ignore configuration-file at predefined locations', action='store_true', required=False) parser.add_argument('-N', '--noupdate', help='do not periodically check for updates', action='store_true', required=False) parser.add_argument('-j', '--restart', help='automatically restart program in case of a connection error', action='store_true', required=False) @@ -105,8 +101,6 @@ parser.add_argument('-q', '--quiet', help='do not send pushbullet message', action='store_true', required=False) parser.add_argument('-I', '--insecure', help='disable SSL/TLS certificate verification', action='store_true', required=False) parser.add_argument('-S', '--silent', help='suppress urllib3 warnings', action='store_true', required=False) -parser.add_argument('-U', '--url', help='url (domoticz)', required=False) -parser.add_argument('-X', '--sensorpairs', help='idx keypairs (domoticz)', required=False, action='append') parser.add_argument('-E', '--elements', help='write elements json object to file', nargs='?', const='/tmp/gigasetelements-cli.json', type=str, required=False) parser.add_argument('-v', '--version', help='show version', action='version', version='%(prog)s version ' + str(_VERSION_)) @@ -448,52 +442,24 @@ def list_events(): return -def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): +def monitor(auth_time, basestation_data, status_data): """List events realtime optionally filtered by type.""" - health = modus = '' - epoch = time.time() - 60 url_monitor = URL_EVENTS + '?limit=10' if args.filter is not None: url_monitor = url_monitor + '&group=' + args.filter - if cfg_domo and args.monitor > 1: - mode = 'Domoticz mode' - rest(GET, url_domo + URL_LOG + 'Gigaset Elements - Command-line Interface: Domoticz mode started') - else: - mode = 'Monitor mode' - args.monitor = 1 - log(mode.ljust(17) + ' | ' + color('started'.ljust(8)) + ' | ' + 'CTRL+C to exit') + log('Monitor mode'.ljust(17) + ' | ' + color('started'.ljust(8)) + ' | ' + 'CTRL+C to exit') from_ts = str(int(time.time()) * 1000) try: while 1: - if args.monitor > 1 and time.time() - epoch > 59: - status_data = rest(GET, URL_HEALTH) - if health != status_data['system_health'].lower(): - domoticz(status_data['system_health'].lower(), basestation_data[0]['id'].lower(), - basestation_data[0]['friendly_name'].lower(), basestation_data, url_domo, cfg_domo) - health = status_data['system_health'].lower() - basestation_data = rest(GET, URL_BASE) - if modus != basestation_data[0]['intrusion_settings']['active_mode']: - domoticz(basestation_data[0]['intrusion_settings']['active_mode'].lower(), basestation_data[0]['id'].lower(), - basestation_data[0]['friendly_name'].lower(), basestation_data, url_domo, cfg_domo) - modus = basestation_data[0]['intrusion_settings']['active_mode'] - epoch = time.time() lastevents = rest(GET, url_monitor + '&from_ts=' + from_ts) for item in reversed(lastevents['events']): try: if 'type' in item['o']: log(time.strftime('%m/%d/%y %H:%M:%S', time.localtime(int(item['ts']) / 1000)) + ' | ' + item['o'][ 'type'].ljust(8) + ' | ' + item['type'] + ' ' + item['o'].get('friendly_name', item['o']['type']), 0, 0, 2) - if args.monitor > 1: - if item['o']['type'] == 'ycam': - domoticz(item['type'][5:].lower(), item['source_id'].lower(), 'ycam', basestation_data, url_domo, cfg_domo) - else: - domoticz(item['type'].lower(), item['o']['id'].lower(), item['o'].get('friendly_name', 'basestation').lower(), - basestation_data, url_domo, cfg_domo) else: log(time.strftime('%m/%d/%y %H:%M:%S', time.localtime(int(item['ts']) / 1000)) + ' | ' + 'system'.ljust(8) + ' | ' + item['source_type'] + ' ' + item['type'], 0, 0, 2) - if args.monitor > 1: - domoticz(item['type'].lower(), basestation_data[0]['id'].lower(), item['source_type'].lower(), basestation_data, url_domo, cfg_domo) from_ts = str(int(item['ts']) + 1) except KeyError: continue @@ -502,31 +468,10 @@ def monitor(auth_time, basestation_data, status_data, url_domo, cfg_domo): else: time.sleep(1) except KeyboardInterrupt: - if args.monitor > 1: - rest(GET, url_domo + URL_LOG + 'Gigaset Elements - Command-line Interface: Domoticz mode halted') log('Program'.ljust(17) + ' | ' + color('halted'.ljust(8)) + ' | ' + 'CTRL+C', 0, 1, 2) return -def domoticz(event, sid, friendly, basestation_data, url_domo, cfg_domo): - """Push events to domoticz server.""" - if event in ['open', 'close', 'sirenon', 'sirenoff', 'on', 'off', 'movement', 'motion']: - if event in ['close', 'sirenoff', 'off']: - cmd = 'off' - else: - cmd = 'on' - rest(GET, url_domo + URL_SWITCH + cmd.title() + '&idx=' + cfg_domo[sid]) - elif event in ['button1', 'button2', 'button3', 'button4']: - rest(GET, url_domo + URL_ALERT + cfg_domo[sid] + '&nvalue=1' + '&svalue=' + event[-1:] + '0') - elif event in ['home', 'custom', 'away', 'night']: - rest(GET, url_domo + URL_ALERT + cfg_domo[basestation_data[0]['id'].lower()].split(',')[1] + '&nvalue=1' + '&svalue=' + LEVEL.get(event)) - else: - status_data = rest(GET, URL_HEALTH) - rest(GET, url_domo + URL_ALERT + cfg_domo[basestation_data[0]['id'].lower()].split(',')[0] + '&nvalue=' + - LEVEL.get(status_data['system_health'], '3') + '&svalue=' + friendly + ' | ' + event) - return - - def sensor(basestation_data, sensor_exist, camera_data, elements_data): """Show sensor details and current state.""" log(basestation_data[0]['friendly_name'].ljust(17) + ' | ' + color(basestation_data[0] @@ -762,15 +707,7 @@ def base(): get_elements(elements_data) if args.monitor: - if args.monitor > 1 and args.sensorpairs: - try: - for keypair in args.sensorpairs: - cfg_domo = {key: value for key, value in (rule.split(":") for rule in keypair.lower().split(';'))} - except ValueError: - log('Config'.ljust(17) + ' | ' + 'ERROR'.ljust(8) + ' | check sensor pairing value format', 3, 1) - else: - cfg_domo = None - monitor(auth_time, basestation_data, status_data, args.url, cfg_domo) + monitor(auth_time, basestation_data, status_data) print() except KeyboardInterrupt: log('Program'.ljust(17) + ' | ' + color('halted'.ljust(8)) + ' | ' + 'CTRL+C', 0, 1, 2) From afc78a1f4aac104d8aeda21c963e70a8f380320e Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Wed, 26 Aug 2020 21:16:42 +0200 Subject: [PATCH 097/107] add --panic and --end --- gigasetelements/gigasetelements.py | 31 +++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 0b778a0..ed13c27 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -63,6 +63,8 @@ URL_BASE = 'https://api.gigaset-elements.de/api/v1/me/basestations' URL_CAMERA = 'https://api.gigaset-elements.de/api/v1/me/cameras' URL_HEALTH = 'https://api.gigaset-elements.de/api/v2/me/health' +URL_DEVICES = 'https://api.gigaset-elements.de/api/v1/me/devices' +URL_STATES = 'https://api.gigaset-elements.de/api/v1/me/states' URL_CHANNEL = 'https://api.gigaset-elements.de/api/v1/me/notifications/users/channels' URL_RELEASE = 'https://pypi.python.org/pypi/gigasetelements-cli/json' URL_ELEMENTS = 'https://api.gigaset-elements.de/api/v2/me/elements' @@ -82,6 +84,8 @@ parser.add_argument('-k', '--delay', help='set alarm timer delay in seconds (use 0 to disable)', type=int, required=False) parser.add_argument('-D', '--daemon', help='daemonize during monitor mode', action='store_true', required=False) parser.add_argument('-z', '--notifications', help='show notification status', action='store_true', required=False) +parser.add_argument('-X', '--panic', help='trigger alarm', action='store_true', required=False) +parser.add_argument('-U', '--end', help='end alarm', action='store_true', required=False) parser.add_argument('-l', '--log', help='fully qualified name of log file', required=False) parser.add_argument('-R', '--rules', help='show custom rules', action='store_true', required=False) parser.add_argument('-P', '--pid', help='fully qualified name of pid file', default='/tmp/gigasetelements-cli.pid', required=False) @@ -109,7 +113,7 @@ s = requests.Session() s.mount('http://', requests.adapters.HTTPAdapter(max_retries=3)) s.mount('https://', requests.adapters.HTTPAdapter(max_retries=3)) -POST, GET = s.post, s.get +POST, GET, DELETE = s.post, s.get, s.delete if args.silent: @@ -166,7 +170,7 @@ def filewritable(filetype, fileloc, mustexit=1): def color(txt): """Add color to string based on presence in list and return in uppercase.""" - green = ['ok', 'online', 'closed', 'up_to_date', 'home', 'auto', 'on', 'hd', 'cable', 'normal', 'daemon', 'wifi', + green = ['ok', 'online', 'closed', 'up_to_date', 'home', 'auto', 'on', 'hd', 'cable', 'normal', 'daemon', 'wifi', 'ended', 'started', 'active', 'green', 'armed', 'pushed', 'verified', 'loaded', 'success', 'download', 'scheduled', 'write'] orange = ['orange', 'warning', 'update'] if args.log is not None: @@ -203,7 +207,7 @@ def rest(method, url, payload=None, header=False, timeout=90, end=1, silent=Fals log('ERROR'.ljust(17) + ' | ' + 'UNKNOWN'.ljust(8) + ' | ' + str(error), 3, end) if request is not None: if not silent: - if request.status_code != requests.codes.ok: # pylint: disable=no-member + if not request.ok: # pylint: disable=no-member urlsplit = urlparse(request.url) log('HTTP ERROR'.ljust(17) + ' | ' + str(request.status_code).ljust(8) + ' | ' + request.reason + ' ' + str(urlsplit.path), 3, end) contenttype = request.headers.get('Content-Type', default='').split(';')[0] @@ -287,6 +291,21 @@ def modus_switch(basestation_data, status_data): return +def trigger_alarm(): + """Trigger alarm.""" + switch = {'action': 'alarm.user.start'} + rest(POST, URL_DEVICES + '/webfrontend/sink', json.dumps(switch)) + log('Alarm'.ljust(17) + ' | ' + color('trigger'.ljust(8)) + ' | ') + return + + +def end_alarm(): + """End alarm.""" + rest(DELETE, URL_STATES + '/userAlarm') + log('Alarm'.ljust(17) + ' | ' + color('ended'.ljust(8)) + ' | ') + return + + def set_delay(basestation_data): """Set alarm trigger delay.""" switch = {"intrusion_settings": {"modes": [{"away": {"trigger_delay": str(args.delay * 1000)}}]}} @@ -706,6 +725,12 @@ def base(): if args.elements: get_elements(elements_data) + if args.panic: + trigger_alarm() + + if args.end: + end_alarm() + if args.monitor: monitor(auth_time, basestation_data, status_data) print() From e7f0fa95b260584dbc41ac822e1d3d32f7edd94a Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Thu, 27 Aug 2020 08:17:43 +0200 Subject: [PATCH 098/107] version bump to 1.6.0b1 --- gigasetelements/gigasetelements.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index ed13c27..68a8ffa 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -43,7 +43,7 @@ os.path.expanduser('~/Library/Application Support/gigasetelements-cli/gigasetelements-cli.conf')] _AUTHOR_ = 'dynasticorpheus@gmail.com' -_VERSION_ = '1.6.0b0' +_VERSION_ = '1.6.0b1' LOGCL = {0: Fore.RESET, 1: Fore.GREEN, 2: Fore.YELLOW, 3: Fore.RED} LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4', 'home': '10', diff --git a/setup.py b/setup.py index e81d982..90fb56b 100755 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setup( name='gigasetelements-cli', - version='1.5.0b5', + version='1.6.0b1', description='gigasetelements-cli allows you to control your \ Gigaset Elements home security system from the command line.', long_description=long_description, From 8265e7ac3a392b3716646ed408c839e94e679cab Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sat, 29 Aug 2020 10:52:27 +0200 Subject: [PATCH 099/107] cheers --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index de38f35..3350d30 100644 --- a/README.rst +++ b/README.rst @@ -154,6 +154,7 @@ A lot of time & effort goes into making gigasetelements-cli so if you like it yo * *RPC B* * *Silke H* * *Frank M* +* *Max G* License ------- From a2b9370dc1851ecc7299de71b033ce4882b04b69 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 31 Aug 2020 09:20:11 +0200 Subject: [PATCH 100/107] CodeQL Analysis --- .github/workflows/codeql-analysis.yml | 62 +++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..40af5de --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,62 @@ +name: "CodeQL" + +on: + push: + branches: [develop] + pull_request: + # The branches below must be a subset of the branches above + branches: [develop] + schedule: + - cron: '0 17 * * 0' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['python'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 From a8fbe8cc50e90824c6263e42ef64a23a770350d9 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Sat, 15 May 2021 14:10:17 +0200 Subject: [PATCH 101/107] cheers --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 3350d30..ecd4d78 100644 --- a/README.rst +++ b/README.rst @@ -155,6 +155,7 @@ A lot of time & effort goes into making gigasetelements-cli so if you like it yo * *Silke H* * *Frank M* * *Max G* +* *Andreas G* License ------- From d44ef8ef8fa720383c66e02802e8ec3d7c1832b9 Mon Sep 17 00:00:00 2001 From: Frank Mariak <120997999+cyfm-morphos@users.noreply.github.com> Date: Mon, 17 Apr 2023 08:57:43 +0200 Subject: [PATCH 102/107] added temp/humidity support for the gigaset water sensor --- gigasetelements/gigasetelements.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 68a8ffa..1de39ee 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -522,17 +522,17 @@ def sensor(basestation_data, sensor_exist, camera_data, elements_data): print() except KeyError: print() - if sensor_exist['thermostat'] or sensor_exist['climate_sensor'] or sensor_exist['umos']: + if sensor_exist['thermostat'] or sensor_exist['climate_sensor'] or sensor_exist['umos'] or sensor_exist['water_sensor']: try: for clm in elements_data["bs01"][0]["subelements"]: - if clm['type'] in ['bs01.ts01', 'bs01.cl01', 'bs01.um01']: + if clm['type'] in ['bs01.ts01', 'bs01.cl01', 'bs01.um01','bs01.wd01']: print('[-] ' + clm['friendlyName'].ljust(17) + ' | ' + color(clm['connectionStatus'].ljust(8)) + ' | firmware ' + color(clm['firmwareStatus']) + ' | battery ' + color(clm['batteryStatus']) + ' | temperature ' +str(round(clm['states']['temperature'], 1)) , end=' ') if clm['type'] == 'bs01.ts01': print('| setpoint ' +str(int(clm['states']['setPoint'])) , end=' ') elif clm['type'] == 'bs01.um01': print('| pressure ' +str(int(clm['states']['pressure'])) , end=' ') - elif clm['type'] == 'bs01.cl01': + elif clm['type'] in ['bs01.cl01','bs01.wd01']: print('| humidity ' +str(round(clm['states']['humidity'], 1)) , end=' ') else: pass From 255a8ab921f6f7f565d30d45e1138476330064a3 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 17 Apr 2023 11:02:49 +0200 Subject: [PATCH 103/107] Use appropriate path --- gigasetelements/gigasetelements.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 1de39ee..3a7435a 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -56,6 +56,8 @@ AUTH_EXPIRE = 14400 +JSONFILE = os.path.join(os.path.expanduser('~'), 'gigasetelements-cli.json') + URL_STATUS = 'https://status.gigaset-elements.de/api/v1/status' URL_IDENTITY = 'https://im.gigaset-elements.de/identity/api/v1/user/login' URL_AUTH = 'https://api.gigaset-elements.de/api/v1/auth/openid/begin?op=gigaset' @@ -88,7 +90,7 @@ parser.add_argument('-U', '--end', help='end alarm', action='store_true', required=False) parser.add_argument('-l', '--log', help='fully qualified name of log file', required=False) parser.add_argument('-R', '--rules', help='show custom rules', action='store_true', required=False) -parser.add_argument('-P', '--pid', help='fully qualified name of pid file', default='/tmp/gigasetelements-cli.pid', required=False) +parser.add_argument('-P', '--pid', help='fully qualified name of pid file', default='/var/run/gigasetelements-cli.pid', required=False) parser.add_argument('-s', '--sensor', help='''show sensor status (use -ss to include sensor id's)''', action='count', default=0, required=False) parser.add_argument('-b', '--siren', help='arm/disarm siren', required=False, choices=('arm', 'disarm')) parser.add_argument('-B', '--sensorid', help='select sensor', type=str, required=False, metavar='sensor id') @@ -105,7 +107,7 @@ parser.add_argument('-q', '--quiet', help='do not send pushbullet message', action='store_true', required=False) parser.add_argument('-I', '--insecure', help='disable SSL/TLS certificate verification', action='store_true', required=False) parser.add_argument('-S', '--silent', help='suppress urllib3 warnings', action='store_true', required=False) -parser.add_argument('-E', '--elements', help='write elements json object to file', nargs='?', const='/tmp/gigasetelements-cli.json', type=str, required=False) +parser.add_argument('-E', '--elements', help='write elements json object to file', nargs='?', const=JSONFILE, type=str, required=False) parser.add_argument('-v', '--version', help='show version', action='version', version='%(prog)s version ' + str(_VERSION_)) args = parser.parse_args() From 30c6e69961c55fea913b8392dfa5e2c4653080c7 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 17 Apr 2023 11:10:04 +0200 Subject: [PATCH 104/107] Update README.rst CodeQL --- README.rst | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index ecd4d78..3b69815 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Gigaset Elements API command-line interface =========================================== -|Version status| |Github stars| |Github forks| |Language grade: Python| |Total alerts| |Downloads| +|Version status| |Github stars| |Github forks| |CodeQL| |Downloads| gigasetelements-cli is a python based program which allows you to control your Gigaset Elements home security system. It comes with an easy to use CLI (command-line interface) suitable for direct use or cron jobs. @@ -165,10 +165,8 @@ GPL2 :target: https://pypi.python.org/pypi/gigasetelements-cli/ .. |Downloads| image:: https://img.shields.io/pypi/dm/gigasetelements-cli.svg :target: https://pypi.python.org/pypi/gigasetelements-cli/ -.. |Language grade: Python| image:: https://img.shields.io/lgtm/grade/python/g/dynasticorpheus/gigasetelements-cli.svg - :target: https://lgtm.com/projects/g/dynasticorpheus/gigasetelements-cli/context:python -.. |Total alerts| image:: https://img.shields.io/lgtm/alerts/g/dynasticorpheus/gigasetelements-cli.svg - :target: https://lgtm.com/projects/g/dynasticorpheus/gigasetelements-cli/alerts/ +.. |CodeQL| image:: https://github.com/dynasticorpheus/gigasetelements-cli/actions/workflows/codeql-analysis.yml/badge.svg + :target: https://github.com/dynasticorpheus/gigasetelements-cli/actions/workflows/codeql-analysis.yml .. |Github forks| image:: https://img.shields.io/github/forks/dynasticorpheus/gigasetelements-cli.svg :target: https://github.com/dynasticorpheus/gigasetelements-cli/network/members/ .. |Github stars| image:: https://img.shields.io/github/stars/dynasticorpheus/gigasetelements-cli.svg From 9e0491521fbe0ddd3b56dce98438178cfa59bb49 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 17 Apr 2023 11:15:03 +0200 Subject: [PATCH 105/107] Update README.rst BuyMeCoffee --- README.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 3b69815..6907ffe 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Gigaset Elements API command-line interface =========================================== -|Version status| |Github stars| |Github forks| |CodeQL| |Downloads| +|Version status| |Github stars| |Github forks| |CodeQL| |Downloads| |BuyMeCoffee| gigasetelements-cli is a python based program which allows you to control your Gigaset Elements home security system. It comes with an easy to use CLI (command-line interface) suitable for direct use or cron jobs. @@ -171,4 +171,5 @@ GPL2 :target: https://github.com/dynasticorpheus/gigasetelements-cli/network/members/ .. |Github stars| image:: https://img.shields.io/github/stars/dynasticorpheus/gigasetelements-cli.svg :target: https://github.com/dynasticorpheus/gigasetelements-cli/stargazers/ - +.. |BuyMeCoffee| image:: https://github.com/camo/cd005dca0ef55d7725912ec03a936d3a7c8de5b5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6275792532306d6525323061253230636f666665652d646f6e6174652d79656c6c6f772e737667 + :target: https://buymeacoffee.com/dynasticorpheus/ From ee2d022a986f7efa29e4d1750e435a95e9378133 Mon Sep 17 00:00:00 2001 From: dynasticorpheus Date: Mon, 17 Apr 2023 11:48:57 +0200 Subject: [PATCH 106/107] Update README.rst Sonarcloud --- README.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 6907ffe..4a4103b 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Gigaset Elements API command-line interface =========================================== -|Version status| |Github stars| |Github forks| |CodeQL| |Downloads| |BuyMeCoffee| +|Version status| |Github stars| |Github forks| |CodeQL| |Quality Gate Status| |Downloads| |BuyMeCoffee| gigasetelements-cli is a python based program which allows you to control your Gigaset Elements home security system. It comes with an easy to use CLI (command-line interface) suitable for direct use or cron jobs. @@ -173,3 +173,5 @@ GPL2 :target: https://github.com/dynasticorpheus/gigasetelements-cli/stargazers/ .. |BuyMeCoffee| image:: https://github.com/camo/cd005dca0ef55d7725912ec03a936d3a7c8de5b5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6275792532306d6525323061253230636f666665652d646f6e6174652d79656c6c6f772e737667 :target: https://buymeacoffee.com/dynasticorpheus/ +.. |Quality Gate Status| image:: https://sonarcloud.io/api/project_badges/measure?project=dynasticorpheus_gigasetelements-cli&metric=alert_status + :target: https://sonarcloud.io/summary/new_code?id=dynasticorpheus_gigasetelements-cli/ From ad763afc9f81d5f2f13f4c4a92fda5b4e7663d17 Mon Sep 17 00:00:00 2001 From: Fabian Lipken Date: Mon, 17 Apr 2023 15:16:59 +0200 Subject: [PATCH 107/107] bump version --- gigasetelements/gigasetelements.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gigasetelements/gigasetelements.py b/gigasetelements/gigasetelements.py index 3a7435a..0e3c128 100755 --- a/gigasetelements/gigasetelements.py +++ b/gigasetelements/gigasetelements.py @@ -43,7 +43,7 @@ os.path.expanduser('~/Library/Application Support/gigasetelements-cli/gigasetelements-cli.conf')] _AUTHOR_ = 'dynasticorpheus@gmail.com' -_VERSION_ = '1.6.0b1' +_VERSION_ = '2023.4.0' LOGCL = {0: Fore.RESET, 1: Fore.GREEN, 2: Fore.YELLOW, 3: Fore.RED} LEVEL = {'intrusion': '4', 'unusual': '3', 'button': '2', 'ok': '1', 'green': '1', 'orange': '3', 'red': '4', 'home': '10', diff --git a/setup.py b/setup.py index 90fb56b..8940119 100755 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setup( name='gigasetelements-cli', - version='1.6.0b1', + version='2023.4.0', description='gigasetelements-cli allows you to control your \ Gigaset Elements home security system from the command line.', long_description=long_description,