Beispiel #1
0
def init(mode=None, **kwargs):
    """Wrapper to create the API object you need to acces the CCU API.

    By default it detects whether or not this code is being executed on the CCU
    or on another system. And initializes either a LocalAPI() object when run
    directly on a CCU or, in all other cases, a RemoteAPI() object. This object
    is then being returned.

    You can provide the mode argument to disable auto detection and either set
    it to "local" to enforce a LocalAPI() object to be created or "remote" to
    enforce a RemoteAPI() object.

    In case a RemoteAPI() object is being created, you need to provide at least
    the additional keyword arguments address="http://[HOST]" which needs to
    contain the base URL to your CCU together with credentials=("[USER]", "PASSWORD")
    which must be valid credentials to authenticate with the CCU.
    """
    if mode is None:
        mode = utils.is_ccu() and "local" or "remote"

    if mode == "local":
        if not utils.is_ccu():
            raise PMException("local mode can only be used on the CCU.")

        return LocalAPI()
    elif mode == "remote":
        try:
            return RemoteAPI(**kwargs)
        except TypeError as e:
            raise PMException(
                "You need to provide at least the address and credentials "
                "to access your CCU (%s)." % e)
    else:
        raise PMException(
            "Invalid mode given. Valid ones are \"local\" and \"remote\".")
Beispiel #2
0
    def _parse_api_response(self, method_name_int, kwargs, body):
        # FIXME: The ccu is performing wrong encoding at least for output of
        # executed rega scripts. But maybe this is a generic problem. Let's see
        # and only fix the known issues for the moment.
        if method_name_int in [
                "rega_run_script", "interface_get_paramset_description",
                "room_get_all"
        ]:
            body = AbstractAPI._replace_wrong_encoded_json(body)

        try:
            msg = json.loads(body)
        except Exception as e:
            raise PMException("Failed to parse response to %s (%s):\n%s\n" %
                              (method_name_int, e, body))

        if msg["error"] is not None:
            if msg["error"]["code"] == 501 and not self._call(
                    'rega_is_present'):
                raise PMConnectionError(
                    "The logic layer (ReGa) is not available (yet). When "
                    "the CCU has just been started, please wait some time "
                    "and retry.")
            else:
                raise PMException(
                    "[%s] %s: %s (Code: %s, Request: %r)" %
                    (method_name_int, msg["error"]["name"],
                     msg["error"]["message"], msg["error"]["code"], kwargs))

        return msg["result"]
Beispiel #3
0
    def _login(self):
        if self._session_id is not None:
            raise PMException("Already logged in.")

        response = self._call("session_login", username=self._credentials[0],
                                              password=self._credentials[1])
        if response is None:
            raise PMException("Login failed: Got no session id.")
        self._session_id = response
Beispiel #4
0
    def _set_credentials(self, credentials):
        if not isinstance(credentials, tuple):
            raise PMException("Please specify the user credentials to log in to the CCU "
                              "like this: \"(username, password)\".")
        elif len(credentials) != 2:
            raise PMException("The credentials must be given as tuple of two elements.")
        elif not utils.is_string(credentials[0]):
            raise PMException("The username is of unhandled type.")
        elif not utils.is_string(credentials[1]):
            raise PMException("The password is of unhandled type.")

        self._credentials = credentials
Beispiel #5
0
    def _validate(self, value):
        if type(value) not in (float, int):
            raise PMException(
                "Invalid type. You need to provide a float value.")

        if value > self.max:
            raise PMException("Invalid value (Exceeds maximum of %0.2f)" %
                              self.max)

        if value < self.min:
            raise PMException("Invalid value (Exceeds minimum of %0.2f)" %
                              self.min)

        return True
Beispiel #6
0
    def _validate(self, value):
        if not isinstance(value, int):
            raise PMException(
                "Invalid type. You need to provide an integer value.")

        if value > self.max:
            raise PMException("Invalid value (Exceeds maximum of %d)" %
                              self.max)

        if value < self.min:
            raise PMException("Invalid value (Exceeds minimum of %d)" %
                              self.min)

        return True
