pax_global_header00006660000000000000000000000064145540100660014513gustar00rootroot0000000000000052 comment=e53661711ec7a86470401b5ff22a5000ac66a949 majuss-lupupy-ef35197/000077500000000000000000000000001455401006600147165ustar00rootroot00000000000000majuss-lupupy-ef35197/.gitignore000066400000000000000000000007751455401006600167170ustar00rootroot00000000000000# Compiled source # ################### *.com *.class *.dll *.exe *.o *.so *.pyc venv __pycache__ test_* *.egg-info/ *.vscode/ build/ dist/ # Packages # ############ # it's better to unpack these files and commit the raw source # git has its own built in compression methods *.7z *.dmg *.gz *.iso *.jar *.rar *.tar *.zip # Logs and databases # ###################### *.log *.sql *.sqlite # OS generated files # ###################### .DS_Store .DS_Store? ._* .Spotlight-V100 .Trashes ehthumbs.db Thumbs.db majuss-lupupy-ef35197/LICENSE000066400000000000000000000020471455401006600157260ustar00rootroot00000000000000MIT License Copyright (c) 2018 Majuss Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. majuss-lupupy-ef35197/README.md000066400000000000000000000026561455401006600162060ustar00rootroot00000000000000# Lupupy A python3 library to communicate with the Lupus Electronics alarm control panel. ## Disclaimer: Published under the MIT license - See LICENSE file for more details. "Lupusec" is a trademark owned by Lupusec Electronics, see www.lupus-electronics.de for more information. I am in no way affiliated with Lupus Electronics. This library is based on the work of MisterWil See: https://github.com/MisterWil/abodepy. By "based" I mean that I copied huge portions of code and customized it to work with Lupusec. ## Installation You can install the library with pip: ``` pip install Lupupy ``` ## Usage You can integrate the library into your own project, or simply use it in the command line. ``` lupupy -u USERNAME -p PASSWORD -i IP_ADDRESS --devices ``` This will retrieve a simple list of all devices. --- ### Shortcomings The library currently only works with the XT1 alarm panel and since version 0.1.1 at least with the XT2. Others may work but aren't tested yet. The json responses of other panel will differ and most likely not work. Most of the advanced devices are not yet supported, I don't have the hardware to reverse engineer these devices. If someone need a further integration please open an issue and we will find a way. ### Currently supported features: - Status of binary sensors like door and window sensors - Setting the mode of the alarm control panel - Get the history for further parsing - Status of power switches majuss-lupupy-ef35197/lupupy/000077500000000000000000000000001455401006600162545ustar00rootroot00000000000000majuss-lupupy-ef35197/lupupy/__init__.py000066400000000000000000000335351455401006600203760ustar00rootroot00000000000000import requests import pickle import time import logging import json import unicodedata import yaml from pathlib import Path import lupupy.devices.alarm as ALARM import lupupy.constants as CONST from lupupy.devices.binary_sensor import LupusecBinarySensor from lupupy.devices.switch import LupusecSwitch from lupupy.exceptions import LupusecException _LOGGER = logging.getLogger(__name__) home = str(Path.home()) class Lupusec: """Interface to Lupusec Webservices.""" def __init__(self, username, password, ip_address, get_devices=False): """LupsecAPI constructor requires IP and credentials to the Lupusec Webinterface. """ self.session = requests.Session() self.session.auth = (username, password) self.api_url = "http://{}/action/".format(ip_address) self.headers = None self.model = self._get_model(ip_address) self._mode = None self._devices = None self._fail_counter = 0 if self.model == 1: resp = self.session.get(self.api_url + CONST.DEVICES_API_XT1) if resp.status_code == 200: _LOGGER.debug("XT1 found, setting it up") self.mode_translation = CONST.MODE_TRANSLATION_XT1 self.api_mode = "mode_st" self.api_sensors = CONST.DEVICES_API_XT1 self.api_device_id = "no" self._request_post("login") else: _LOGGER.debug("Unknown error while finding out which model is used") return elif self.model == 2: _LOGGER.debug("XT2 or higher found, setting up") self.mode_translation = CONST.MODE_TRANSLATION_XT2 self.api_mode = "mode_a1" self.api_sensors = CONST.DEVICES_API_XT2 self.api_device_id = "sid" self._token_ts = time.time() response = self._request_get("tokenGet") self.headers = {"X-Token": json.loads(response.text)["message"]} else: _LOGGER.error("Unable to setup Lupusec panel, model not supported.") try: self._history_cache = pickle.load( open(home + "/" + CONST.HISTORY_CACHE_NAME, "rb") ) except FileNotFoundError as e: _LOGGER.debug(e) self._history_cache = [] pickle.dump( self._history_cache, open(home + "/" + CONST.HISTORY_CACHE_NAME, "wb") ) self._panel = self.get_panel() self._cacheSensors = None self._cacheStampS = time.time() self._cachePss = None self._cacheStampP = time.time() if get_devices or self._devices is None: self.get_devices() def _request_get(self, action): if self.model == 2: ts = time.time() if ts - self._token_ts > 60: self._token_ts = ts response = self._request_get("tokenGet") self.headers = {"X-Token": json.loads(response.text)["message"]} response = self.session.get( self.api_url + action, timeout=15, headers=self.headers ) _LOGGER.debug( "Action and statuscode of apiGET command: %s, %s", action, response.status_code, ) return response def _request_post(self, action, params={}): if self.model == 2: ts = time.time() if ts - self._token_ts > 60: self._token_ts = ts response = self._request_get("tokenGet") self.headers = {"X-Token": json.loads(response.text)["message"]} return self.session.post( self.api_url + action, data=params, headers=self.headers ) def _get_model(self, ip_address): response = requests.get("http://{}/images/model.gif".format(ip_address)) if response.status_code == 200: return 1 else: return 2 def remove_control_characters(self, s): return "".join(ch for ch in s if unicodedata.category(ch)[0] != "C") def clean_json(self, textdata): _LOGGER.debug("Input for clean json" + textdata) if self.model == 1: textdata = textdata.replace("\t", "") i = textdata.index("\n") textdata = textdata[i + 1:-2] try: textdata = yaml.load(textdata, Loader=yaml.BaseLoader) except Exception as e: _LOGGER.warning( "Lupupy couldn't parse provided response: %s, %s", e, textdata ) return textdata else: try: return json.loads(self.remove_control_characters(textdata)) except json.decoder.JSONDecodeError as e: _LOGGER.error("Failed to parse JSON from " + str(textdata)) _LOGGER.error(e) def get_power_switches(self): stampNow = time.time() length = len(self._devices) if self._cachePss is None or stampNow - self._cacheStampP > 2.0: self._cacheStamp_p = stampNow response = self._request_get("pssStatusGet") response = self.clean_json(response.text)["forms"] powerSwitches = [] counter = 1 for pss in response: powerSwitch = {} if response[pss]["ready"] == 1: powerSwitch["status"] = response[pss]["pssonoff"] powerSwitch["device_id"] = counter + length powerSwitch["type"] = CONST.TYPE_POWER_SWITCH powerSwitch["name"] = response[pss]["name"] powerSwitches.append(powerSwitch) else: _LOGGER.debug("Pss skipped, not active") counter += 1 self._cachePss = powerSwitches return self._cachePss def get_sensors(self): stamp_now = time.time() if self._cacheSensors is None or stamp_now - self._cacheStampS > 2.0: self._cacheStampS = stamp_now response = self._request_get(self.api_sensors) response = self.clean_json(response.text)["senrows"] sensors = [] for device in response: if self.model == 1: device["status"] = device["cond"] else: if "openClose" in device: device["status"] = device["openClose"] device.pop("openClose") device["device_id"] = device[self.api_device_id] device.pop("cond") device.pop(self.api_device_id) if device["status"] == "{WEB_MSG_DC_OPEN}" or device["status"] == CONST.STATUS_OPEN: device["status"] = 1 if device["status"] == "{WEB_MSG_DC_CLOSE}" or device["status"] == "0" or device["status"] == "": device["status"] = "Geschlossen" sensors.append(device) self._cacheSensors = sensors return self._cacheSensors def get_panel( self, ): # we are trimming the json from Lupusec heavily, since its bullcrap response = self._request_get("panelCondGet") if response.status_code != 200: self._fail_counter += 1 if response.status_code == 401 and self.model == 2 and self._fail_counter < 5: response = self._request_get("tokenGet") self.headers = {"X-Token": json.loads(response.text)["message"]} self.get_panel() else: raise Exception("Unable to get panel " + str(response.status_code) + " Failed tries: " + self._fail_counter) panel = self.clean_json(response.text)["updates"] panel["mode"] = panel[self.api_mode] panel.pop(self.api_mode) if self.model == 2: panel["mode"] = CONST.XT2_MODES_TO_TEXT[panel["mode"]] panel["device_id"] = CONST.ALARM_DEVICE_ID panel["type"] = CONST.ALARM_TYPE panel["name"] = CONST.ALARM_NAME if self.model == 1: history = self.get_history_xt1() for histrow in history: if histrow not in self._history_cache: if (CONST.MODE_ALARM_TRIGGERED in histrow[CONST.HISTORY_ALARM_COLUMN]): panel["mode"] = CONST.STATE_ALARM_TRIGGERED self._history_cache.append(histrow) pickle.dump( self._history_cache, open(home + "/" + CONST.HISTORY_CACHE_NAME, "wb"), ) elif self.model == 2: history = self.get_history_xt2() for histrow in history: if histrow not in self._history_cache: if histrow[CONST.HISTORY_ALARM_COLUMN_XT2] == CONST.MODE_ALARM_TRIGGERED_XT2: panel["mode"] = CONST.STATE_ALARM_TRIGGERED self._history_cache.append(histrow) pickle.dump( self._history_cache, open(home + "/" + CONST.HISTORY_CACHE_NAME, "wb"), ) return panel def get_history_xt1(self): response = self._request_get(CONST.HISTORY_REQUEST_XT1) return self.clean_json(response.text)[CONST.HISTORY_HEADER] def get_history_xt2(self): response = self._request_get(CONST.HISTORY_REQUEST_XT2) return self.clean_json(response.text)[CONST.HISTORY_HEADER_XT2] def refresh(self): """Do a full refresh of all devices and automations.""" self.get_devices(refresh=True) def get_devices(self, refresh=False, generic_type=None): """Get all devices from Lupusec.""" _LOGGER.info("Updating all devices...") if refresh or self._devices is None: if self._devices is None: self._devices = {} responseObject = self.get_sensors() if responseObject and not isinstance(responseObject, (tuple, list)): responseObject = responseObject for deviceJson in responseObject: # Attempt to reuse an existing device device = self._devices.get(deviceJson["name"]) # No existing device, create a new one if device: device.update(deviceJson) else: device = newDevice(deviceJson, self) if not device: _LOGGER.info("Device is unknown") continue self._devices[device.device_id] = device # We will be treating the Lupusec panel itself as an armable device. panelJson = self.get_panel() _LOGGER.debug("Get the panel in get_devices: %s", panelJson) self._panel.update(panelJson) alarmDevice = self._devices.get("0") if alarmDevice: alarmDevice.update(panelJson) else: alarmDevice = ALARM.create_alarm(panelJson, self) self._devices["0"] = alarmDevice # Now we will handle the power switches if self.model == 1: switches = self.get_power_switches() _LOGGER.debug( "Get active the power switches in get_devices: %s", switches ) for deviceJson in switches: # Attempt to reuse an existing device device = self._devices.get(deviceJson["name"]) # No existing device, create a new one if device: device.update(deviceJson) else: device = newDevice(deviceJson, self) if not device: _LOGGER.info("Device is unknown") continue self._devices[device.device_id] = device elif self.model == 2: _LOGGER.debug("Power switches for XT2 not implemented") if generic_type: devices = [] for device in self._devices.values(): if device.type is not None and device.type in generic_type: devices.append(device) return devices return list(self._devices.values()) def get_device(self, device_id, refresh=False): """Get a single device.""" if self._devices is None: self.get_devices() refresh = False device = self._devices.get(device_id) if device and refresh: device.refresh() return device def get_alarm(self, area="1", refresh=False): """Shortcut method to get the alarm device.""" if self._devices is None: self.get_devices() refresh = False return self.get_device(CONST.ALARM_DEVICE_ID, refresh) def set_mode(self, mode): if self.model == 1: params = { "mode": mode, } elif self.model == 2: params = {"mode": mode, "area": 1} r = self._request_post("panelCondPost", params) responseJson = self.clean_json(r.text) return responseJson def newDevice(deviceJson, lupusec): """Create new device object for the given type.""" type_tag = deviceJson.get("type") if not type_tag: _LOGGER.info("Device has no type") if type_tag in CONST.TYPE_OPENING: return LupusecBinarySensor(deviceJson, lupusec) elif type_tag in CONST.TYPE_SENSOR: return LupusecBinarySensor(deviceJson, lupusec) elif type_tag in CONST.TYPE_SIREN: return LupusecBinarySensor(deviceJson, lupusec) elif type_tag in CONST.TYPE_KEYPAD: return LupusecBinarySensor(deviceJson, lupusec) elif type_tag in CONST.TYPE_SWITCH: return LupusecSwitch(deviceJson, lupusec) else: _LOGGER.info("Device is not known") return None majuss-lupupy-ef35197/lupupy/__main__.py000066400000000000000000000120061455401006600203450ustar00rootroot00000000000000import lupupy import argparse import logging import json _LOGGER = logging.getLogger("lupuseccl") def setup_logging(log_level=logging.INFO): """Set up the logging.""" logging.basicConfig(level=log_level) fmt = "%(asctime)s %(levelname)s (%(threadName)s) " "[%(name)s] %(message)s" colorfmt = "%(log_color)s{}%(reset)s".format(fmt) datefmt = "%Y-%m-%d %H:%M:%S" # Suppress overly verbose logs from libraries that aren't helpful logging.getLogger("requests").setLevel(logging.WARNING) try: from colorlog import ColoredFormatter logging.getLogger().handlers[0].setFormatter( ColoredFormatter( colorfmt, datefmt=datefmt, reset=True, log_colors={ "DEBUG": "cyan", "INFO": "green", "WARNING": "yellow", "ERROR": "red", "CRITICAL": "red", }, ) ) except ImportError: pass logger = logging.getLogger("") logger.setLevel(log_level) def get_arguments(): """Get parsed arguments.""" parser = argparse.ArgumentParser("Lupupy: Command Line Utility") parser.add_argument( "-u", "--username", help="Username", required=False ) parser.add_argument( "-p", "--password", help="Password", required=False ) parser.add_argument( "--arm", help="Arm alarm to mode", required=False, default=False, action="store_true" ) parser.add_argument( "-i", "--ip_address", help="IP of the Lupus panel", required=False ) parser.add_argument( "--disarm", help="Disarm the alarm", required=False, default=False, action="store_true" ) parser.add_argument( "--home", help="Set to home mode", required=False, default=False, action="store_true" ) parser.add_argument( "--devices", help="Output all devices", required=False, default=False, action="store_true" ) parser.add_argument( "--history", help="Get the history", required=False, default=False, action="store_true" ) parser.add_argument( "--status", help="Get the status of the panel", required=False, default=False, action="store_true" ) parser.add_argument( "--debug", help="Enable debug logging", required=False, default=False, action="store_true" ) parser.add_argument( "--quiet", help="Output only warnings and errors", required=False, default=False, action="store_true" ) parser.add_argument( "--version", "-v", help="Shows lupupy version", required=False, default=False, action="store_true" ) return parser.parse_args() def call(): """Execute command line helper.""" args = get_arguments() if args.debug: log_level = logging.DEBUG elif args.quiet: log_level = logging.WARN else: log_level = logging.INFO setup_logging(log_level) lupusec = None if args.version: _LOGGER.info(lupupy.CONST.VERSION) return if not args.username or not args.password or not args.ip_address: raise Exception("Please supply a username, password and ip.") def _devicePrint(dev, append=""): _LOGGER.info("%s%s", dev.desc, append) try: if args.username and args.password and args.ip_address: lupusec = lupupy.Lupusec( ip_address=args.ip_address, username=args.username, password=args.password, ) if args.arm: if lupusec.get_alarm().set_away(): _LOGGER.info("Alarm mode changed to armed") else: _LOGGER.warning("Failed to change alarm mode to armed") if args.disarm: if lupusec.get_alarm().set_standby(): _LOGGER.info("Alarm mode changed to disarmed") else: _LOGGER.warning("Failed to change alarm mode to disarmed") if args.home: if lupusec.get_alarm().set_home(): _LOGGER.info("Alarm mode changed to home") else: _LOGGER.warning("Failed to change alarm mode to home") if args.history: _LOGGER.info(json.dumps(lupusec.get_history(), indent=4, sort_keys=True)) if args.status: _LOGGER.info("Mode of panel: %s", lupusec.get_alarm().mode) if args.devices: for device in lupusec.get_devices(): _devicePrint(device) except Exception as exc: _LOGGER.error(exc) finally: _LOGGER.info("--Finished running--") def main(): """Execute from command line.""" call() if __name__ == "__main__": main() majuss-lupupy-ef35197/lupupy/constants.py000066400000000000000000000060421455401006600206440ustar00rootroot00000000000000# Used in setup.py # -*- coding: utf-8 -*- VERSION = "0.3.2" PROJECT_PACKAGE_NAME = "lupupy" PROJECT_LICENSE = "MIT" PROJECT_URL = "http://www.github.com/majuss/lupupy" PROJECT_DESCRIPTION = "A python cli for Lupusec alarm panels." PROJECT_LONG_DESCRIPTION = ( "lupupy is a python3 interface for" " the Lupus Electronics alarm panel." " Its intented to get used in various" " smart home services to get a full" " integration of all your devices." ) PROJECT_AUTHOR = "Majuss" MODE_AWAY = "Arm" MODE_HOME = "Home" MODE_DISARMED = "Disarm" MODE_ALARM_TRIGGERED = "Einbruch" MODE_ALARM_TRIGGERED_XT2 = "3" ALL_MODES = [MODE_DISARMED, MODE_HOME, MODE_AWAY] MODE_TRANSLATION_XT1 = {"Disarm": 2, "Home": 1, "Arm": 0} MODE_TRANSLATION_XT2 = {"Disarm": 0, "Arm": 1, "Home": 2} XT2_MODES_TO_TEXT = { "{AREA_MODE_0}": "Disarm", "{AREA_MODE_1}": "Arm", "{AREA_MODE_2}": "Home", "{AREA_MODE_3}": "Home", "{AREA_MODE_4}": "Home", } STATE_ALARM_DISARMED = "disarmed" STATE_ALARM_ARMED_HOME = "armed_home" STATE_ALARM_ARMED_AWAY = "armed_away" STATE_ALARM_TRIGGERED = "alarm_triggered" MODE_TRANSLATION_GENERIC = { "Disarm": "disarmed", "Home": "armed_home", "Arm": "armed_away", } DEFAULT_MODE = MODE_AWAY HISTORY_REQUEST_XT1 = "historyGet" HISTORY_REQUEST_XT2 = "recordListGet" HISTORY_ALARM_COLUMN = "a" HISTORY_ALARM_COLUMN_XT2 = "type" HISTORY_HEADER = "hisrows" HISTORY_HEADER_XT2 = "logrows" HISTORY_CACHE_NAME = ".lupusec_history_cache" STATUS_ON_INT = 0 STATUS_ON = "on" STATUS_OFF_INT = 1 STATUS_OFF = "off" STATUS_OFFLINE = "offline" STATUS_CLOSED = "Geschlossen" STATUS_CLOSED_INT = 0 STATUS_OPEN = "Offen" STATUS_OPEN_INT = 1 STATUS_OPEN_WEB = "{WEB_MSG_DC_OPEN}" STATUS_CLOSED_WEB = "{WEB_MSG_DC_CLOSE}" ALARM_NAME = "Lupusec Alarm" ALARM_DEVICE_ID = "0" ALARM_TYPE = "Alarm" # GENERIC Lupusec DEVICE TYPES TYPE_WINDOW = "Fensterkontakt" TYPE_DOOR = "Türkontakt" TYPE_SMOKE = "Rauchmelder" TYPE_WATER = "Wassermelder" TYPE_POWER_SWITCH = "Steckdose" TYPE_CONTACT_XT = 4 TYPE_WATER_XT = 5 TYPE_SMOKE_XT = 11 TYPE_POWER_SWITCH_1_XT = 24 TYPE_POWER_SWITCH_2_XT = 25 TYPE_KEYPAD_V2 = 37 TYPE_INDOOR_SIREN_XT=45 TYPE_OUTDOOR_SIREN_XT=46 TYPE_SWITCH = [TYPE_POWER_SWITCH, TYPE_POWER_SWITCH_1_XT, TYPE_POWER_SWITCH_2_XT] TYPE_OPENING = [TYPE_DOOR, TYPE_WINDOW, TYPE_CONTACT_XT] TYPE_SIREN = [TYPE_INDOOR_SIREN_XT, TYPE_OUTDOOR_SIREN_XT] TYPE_KEYPAD = [TYPE_KEYPAD_V2] BINARY_SENSOR_TYPES = TYPE_OPENING TYPE_SENSOR = [TYPE_SMOKE, TYPE_WATER, TYPE_WATER_XT, TYPE_SMOKE_XT] # Home Assistant DEVICE TYPES HA_DEVICE_CLASS_WINDOW = "window" HA_DEVICE_CLASS_DOOR = "door" HA_DEVICE_CLASS_MOISTURE = "moisture" HA_DEVICE_CLASS_SMOKE = "smoke" TYPE_TRANSLATION = { TYPE_WINDOW: HA_DEVICE_CLASS_WINDOW, TYPE_DOOR: HA_DEVICE_CLASS_DOOR, TYPE_CONTACT_XT: HA_DEVICE_CLASS_DOOR, TYPE_WATER_XT: HA_DEVICE_CLASS_MOISTURE, TYPE_SMOKE_XT: HA_DEVICE_CLASS_SMOKE, TYPE_KEYPAD_V2: "Keypad V2", TYPE_INDOOR_SIREN_XT: "Innensirene", TYPE_OUTDOOR_SIREN_XT: "Außensirene", } DEVICES_API_XT1 = "sensorListGet" DEVICES_API_XT2 = "deviceListGet" majuss-lupupy-ef35197/lupupy/devices/000077500000000000000000000000001455401006600176765ustar00rootroot00000000000000majuss-lupupy-ef35197/lupupy/devices/__init__.py000066400000000000000000000073041455401006600220130ustar00rootroot00000000000000"""Init file for devices directory.""" import json import logging import lupupy.constants as CONST class LupusecDevice(object): """Class to represent each Lupusec device.""" def __init__(self, json_obj, lupusec): """Set up Lupusec device.""" self._json_state = json_obj self._device_id = json_obj.get('device_id') self._name = json_obj.get('name') self._type = json_obj.get('type') if self._type in CONST.TYPE_TRANSLATION: self._generic_type = CONST.TYPE_TRANSLATION[self._type] else: self._generic_type = 'generic_type_unknown' self._status = json_obj.get('status') self._lupusec = lupusec if not self._name: self._name = self._generic_type + ' ' + self.device_id def get_value(self, name): """Get a value from the json object. """ return self._json_state.get(name) def refresh(self): """Refresh a device""" # new_device = {} if self.type in CONST.BINARY_SENSOR_TYPES: response = self._lupusec.get_sensors() for device in response: if device['device_id'] == self._device_id: self.update(device) return device elif self.type == CONST.ALARM_TYPE: response = self._lupusec.get_panel() self.update(response) return response elif self.type == CONST.TYPE_POWER_SWITCH: response = self._lupusec.get_power_switches() for pss in response: if pss['device_id'] == self._device_id: self.update(pss) return pss def set_status(self, status): """Set status of power switch.""" # self._apipost def update(self, json_state): """Update the json data from a dictionary. Only updates if it already exists in the device. """ if self._type in CONST.BINARY_SENSOR_TYPES: self._json_state['status'] = json_state['status'] else: self._json_state.update( {k: json_state[k] for k in json_state if self._json_state.get(k)}) @property def status(self): """Shortcut to get the generic status of a device.""" return self.get_value('status') @property def level(self): """Shortcut to get the generic level of a device.""" return self.get_value('level') @property def battery_low(self): """Is battery level low.""" return int(self.get_value('faults').get('low_battery', '0')) == 1 @property def no_response(self): """Is the device responding.""" return int(self.get_value('faults').get('no_response', '0')) == 1 @property def out_of_order(self): """Is the device out of order.""" return int(self.get_value('faults').get('out_of_order', '0')) == 1 @property def tampered(self): """Has the device been tampered with.""" # 'tempered' - Typo in API? return int(self.get_value('faults').get('tempered', '0')) == 1 @property def name(self): """Get the name of this device.""" return self._name @property def type(self): """Get the type of this device.""" return self._type @property def generic_type(self): """Get the generic type of this device.""" return self._generic_type @property def device_id(self): """Get the device id.""" return self._device_id @property def desc(self): """Get a short description of the device.""" return '{0} (ID: {1}) - {2} - {3}'.format( self.name, self.device_id, self.type, self.status) majuss-lupupy-ef35197/lupupy/devices/alarm.py000066400000000000000000000061271455401006600213520ustar00rootroot00000000000000"""Lupusec alarm device.""" import json import logging from lupupy.devices.switch import LupusecDevice, LupusecSwitch import lupupy.constants as CONST _LOGGER = logging.getLogger(__name__) def create_alarm(panel_json, lupusec, area="1"): """Create a new alarm device from a panel response.""" return LupusecAlarm(panel_json, lupusec, area) class LupusecAlarm(LupusecSwitch): """Class to represent the Lupusec alarm as a device.""" def __init__(self, json_obj, lupusec, area="1"): """Set up Lupusec alarm device.""" LupusecSwitch.__init__(self, json_obj, lupusec) self._area = area def set_mode(self, mode): """Set Lupusec alarm mode.""" _LOGGER.debug("State change called from alarm device") if not mode: _LOGGER.info("No mode supplied") elif mode not in CONST.ALL_MODES: _LOGGER.warning("Invalid mode") response_object = self._lupusec.set_mode(self._lupusec.mode_translation[mode]) if response_object["result"] != 1 and response_object["result"] is not "1": _LOGGER.warning("Mode setting unsuccessful") self._json_state["mode"] = mode _LOGGER.info("Mode set to: %s", mode) return True def set_home(self): """Arm Lupusec to home mode.""" return self.set_mode(CONST.MODE_HOME) def set_away(self): """Arm Lupusec to armed mode.""" return self.set_mode(CONST.MODE_AWAY) def set_standby(self): """Arm Lupusec to stay mode.""" return self.set_mode(CONST.MODE_DISARMED) def refresh(self): """Refresh the alarm device.""" response_object = LupusecDevice.refresh(self) return response_object def switch_on(self): """Arm Abode to default mode.""" return self.set_mode(CONST.DEFAULT_MODE) def switch_off(self): """Arm Abode to home mode.""" return self.set_standby() @property def is_on(self): """Is alarm armed.""" return self.mode in (CONST.MODE_HOME, CONST.MODE_AWAY) @property def is_standby(self): """Is alarm in standby mode.""" return self.mode == CONST.MODE_DISARMED @property def is_home(self): """Is alarm in home mode.""" return self.mode == CONST.MODE_HOME @property def is_away(self): """Is alarm in away mode.""" return self.mode == CONST.MODE_AWAY @property def is_alarm_triggered(self): """Is alarm in alarm triggered mode.""" return self.mode == CONST.STATE_ALARM_TRIGGERED @property def mode(self): """Get alarm mode.""" mode = self.get_value("mode") return mode @property def status(self): """To match existing property.""" return self.mode @property def battery(self): """Return true if base station on battery backup.""" return int(self._json_state.get("battery", "0")) == 1 @property def is_cellular(self): """Return true if base station on cellular backup.""" return int(self._json_state.get("is_cellular", "0")) == 1 majuss-lupupy-ef35197/lupupy/devices/binary_sensor.py000066400000000000000000000011731455401006600231270ustar00rootroot00000000000000"""Lupusec binary sensor device.""" from lupupy.devices import LupusecDevice import lupupy.constants as CONST class LupusecBinarySensor(LupusecDevice): """Class to represent an on / off, online/offline sensor.""" @property def is_on(self): """ Get sensor state. Assume offline or open (worst case). """ return self.status not in ( CONST.STATUS_OFF, CONST.STATUS_OFFLINE, CONST.STATUS_CLOSED, CONST.STATUS_CLOSED_INT, CONST.STATUS_OPEN_WEB, CONST.STATUS_CLOSED_WEB, CONST.STATUS_OPEN, ) majuss-lupupy-ef35197/lupupy/devices/switch.py000066400000000000000000000016161455401006600215550ustar00rootroot00000000000000"""Lupusec switch device.""" from lupupy.devices import LupusecDevice import lupupy.constants as CONST class LupusecSwitch(LupusecDevice): """Class to add switch functionality.""" def switch_on(self): """Turn the switch on.""" success = self.set_status(CONST.STATUS_ON_INT) if success: self._json_state['status'] = CONST.STATUS_ON return success def switch_off(self): """Turn the switch off.""" success = self.set_status(CONST.STATUS_OFF_INT) if success: self._json_state['status'] = CONST.STATUS_OFF return success @property def is_on(self): """ Get switch state. Assume switch is on. """ return self.status not in (CONST.STATUS_OFF, CONST.STATUS_OFFLINE) @property def is_dimmable(self): """Device dimmable.""" return False majuss-lupupy-ef35197/lupupy/exceptions.py000066400000000000000000000010631455401006600210070ustar00rootroot00000000000000"""The exceptions used by Lupupy.""" class LupusecException(Exception): """Class to throw general lupusec exception.""" def __init__(self, error, details=None): """Initialize LupusecException.""" # Call the base class constructor with the parameters it needs super(LupusecException, self).__init__(error[1]) self.errcode = error[0] self.message = error[1] self.details = details # class LupusecAuthenticationException(LupusecException): # """Class to throw authentication exception.""" # pass majuss-lupupy-ef35197/setup.py000066400000000000000000000017311455401006600164320ustar00rootroot00000000000000#!/usr/bin/env python3 """LupuPy setup script.""" from setuptools import setup, find_packages from lupupy.constants import (VERSION, PROJECT_PACKAGE_NAME, PROJECT_LICENSE, PROJECT_URL, PROJECT_DESCRIPTION, PROJECT_AUTHOR, PROJECT_LONG_DESCRIPTION) PACKAGES = find_packages() setup( name=PROJECT_PACKAGE_NAME, version=VERSION, description=PROJECT_DESCRIPTION, long_description=PROJECT_LONG_DESCRIPTION, author=PROJECT_AUTHOR, license=PROJECT_LICENSE, url=PROJECT_URL, platforms='any', py_modules=['lupupy'], packages=PACKAGES, keywords='smart home automation', include_package_data=True, python_requires='>=3.5', install_requires=[ 'requests>=2.12.4', 'pyyaml', 'colorlog' ], test_suite='tests', entry_points={ 'console_scripts': [ 'lupupy = lupupy.__main__:main' ] } )