Beispiel #7
0
    def _check_response(cls, handle):
        if handle.getcode() != 200:
            raise PMException("Got invalid HTTP status code: %d" %
                              (handle.getcode()))

        response_body = handle.read().decode("utf-8")
        cls.cls_logger().debug("Response-Body: \"%s\"" % response_body)

        data = json.loads(response_body)
        if data["status"] != 1:
            raise PMException("Got invalid response (%d): %s" %
                              (data["status"], response_body))

        return True
Beispiel #8
0
    def _set_http_auth(self, credentials):
        if credentials is not None:
            if not isinstance(credentials, tuple):
                raise PMException("Please specify the http auth credentials "
                                  "like this: \"(username, password)\".")
            elif len(credentials) != 2:
                raise PMException(
                    "The http auth credentials must be given as tuple of two elements."
                )
            elif not utils.is_string(credentials[0]):
                raise PMException("The username is of unhandled type.")
            elif not utils.is_string(credentials[1]):
                raise PMException("The password is of unhandled type.")

        self._http_auth = credentials
Beispiel #9
0
    def _init_tclsh(self):
        try:
            self._tclsh = subprocess.Popen(
                ["/bin/tclsh"],
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,  #stderr=subprocess.PIPE,
                cwd="/www/api",
                shell=False)
        except OSError as e:
            if e.errno == 2:
                raise PMException(
                    "Could not find /bin/tclsh. Maybe running local API on "
                    "non CCU device?")
            else:
                raise

        self._tclsh.stdin.write(
            "load tclrpc.so\n"
            "load tclrega.so\n"
            "source /www/api/eq3/common.tcl\n"
            "source /www/api/eq3/ipc.tcl\n"
            "source /www/api/eq3/json.tcl\n"
            "source /www/api/eq3/jsonrpc.tcl\n"
            "source /www/api/eq3/hmscript.tcl\n"
            "source /www/api/eq3/event.tcl\n"
            "array set INTERFACE_LIST [ipc_getInterfaces]\n"
            "array set METHOD_LIST  [file_load %s]\n" % self._methods_file)
Beispiel #10
0
    def _get_args(self, method, args):
        def quote_string(s):
            return "\"%s\"" % s.replace("\"", "\\\"")

        args_parsed = "[list "
        for arg_name in method["ARGUMENTS"]:
            try:
                if arg_name == "_session_id_" and arg_name not in args:
                    val = quote_string(
                        ""
                    )  # Fake default session id. Not needed for local API
                else:
                    val = args[arg_name]
                    if val is None:
                        val = quote_string("")
                    elif type(val) in [int, float]:
                        val = "%s" % val
                    elif type(val) == bool:
                        val = 1 if val else 0
                    else:
                        val = quote_string("%s" % val)

                args_parsed += "%s %s " % (quote_string(arg_name), val)
            except KeyError:
                raise PMException("Missing argument \"%s\". Needs: %s" %
                                  (arg_name, ", ".join(method["ARGUMENTS"])))
        return args_parsed.rstrip(" ") + "]"
Beispiel #11
0
    def _find_listen_address_to_reach_ccu(self):
        """Determines the host address to tell the CCU to send it's messages to.

        When the listen_address is either not set or set to an empty string this makes
        the XML-RPC server listen on all local interfaces / addresses. But the drawback
        is that we don't know which URL we should tell the CCU to send it's events to.

        This method determines the address to tell the CCU.

        When the script is executed on the CCU it returns "127.0.0.1". Otherwise it creats
        a socket and opens a connection to the CCU address (which is used by self._ccu.api)
        and port 80. Then it knows which local IP address has been used to communicate
        with the CCU. This address is then returned.
        """
        if isinstance(self._ccu.api, pmatic.api.LocalAPI):
            return "127.0.0.1"

        ccu_address = urlparse(self._ccu.api.address).hostname

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(1)  # wait for 1 second for the connect.
        try:
            s.connect((ccu_address, 80))
            address = s.getsockname()[0]
            s.close()
            return address
        except socket.error as e:
            raise PMException(
                "Unable to detect the address to listen on for XML-RPC "
                "messages from the CCU (%s). You might fix this by explicitly "
                "providing the parameter listen_address=([ADDRESS], [PORT]) "
                "to pmatic.events.init()." % e)
Beispiel #12
0
 def _init_interface_id(self, interface_id):
     """Initializes the interface ID of this object."""
     if interface_id is not None:
         if utils.is_string(interface_id):
             self._interface_id = interface_id
         else:
             raise PMException("interface_id has to be of type string")
     else:
         self._interface_id = "pmatic-%d" % EventListener._next_id()
Beispiel #13
0
    def last_changed(self):
        """Provides the unix time when the value has been changed the last time.

        This is measured since the creation of the object (startup of pmatic).

        It raises a :class:`PMException` when the parameter can not be read."""
        if not self.readable:
            raise PMException("The value can not be read.")
        return self._value_changed
Beispiel #14
0
    def _set_address(self, address):
        if not utils.is_string(address):
            raise PMException("Please specify the address of the CCU.")

        # Add optional protocol prefix
        if not address.startswith("https://") and not address.startswith("http://"):
            address = "http://%s" % address

        self._address = address
Beispiel #15
0
    def _get_method(self, method_name_int):
        """Returns the method specification (dict) of the given API methods.

        The method name needs to be specified with it's internal name (like
        the methods of the API object are named). When the request API method
        does not exist a PMException is raised.
        """
        try:
            return self._methods[method_name_int]
        except KeyError:
            raise PMException("Method \"%s\" is not a valid method." % method_name_int)
Beispiel #16
0
    def value(self):
        """The current value of this paramter.

        :getter: Provides the current value of this parameter as reported by the CCU.
                 It raises a :class:`PMException` when the parameter can not be read.
        :setter: Specify the new value. Returns a :class:`PMException` when the parameter
                 can not be written or a validation error occurs. It sets the value on
                 the CCU. In case this fails a :class:`PMActionFailed` exception is raised.
        """
        if not self.readable:
            raise PMException("The value can not be read.")
        return self._value
Beispiel #17
0
    def _init_listen_address(self, listen_address):
        """Parses the listen_address provided by the user."""
        if listen_address is None:
            # listen on all interfaces. Use port 9123 by default.
            self._listen_address = ('', 9123)

        elif isinstance(listen_address, tuple) and len(listen_address) == 2:
            self._listen_address = listen_address

        else:
            raise PMException("listen_address needs to be a tuple of two "
                              "elements (address, port): %r" % (listen_address, ))
Beispiel #18
0
    def _connect(cls):
        if SimpleTR64Lan == None:
            raise PMException(
                "Could not import the required \"simpletr64.actions.lan.Lan\"."
            )

        if cls.connection == None:
            cls.connection = SimpleTR64Lan(hostname=cls._address,
                                           port=cls._port,
                                           protocol=cls._protocol)
            cls.connection.setupTR64Device("fritz.box")
            cls.connection.username = cls._user
            cls.connection.password = cls._password
Beispiel #19
0
    def _do_call(self, method_name_int, **kwargs):
        method = self._get_method(method_name_int)
        args = self._get_arguments(method, kwargs)

        self.logger.debug("CALL: %s ARGS: %r", method["NAME"], args)
        #import traceback
        #stack = "" #("".join(traceback.format_stack()[:-1])).encode("utf-8")
        #print(b"".join(traceback.format_stack()[:-1]))
        #self.logger.debug("  Callstack: %s\n" % stack)

        json_data = json.dumps({
            "method": method["NAME"],
            "params": args,
        })
        url = "%s/api/homematic.cgi" % self._address

        try:
            self.logger.debug("  URL: %s DATA: %s", url, json_data)
            request = Request(url, data=json_data.encode("utf-8"))

            if self._http_auth:
                base64string = base64.encodestring(
                    '%s:%s' % self._http_auth).replace('\n', '')
                request.add_header("Authorization", "Basic %s" % base64string)

            handle = urlopen(request, timeout=self._connect_timeout)
        except Exception as e:
            if isinstance(e, URLError):
                msg = e.reason
            elif isinstance(e, BadStatusLine):
                msg = "Request terminated. Is the device rebooting?"
            else:
                msg = e
            raise PMConnectionError("Unable to open \"%s\" [%s]: %s" %
                                    (url, type(e).__name__, msg))

        response_txt = ""
        for line in handle.readlines():
            response_txt += line.decode("utf-8")

        http_status = handle.getcode()

        self.logger.debug("  HTTP-STATUS: %d", http_status)
        if http_status != 200:
            raise PMException("Error %d opening \"%s\" occured: %s" %
                              (http_status, url, response_txt))

        self.logger.debug("  RESPONSE: %s", response_txt)
        return self._parse_api_response(method_name_int, kwargs, response_txt)
Beispiel #20
0
    def value(self, value):
        if not self.writable:
            raise PMException("The value can not be changed.")
        self._validate(value)

        result = self.channel._ccu.api.interface_set_value(
            interface="BidCos-RF",
            address=self.channel.address,
            valueKey=self.id,
            type=self.datatype,
            value=self._to_api_value(value))
        if not result:
            raise PMActionFailed("Failed to set the value in CCU.")

        self._set_value(value)
Beispiel #21
0
    def _load(self, path, default=None):
        try:
            try:
                fh = open(path)
                data = json.load(fh)
            except IOError as e:
                # a non existing file is allowed.
                if e.errno == 2:
                    data = default
                else:
                    raise

            return data
        except Exception as e:
            raise PMException("Failed to load %s: %s" % (self._name, e))
Beispiel #22
0
    def _dispatch(self, method, params):
        """Central entry point for all calls.

        It does not let exceptions through to the caller. The exceptions
        are all catched and logged.
        """
        try:
            func = getattr(self, method)
        except AttributeError:
            raise PMException("Requested method %r is not implemented." % method)

        try:
            return func(*params)
        except Exception:
            self.logger.error("Exception in XML-RPC call %s%r:",
                                method, tuple(params), exc_info=True)
            return False
Beispiel #23
0
    def _callback(self, cb_name, *args, **kwargs):
        """Execute all registered callbacks for this event.

        While the function is being processed, it reminds that the current callback is being
        executed. When another part of pmatic is trying to execut the exact same callback
        another time (may result in endless recursion), the second call is not executing the
        callbacks again. """
        if cb_name in self._in_callbacks:
            return  # skip nested execution
        else:
            self._in_callbacks.append(cb_name)

        try:
            for callback in self._get_callbacks(cb_name):
                try:
                    callback(self, *args, **kwargs)
                except Exception as e:
                    raise PMException("Exception in callback (%s - %s): %s" %
                                      (cb_name, callback, e))
        finally:
            self._in_callbacks.remove(cb_name)
Beispiel #24
0
 def _get_callbacks(self, cb_name):
     try:
         return self._callbacks[cb_name]
     except KeyError:
         raise PMException("Invalid callback %s specified (Available: %s)" %
                           (cb_name, ", ".join(self._callbacks.keys())))
 def raise_invalid_value(*args, **kwargs):
     raise PMException("bla 601 bla")
 def x(param): # pylint:disable=unused-argument
     raise PMException("DING")
Beispiel #27
0
 def update(self, *args, **kwargs):  # pylint:disable=unused-argument
     raise PMException("Can not be changed.")
Beispiel #28
0
 def __setitem__(self, key, val):
     raise PMException("Can not be changed.")
Beispiel #29
0
 def lowlevel_call(*args, **kwargs):
     if args:
         raise PMException(
             "You need to specify your arguments as named arguments. "
             "For example api.sys_var_get_value_by_name(name=\"...\").")
     return self._call(method_name_int, **kwargs)
Beispiel #30
0
 def _set_connect_timeout(self, timeout):
     if type(timeout) not in [int, float]:
         raise PMException(
             "Invalid timeout value. Must be of type int or float.")
     self._connect_timeout = timeout