Beispiel #1
0
class TestHtParams:
    @pytest.mark.parametrize("name, acl", [(name, param.acl) for name, param in HtParams.items()])
    def test_acl(self, name, acl):
        assert acl is not None, "acl must not be None"
        m = re.match("^(r-|-w|rw)$", acl)
        assert m is not None, "invalid acl definition for parameter {!r} [{!r}]".format(name, acl)
        #assert 0

    @pytest.mark.parametrize("name, min, max", [(name, param.min, param.max) for name, param in HtParams.items()])
    def test_limits(self, name, min, max):
        assert min is not None, "minimal value for parameter {!r} must not be None".format(name)
        assert max is not None, "maximal value for parameter {!r} must not be None".format(name)
        assert min <= max
        assert max >= min
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.parametrize("name, param", [(name, param) for name, param in HtParams.items()])
    def test_validate_param(self, hthp, name, param):
        hthp.send_request(param.cmd())
        resp = hthp.read_response()
        m = re.match("^{},.*NAME=([^,]+).*VAL=([^,]+).*MAX=([^,]+).*MIN=([^,]+).*$".format(param.cmd()), resp)
        assert m is not None, "invalid response for query of parameter {!r} [{!r}]".format(name, resp)
        dp_name = m.group(1).strip()
        assert dp_name == name,\
            "data point name doesn't match with the parameter name {!r} [{!r}]".format(name, dp_name)
        dp_value = param.from_str(m.group(2))
        assert dp_value is not None, "data point value must not be None [{}]".format(dp_value)
        dp_max = param.from_str(m.group(3))
        assert dp_max == param.max,\
            "data point max value doesn't match with the parameter's one {!s} [{!s}]".format(param.max, dp_max)
        dp_min = param.from_str(m.group(4))
        assert dp_min == param.min,\
            "data point min value doesn't match with the parameter's one {!s} [{!s}]".format(param.min, dp_min)
class TestHtParams:
    @pytest.mark.parametrize("name, acl",
                             [(name, param.acl)
                              for name, param in HtParams.items()])
    def test_acl(self, name: str, acl: str):
        assert acl is not None, "'acl' must not be None"
        m = re.match(r"^(r-|-w|rw)$", acl)
        assert m is not None, "invalid acl definition for parameter {!r} [{!r}]".format(
            name, acl)
        #assert 0

    @pytest.mark.parametrize("name, min_val, max_val",
                             [(name, param.min_val, param.max_val)
                              for name, param in HtParams.items()])
    def test_limits(self, name: str, min_val: HtParamValueType,
                    max_val: HtParamValueType):
        assert min_val is not None, "minimal value for parameter {!r} must not be None".format(
            name)
        assert max_val is not None, "maximal value for parameter {!r} must not be None".format(
            name)
        assert min_val <= max_val
        assert max_val >= min_val
        #assert 0

    @pytest.mark.parametrize("name, param", HtParams.items())
    def test_get(self, name: str, param: HtParam):
        assert HtParams.get(name) == param
        #assert 0

    def test_dump(self):
        assert HtParams.dump() is None
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.parametrize("name, param", HtParams.items())
    def test_validate_param(self, hthp: HtHeatpump, name: str, param: HtParam):
        hthp.send_request(param.cmd())
        resp = hthp.read_response()
        m = re.match(
            r"^{},.*NAME=([^,]+).*VAL=([^,]+).*MAX=([^,]+).*MIN=([^,]+).*$".
            format(param.cmd()), resp)
        assert m is not None, "invalid response for query of parameter {!r} [{!r}]".format(
            name, resp)
        dp_name = m.group(1).strip()
        assert dp_name == name,\
            "data point name doesn't match with the parameter name {!r} [{!r}]".format(name, dp_name)
        dp_value = param.from_str(m.group(2))
        assert dp_value is not None, "data point value must not be None [{}]".format(
            dp_value)
        dp_max = param.from_str(m.group(3))
        assert dp_max == param.max_val,\
            "data point max value doesn't match with the parameter's one {!s} [{!s}]".format(param.max_val, dp_max)
        dp_min = param.from_str(m.group(4))
        if name == "Verdichter laeuft seit" and dp_min == 10:
            dp_min = 0  # seems to be incorrect for the data point "Verdichter laeuft seit" [10 == 0]
        assert dp_min == param.min_val,\
            "data point min value doesn't match with the parameter's one {!s} [{!s}]".format(param.min_val, dp_min)
Beispiel #3
0
    def validate(obj: Dict) -> Dict:
        for name in obj.keys():
            if name not in HtParams.keys():
                raise vol.Invalid(
                    f"{name!r} is not a valid heat pump parameter")

        return obj
Beispiel #4
0
 def test_fast_query(self, hthp: HtHeatpump):
     values = hthp.fast_query()
     assert isinstance(values, dict), "'values' must be of type dict"
     assert len(values) == len(HtParams.of_type("MP"))
     for n, v in values.items():
         assert n in HtParams
         assert v is not None
         assert HtParams[n].in_limits(v)
class TestHtParam:
    @pytest.mark.parametrize(
        "s, data_type, exp_value, strict",
        [
            ("0", HtDataTypes.BOOL, False, False),
            ("1", HtDataTypes.BOOL, True, False),
            ("123", HtDataTypes.INT, 123, False),
            ("-321", HtDataTypes.INT, -321, False),
            ("123.456", HtDataTypes.FLOAT, 123.456, False),
            ("-321.456", HtDataTypes.FLOAT, -321.456, False),
            ("789", HtDataTypes.FLOAT, 789.0, False),
            # -- should raise a 'ValueError':
            ("True", HtDataTypes.BOOL, None, False),
            ("False", HtDataTypes.BOOL, None, False),
            ("true", HtDataTypes.BOOL, None, False),
            ("false", HtDataTypes.BOOL, None, False),
            ("yes", HtDataTypes.BOOL, None, False),
            ("no", HtDataTypes.BOOL, None, False),
            ("y", HtDataTypes.BOOL, None, False),
            ("n", HtDataTypes.BOOL, None, False),
            ("TRUE", HtDataTypes.BOOL, None, False),
            ("FALSE", HtDataTypes.BOOL, None, False),
            ("YES", HtDataTypes.BOOL, None, False),
            ("NO", HtDataTypes.BOOL, None, False),
            ("Y", HtDataTypes.BOOL, None, False),
            ("N", HtDataTypes.BOOL, None, False),
            ("abc", HtDataTypes.BOOL, None, False),
            ("def", HtDataTypes.INT, None, False),
            ("--99", HtDataTypes.INT, None, False),
            ("12+55", HtDataTypes.INT, None, False),
            ("ghi", HtDataTypes.FLOAT, None, False),
            ("--99.0", HtDataTypes.FLOAT, None, False),
            ("12.3+55.9", HtDataTypes.FLOAT, None, False),
            ("789", HtDataTypes.FLOAT, None, True),
            # ...
        ])
    def test_from_str_static(self, s: str, data_type: HtDataTypes,
                             exp_value: Optional[HtParamValueType],
                             strict: bool):
        if exp_value is None:
            with pytest.raises(ValueError):
                HtParam.from_str(s, data_type, strict)
        else:
            assert HtParam.from_str(s, data_type, strict) == exp_value
        #assert 0

    def test_from_str_static_assert(self):
        with pytest.raises(AssertionError):
            HtParam.from_str("", 0)
        #assert 0

    @pytest.mark.parametrize("param", HtParams.values())
    def test_from_str_member(self, param: HtParam):
        assert param.from_str(param.to_str(
            param.min_val)) == param.min_val  # type: ignore
        assert param.from_str(param.to_str(
            param.max_val)) == param.max_val  # type: ignore
        #assert 0

    @pytest.mark.parametrize(
        "val, data_type, exp_str",
        [
            (False, HtDataTypes.BOOL, "0"),
            (True, HtDataTypes.BOOL, "1"),
            (123, HtDataTypes.INT, "123"),
            (-321, HtDataTypes.INT, "-321"),
            (123.456, HtDataTypes.FLOAT, "123.456"),
            (-321.456, HtDataTypes.FLOAT, "-321.456"),
            (789, HtDataTypes.FLOAT, "789.0"),
            (-789, HtDataTypes.FLOAT, "-789.0"),
            (789.0, HtDataTypes.FLOAT, "789.0"),
            (-789.0, HtDataTypes.FLOAT, "-789.0"),
            # ... add some more samples here!
        ])
    def test_to_str_static(self, val: HtParamValueType, data_type: HtDataTypes,
                           exp_str: str):
        assert HtParam.to_str(val, data_type) == exp_str
        #assert 0

    @pytest.mark.parametrize("param", HtParams.values())
    def test_to_str_member(self, param: HtParam):
        assert param.to_str(param.min_val) == HtParam.to_str(
            param.min_val, param.data_type)  # type: ignore
        assert param.to_str(param.max_val) == HtParam.to_str(
            param.max_val, param.data_type)  # type: ignore
        #assert 0

    @pytest.mark.parametrize("name, param", HtParams.items())
    def test_repr(self, name: str, param: HtParam):
        assert repr(param) == "HtParam({},{:d},{!r},{}[{},{}])".format(
            param.dp_type, param.dp_number, param.acl, param.data_type,
            param.min_val, param.max_val)
        #assert 0

    @pytest.mark.parametrize("name, cmd",
                             [(name, param.cmd())
                              for name, param in HtParams.items()])
    def test_cmd(self, name: str, cmd: str):
        m = re.match(r"^[S|M]P,NR=(\d+)$", cmd)
        assert m is not None, "non valid command string for parameter {!r} [{!r}]".format(
            name, cmd)
        #assert 0

    @pytest.mark.parametrize("name, param", HtParams.items())
    def test_set_limits(self, name: str, param: HtParam):
        assert not param.set_limits(param.min_val, param.max_val)
        #assert 0

    @pytest.mark.parametrize("name, param", HtParams.items())
    def test_in_limits(self, name: str, param: HtParam):
        assert param.in_limits(param.min_val)
        assert param.in_limits(param.max_val)
        #assert 0

    @pytest.mark.parametrize("param", HtParams.values())
    def test_in_limits_None(self, param: HtParam):
        assert param.in_limits(None)
 def test_dump(self):
     assert HtParams.dump() is None
 def test_get(self, name: str, param: HtParam):
     assert HtParams.get(name) == param
Beispiel #8
0
    def do_GET(self):
        parsed_path = urlparse.urlparse(self.path)
        _logger.info(parsed_path.path.lower())

        result = None
        try:
            hp.reconnect()
            hp.login()

            if parsed_path.path.lower() in ("/datetime/sync",
                                            "/datetime/sync/"):
                # synchronize the system time of the heat pump with the current time
                dt, _ = hp.set_date_time(datetime.now())
                result = {"datetime": dt.isoformat()}
                _logger.debug(dt.isoformat())

            elif parsed_path.path.lower() in ("/faultlist/last",
                                              "/faultlist/last/"):
                # query for the last fault message of the heat pump
                idx, err, dt, msg = hp.get_last_fault()
                result = {
                    "index": idx,
                    "error": err,
                    "datetime": dt.isoformat(),
                    "message": msg
                }
                _logger.debug("#{:d} [{}]: {:d}, {}".format(
                    idx, dt.isoformat(), err, msg))

            elif parsed_path.path.lower() in ("/faultlist", "/faultlist/"):
                # query for the whole fault list of the heat pump
                result = []
                for e in hp.get_fault_list():
                    e.update({"datetime": e["datetime"].isoformat()
                              })  # convert datetime dict entry to string
                    result.append(e)
                    _logger.debug("#{:03d} [{}]: {:05d}, {}".format(
                        e["index"], e["datetime"], e["error"], e["message"]))

            elif parsed_path.path.lower() == "/":
                qsl = urlparse.parse_qsl(parsed_path.query,
                                         keep_blank_values=True)
                _logger.info(qsl)
                result = {}
                if not qsl:
                    # query for all "known" parameters
                    for name in HtParams.keys():
                        value = hp.get_param(name)
                        # convert boolean values to 0/1 (if desired)
                        if args.boolasint and HtParams[
                                name].data_type == HtDataTypes.BOOL:
                            value = 1 if value else 0
                        result.update({name: value})
                        _logger.debug("{}: {}".format(name, value))
                else:
                    # query and/or set specific parameter values of the heat pump
                    params = {}
                    try:
                        # check if all requested/given parameter names are known and all passed values are valid
                        for query in qsl:
                            name, value = query  # value is '' (blank string) for non given values
                            # try to convert the passed value (if given) to the specific data type
                            value = HtParams[name].from_str(
                                value) if value else None
                            params.update({name: value})
                    except KeyError as ex:
                        # for unknown parameter name: HTTP response 404 = Not Found
                        raise HttpGetException(404, str(ex))
                    except ValueError as ex:
                        # for an invalid parameter value: HTTP response 400 = Bad Request
                        raise HttpGetException(400, str(ex))
                    # query/set all requested parameter values
                    for name, value in params.items():
                        if value is None:
                            # query for the value of the requested parameter
                            value = hp.get_param(name)
                        else:
                            # set the parameter of the heat pump to the passed value
                            value = hp.set_param(name, value)
                        # convert boolean values to 0/1 (if desired)
                        if args.boolasint and HtParams[
                                name].data_type == HtDataTypes.BOOL:
                            value = 1 if value else 0
                        result.update({name: value})
                        _logger.debug("{}: {}".format(name, value))

            else:
                # for an invalid url request: HTTP response 400 = Bad Request
                raise HttpGetException(
                    400, "invalid url request {!r}".format(
                        parsed_path.path.lower()))

        except HttpGetException as ex:
            _logger.error(ex)
            self.send_response(ex.response_code, str(ex))
            self.send_header("Content-Type", "application/json")
            self.end_headers()
        except Exception as ex:
            _logger.error(ex)
            # HTTP response 500 = Internal Server Error
            self.send_response(500, str(ex))
            self.send_header("Content-Type", "application/json")
            self.end_headers()
        else:
            # HTTP response 200 = OK
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            message = json.dumps(result, indent=2, sort_keys=True)
            _logger.info(message)
            self.wfile.write(bytes(message, "utf8"))

        finally:
            hp.logout()  # logout() should not fail!
Beispiel #9
0
class TestHtHeatpump:
    @pytest.mark.run_if_connected
    def test_open_connection(self, hthp: HtHeatpump):
        assert hthp.is_open
        with pytest.raises(IOError):
            hthp.open_connection()
        #assert 0

    @pytest.mark.parametrize("action", [
        VerifyAction.NONE(), {VerifyAction.NAME},
        {VerifyAction.NAME, VerifyAction.MIN},
        {VerifyAction.NAME, VerifyAction.MIN, VerifyAction.MAX},
        {
            VerifyAction.NAME, VerifyAction.MIN, VerifyAction.MAX,
            VerifyAction.VALUE
        }, {VerifyAction.MIN, VerifyAction.MAX, VerifyAction.VALUE},
        {VerifyAction.MAX, VerifyAction.VALUE}, {VerifyAction.VALUE},
        VerifyAction.ALL()
    ])
    def test_verify_param_action(self, cmdopt_device: str,
                                 cmdopt_baudrate: int, action: set):
        hp = HtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        val = hp.verify_param_action
        assert isinstance(val, set)
        hp.verify_param_action = action
        assert hp.verify_param_action == action
        hp.verify_param_action = val
        #assert 0

    def test_verify_param_error(self, cmdopt_device: str,
                                cmdopt_baudrate: int):
        hp = HtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        val = hp.verify_param_error
        assert isinstance(val, bool)
        hp.verify_param_error = True
        assert hp.verify_param_error is True
        hp.verify_param_error = False
        assert hp.verify_param_error is False
        hp.verify_param_error = val
        #assert 0

    def test_send_request(self, cmdopt_device: str, cmdopt_baudrate: int):
        hp = HtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        with pytest.raises(IOError):
            hp.send_request(r"LIN")
        #assert 0

    def test_read_response(self, cmdopt_device: str, cmdopt_baudrate: int):
        hp = HtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        with pytest.raises(IOError):
            hp.read_response()
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_get_serial_number(self, hthp: HtHeatpump):
        rid = hthp.get_serial_number()
        assert isinstance(rid, int), "'rid' must be of type int"
        assert rid > 0
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_get_version(self, hthp: HtHeatpump):
        version = hthp.get_version()
        # ( "3.0.20", 2321 )
        assert isinstance(version, tuple), "'version' must be of type tuple"
        assert len(version) == 2
        ver_str, ver_num = version
        assert isinstance(ver_str, str), "'ver_str' must be of type str"
        m = re.match(r"^(\d+).(\d+).(\d+)$", ver_str)
        assert m is not None, "invalid version string [{!r}]".format(ver_str)
        assert isinstance(ver_num, int), "'ver_num' must be of type int"
        assert ver_num > 0
        hthp.send_request(r"SP,NR=9")
        resp = hthp.read_response()
        m = re.match(r"^SP,NR=9,.*NAME=([^,]+).*VAL=([^,]+).*$", resp)
        assert m is not None, "invalid response for query of the software version [{!r}]".format(
            resp)
        assert ver_str == m.group(1).strip()
        assert ver_num == int(m.group(2))
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_get_date_time(self, hthp: HtHeatpump):
        date_time = hthp.get_date_time()
        # (datetime.datetime(...), 2)  # 2 = Tuesday
        assert isinstance(date_time,
                          tuple), "'date_time' must be of type tuple"
        assert len(date_time) == 2
        dt, weekday = date_time
        assert isinstance(dt,
                          datetime.datetime), "'dt' must be of type datetime"
        assert isinstance(weekday, int), "'weekday' must be of type int"
        assert weekday in range(1, 8)
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_set_date_time(self, hthp: HtHeatpump):
        pass  # TODO
        #assert 0

    def test_set_date_time_raises_TypeError(self, cmdopt_device: str,
                                            cmdopt_baudrate: int):
        hp = HtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        with pytest.raises(TypeError):
            hp.set_date_time(123)  # type: ignore
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_get_last_fault(self, hthp: HtHeatpump):
        fault = hthp.get_last_fault()
        # (29, 20, datetime.datetime(...), "EQ_Spreizung")
        assert isinstance(fault, tuple), "'fault' must be of type tuple"
        assert len(fault) == 4
        index, error, dt, msg = fault
        assert isinstance(index, int), "'index' must be of type int"
        assert 0 <= index < hthp.get_fault_list_size()
        assert isinstance(error, int), "'error' must be of type int"
        assert error >= 0
        assert isinstance(dt,
                          datetime.datetime), "'dt' must be of type datetime"
        assert isinstance(msg, str), "'msg' must be of type str"
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_get_fault_list_size(self, hthp: HtHeatpump):
        size = hthp.get_fault_list_size()
        assert isinstance(size, int), "'size' must be of type int"
        assert size >= 0
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_get_fault_list(self, hthp: HtHeatpump):
        fault_list = hthp.get_fault_list()
        # [ { "index": 29,  # fault list index
        #     "error": 20,  # error code
        #     "datetime": datetime.datetime(...),  # date and time of the entry
        #     "message": "EQ_Spreizung",  # error message
        #     },
        #   # ...
        #   ]
        assert isinstance(fault_list,
                          list), "'fault_list' must be of type list"
        for entry in fault_list:
            assert isinstance(entry, dict), "'entry' must be of type dict"
            index = entry["index"]
            assert isinstance(index, int), "'index' must be of type int"
            assert 0 <= index < hthp.get_fault_list_size()
            error = entry["error"]
            assert isinstance(error, int), "'error' must be of type int"
            assert error >= 0
            dt = entry["datetime"]
            assert isinstance(
                dt, datetime.datetime), "'dt' must be of type datetime"
            msg = entry["message"]
            assert isinstance(msg, str), "'msg' must be of type str"
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_get_fault_list_with_index(self, hthp: HtHeatpump):
        size = hthp.get_fault_list_size()
        assert isinstance(size, int), "'size' must be of type int"
        assert size >= 0
        for i in range(size):
            fault_list = hthp.get_fault_list(i)
            assert isinstance(fault_list,
                              list), "'fault_list' must be of type list"
            assert len(fault_list) == 1
            entry = fault_list[0]
            assert isinstance(entry, dict), "'entry' must be of type dict"
            index = entry["index"]
            assert isinstance(index, int), "'index' must be of type int"
            assert 0 <= index < hthp.get_fault_list_size()
            error = entry["error"]
            assert isinstance(error, int), "'error' must be of type int"
            assert error >= 0
            dt = entry["datetime"]
            assert isinstance(
                dt, datetime.datetime), "'dt' must be of type datetime"
            msg = entry["message"]
            assert isinstance(msg, str), "'msg' must be of type str"
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_get_fault_list_with_index_raises_IOError(self, hthp: HtHeatpump):
        with pytest.raises(IOError):
            hthp.get_fault_list(-1)  # index=-1 is invalid
        with pytest.raises(IOError):
            hthp.get_fault_list(9999)  # index=9999 is invalid
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_get_fault_list_with_indices(self, hthp: HtHeatpump):
        size = hthp.get_fault_list_size()
        for cnt in range(size + 1):
            indices = random.sample(range(size), cnt)
            fault_list = hthp.get_fault_list(*indices)
            assert isinstance(fault_list,
                              list), "'fault_list' must be of type list"
            for entry in fault_list:
                assert isinstance(entry, dict), "'entry' must be of type dict"
                index = entry["index"]
                assert isinstance(index, int), "'index' must be of type int"
                assert 0 <= index < hthp.get_fault_list_size()
                error = entry["error"]
                assert isinstance(error, int), "'error' must be of type int"
                assert error >= 0
                dt = entry["datetime"]
                assert isinstance(
                    dt, datetime.datetime), "'dt' must be of type datetime"
                msg = entry["message"]
                assert isinstance(msg, str), "'msg' must be of type str"
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.parametrize("name, param", HtParams.items())
    def test_get_param(self, hthp: HtHeatpump, name: str, param: HtParam):
        value = hthp.get_param(name)
        assert value is not None, "'value' must not be None"
        assert param.in_limits(value)
        #assert 0

    def test_get_param_raises_KeyError(self, cmdopt_device: str,
                                       cmdopt_baudrate: int):
        hp = HtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        with pytest.raises(KeyError):
            hp.get_param("BlaBlaBla")
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.parametrize("name, param", HtParams.items())
    def test_set_param(self, hthp: HtHeatpump, name: str, param: HtParam):
        pass  # TODO
        #assert 0

    def test_set_param_raises_KeyError(self, cmdopt_device: str,
                                       cmdopt_baudrate: int):
        hp = HtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        with pytest.raises(KeyError):
            hp.set_param("BlaBlaBla", 123)
        #assert 0

    @pytest.mark.parametrize(
        "name, param",
        [(name, param) for name, param in HtParams.items()
         if param.data_type in (HtDataTypes.INT, HtDataTypes.FLOAT)])
    def test_set_param_raises_ValueError(self, cmdopt_device: str,
                                         cmdopt_baudrate: int, name: str,
                                         param: HtParam):
        hp = HtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        with pytest.raises(ValueError):
            hp.set_param(name, param.min_val - 1,
                         ignore_limits=False)  # type: ignore
        with pytest.raises(ValueError):
            hp.set_param(name, param.max_val + 1,
                         ignore_limits=False)  # type: ignore
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_in_error(self, hthp: HtHeatpump):
        in_error = hthp.in_error
        assert isinstance(in_error, bool), "'in_error' must be of type bool"
        assert in_error == hthp.get_param("Stoerung")
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_query(self, hthp: HtHeatpump):
        values = hthp.query()
        # { "HKR Soll_Raum": 21.0,
        #   "Stoerung": False,
        #   "Temp. Aussen": 8.8,
        #   # ...
        #   }
        assert isinstance(values, dict), "'values' must be of type dict"
        assert len(values) == len(HtParams)
        for n, v in values.items():
            assert n in HtParams
            assert v is not None
            assert HtParams[n].in_limits(v)
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.parametrize("names", [
        random.sample(HtParams.keys(), cnt)
        for cnt in range(len(HtParams) + 1)
    ])
    def test_query_with_names(self, hthp: HtHeatpump, names: List[str]):
        values = hthp.query(*names)
        # { "HKR Soll_Raum": 21.0,
        #   "Stoerung": False,
        #   "Temp. Aussen": 8.8,
        #   # ...
        #   }
        assert isinstance(values, dict), "'values' must be of type dict"
        assert not names or len(values) == len(set(names))
        for n, v in values.items():
            assert n in HtParams
            assert not names or n in names
            assert v is not None
            assert HtParams[n].in_limits(v)
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_fast_query(self, hthp: HtHeatpump):
        values = hthp.fast_query()
        assert isinstance(values, dict), "'values' must be of type dict"
        assert len(values) == len(HtParams.of_type("MP"))
        for n, v in values.items():
            assert n in HtParams
            assert v is not None
            assert HtParams[n].in_limits(v)
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.parametrize("names", [
        random.sample(HtParams.of_type("MP").keys(), cnt)
        for cnt in range(len(HtParams.of_type("MP")) + 1)
    ])
    def test_fast_query_with_names(self, hthp: HtHeatpump, names: List[str]):
        values = hthp.fast_query(*names)
        assert isinstance(values, dict), "'values' must be of type dict"
        assert not names or len(values) == len(set(names))
        for n, v in values.items():
            assert n in HtParams
            assert not names or n in names
            assert v is not None
            assert HtParams[n].in_limits(v)
        #assert 0

    def test_fast_query_with_names_raises_KeyError(self, cmdopt_device: str,
                                                   cmdopt_baudrate: int):
        hp = HtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        with pytest.raises(KeyError):
            hp.fast_query("BlaBlaBla")
        #assert 0

    @pytest.mark.parametrize("names", [
        random.sample(HtParams.of_type("SP").keys(), cnt)
        for cnt in range(1,
                         len(HtParams.of_type("SP")) + 1)
    ])
    def test_fast_query_with_names_raises_ValueError(self, cmdopt_device: str,
                                                     cmdopt_baudrate: int,
                                                     names: List[str]):
        hp = HtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        with pytest.raises(ValueError):
            hp.fast_query(*names)
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_get_time_progs(self, hthp: HtHeatpump):
        time_progs = hthp.get_time_progs()
        assert isinstance(time_progs,
                          List), "'time_progs' must be of type list"
        assert len(time_progs) > 0
        assert all(
            [isinstance(time_prog, TimeProgram) for time_prog in time_progs])
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.parametrize(
        "index",
        range(5))  # TODO range(5) -> range(len(hthp.get_time_progs()))
    def test_get_time_prog(self, hthp: HtHeatpump, index: int):
        time_prog = hthp.get_time_prog(index, with_entries=False)
        assert isinstance(
            time_prog, TimeProgram), "'time_prog' must be of type TimeProgram"
        time_prog = hthp.get_time_prog(index, with_entries=True)
        assert isinstance(
            time_prog, TimeProgram), "'time_prog' must be of type TimeProgram"
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.parametrize("index", [-1, 5])
    def test_get_time_prog_raises_IOError(self, hthp: HtHeatpump, index: int):
        with pytest.raises(IOError):
            hthp.get_time_prog(index, with_entries=False)
        with pytest.raises(IOError):
            hthp.get_time_prog(index, with_entries=True)
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.parametrize(
        "index, day, num",
        [  # for ALL time program entries
            (index, day, num) for index in range(5) for day in range(7)
            for num in range(7)
        ])
    def test_get_time_prog_entry(self, hthp: HtHeatpump, index: int, day: int,
                                 num: int):
        entry = hthp.get_time_prog_entry(index, day, num)
        assert isinstance(
            entry, TimeProgEntry), "'entry' must be of type TimeProgEntry"
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.parametrize(
        "index, day, num",
        [
            (5, 0, 0),  # index=5 is invalid
            (0, 7, 0),  # day=7 is invalid
            (0, 0, 7),  # num=7 is invalid
        ])
    def test_get_time_prog_entry_raises_IOError(self, hthp: HtHeatpump,
                                                index: int, day: int,
                                                num: int):
        with pytest.raises(IOError):
            hthp.get_time_prog_entry(index, day, num)
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_set_time_prog_entry(self, hthp: HtHeatpump):
        pass  # TODO
        #assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    def test_set_time_prog(self, hthp: HtHeatpump):
        pass  # TODO
Beispiel #10
0
def main():
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s %(levelname)s [%(name)s|%(funcName)s]: %(message)s"
    )

    hp = HtHeatpump("/dev/ttyUSB0", baudrate=115200)
    hp.open_connection()
    hp.login()

    rid = hp.get_serial_number()
    print("connected successfully to heat pump with serial number {:d}".format(
        rid))
    ver = hp.get_version()
    print("software version = {} ({:d})".format(ver[0], ver[1]))

    names = HtParams.of_type("MP").keys()

    t_query = t_fast_query = 0.0
    for i in range(10):
        start = timer()
        values = hp.query(*names)
        t_query += (timer() - start)
        start = timer()
        values = hp.fast_query(*names)
        t_fast_query += (timer() - start)
    i += 1
    t_query = t_query / i
    t_fast_query = t_fast_query / i

    print("\n" + "-" * 100)
    print("HtHeatpump.query({:d})      execution time: {:.3f} sec".format(
        len(names), t_query))
    print("HtHeatpump.fast_query({:d}) execution time: {:.3f} sec".format(
        len(names), t_fast_query))
    print("-> {:.3f} x faster".format(t_query / t_fast_query))

    while True:
        print("\n" + "-" * 100)
        rand_names = random.sample(names, random.randint(0, len(names)))
        print("{!s}".format(rand_names))
        # fast query for the given parameter(s)
        values = hp.fast_query(*rand_names)
        # print the current value(s) of the retrieved parameter(s)
        print(", ".join(
            map(lambda name: "{!r} = {}".format(name, values[name]), values)))
        #for name in sorted(values.keys()):
        #    print("{:{width}} [{},{:02d}]: {}".format(name,
        #                                              HtParams[name].dp_type,
        #                                              HtParams[name].dp_number,
        #                                              values[name],
        #                                              width=len(max(values.keys(), key=len))))
        for i in range(5, 0, -1):
            #print(i)
            sys.stdout.write("\rContinue in {:d}s ...".format(i))
            sys.stdout.flush()
            time.sleep(1)
        print("\rContinue in 0s ...")

    hp.logout()  # try to logout for an ordinary cancellation (if possible)
    hp.close_connection()

    sys.exit(0)
Beispiel #11
0
def main():
    parser = argparse.ArgumentParser(
        description=textwrap.dedent('''\
            Command line tool to query for parameters of the Heliotherm heat pump.

            Example:

              $ python3 %(prog)s --device /dev/ttyUSB1 "Temp. Aussen" "Stoerung"
              Stoerung    : False
              Temp. Aussen: 5.0
            '''),
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=textwrap.dedent('''\
            DISCLAIMER
            ----------

              Please note that any incorrect or careless usage of this program as well as
              errors in the implementation can damage your heat pump!

              Therefore, the author does not provide any guarantee or warranty concerning
              to correctness, functionality or performance and does not accept any liability
              for damage caused by this program or mentioned information.

              Thus, use it on your own risk!
            ''') + "\r\n")

    parser.add_argument(
        "-d",
        "--device",
        default="/dev/ttyUSB0",
        type=str,
        help=
        "the serial device on which the heat pump is connected, default: %(default)s"
    )

    parser.add_argument(
        "-b",
        "--baudrate",
        default=115200,
        type=int,
        # the supported baudrates of the Heliotherm heat pump (HP08S10W-WEB):
        choices=[9600, 19200, 38400, 57600, 115200],
        help=
        "baudrate of the serial connection (same as configured on the heat pump), default: %(default)s"
    )

    parser.add_argument("-j",
                        "--json",
                        action="store_true",
                        help="output will be in JSON format")

    parser.add_argument("--boolasint",
                        action="store_true",
                        help="boolean values will be stored as '0' and '1'")

    parser.add_argument("-t",
                        "--time",
                        action="store_true",
                        help="measure the execution time")

    parser.add_argument("-v",
                        "--verbose",
                        action="store_true",
                        help="increase output verbosity by activating logging")

    parser.add_argument(
        "name",
        type=str,
        nargs='*',
        help=
        "parameter name(s) to query for (as defined in htparams.csv) or omit to query for all known parameters"
    )

    args = parser.parse_args()

    # activate logging with level DEBUG in verbose mode
    if args.verbose:
        logging.basicConfig(level=logging.DEBUG)
    else:
        logging.basicConfig(level=logging.WARNING)
    # if not given, query for all "known" parameters
    params = args.name if args.name else HtParams.keys()

    hp = HtHeatpump(args.device, baudrate=args.baudrate)
    start = timer()
    try:
        hp.open_connection()
        hp.login()

        rid = hp.get_serial_number()
        if args.verbose:
            _logger.info(
                "connected successfully to heat pump with serial number {:d}".
                format(rid))
        ver = hp.get_version()
        if args.verbose:
            _logger.info("software version = {} ({:d})".format(ver[0], ver[1]))

        # query for the given parameter(s)
        values = {}
        for p in params:
            val = hp.get_param(p)
            if args.boolasint and HtParams[p].data_type == HtDataTypes.BOOL:
                val = 1 if val else 0
            values.update({p: val})

        # print the current value(s) of the retrieved parameter(s)
        if args.json:
            print(json.dumps(values, indent=4, sort_keys=True))
        else:
            if len(params) > 1:
                for p in sorted(params):
                    print("{:{width}}: {}".format(p,
                                                  values[p],
                                                  width=len(
                                                      max(params, key=len))))
            elif len(params) == 1:
                print(values[params[0]])

    except Exception as ex:
        _logger.error(ex)
        sys.exit(1)
    finally:
        hp.logout()  # try to logout for an ordinary cancellation (if possible)
        hp.close_connection()
    end = timer()

    # print execution time only if desired
    if args.time:
        print("execution time: {:.2f} sec".format(end - start))

    sys.exit(0)
Beispiel #12
0
    def do_GET(self):
        parsed_path = urlparse.urlparse(self.path)
        _logger.info(parsed_path.path.lower())

        try:
            hp.reconnect()
            hp.login()

            if re.match(self.DATETIME_SYNC_PATH, parsed_path.path.lower()):
                # synchronize the system time of the heat pump with the current time
                dt, _ = hp.set_date_time(datetime.now())
                result = {"datetime": dt.isoformat()}
                _logger.debug(dt.isoformat())

            elif re.match(self.DATETIME_PATH, parsed_path.path.lower()):
                # return the current system time of the heat pump
                dt, _ = hp.get_date_time()
                result = {"datetime": dt.isoformat()}
                _logger.debug(dt.isoformat())

            elif re.match(self.FAULTLIST_LAST_PATH, parsed_path.path.lower()):
                # query for the last fault message of the heat pump
                idx, err, dt, msg = hp.get_last_fault()
                result = {
                    "index": idx,
                    "error": err,
                    "datetime": dt.isoformat(),
                    "message": msg
                }
                _logger.debug("#{:d} [{}]: {:d}, {}".format(
                    idx, dt.isoformat(), err, msg))

            elif re.match(self.FAULTLIST_PATH, parsed_path.path.lower()):
                # query for the whole fault list of the heat pump
                result = []
                for entry in hp.get_fault_list():
                    entry.update({"datetime": entry["datetime"].isoformat()
                                  })  # convert datetime dict entry to string
                    result.append(entry)
                    _logger.debug("#{:03d} [{}]: {:05d}, {}".format(
                        entry["index"], entry["datetime"], entry["error"],
                        entry["message"]))

            elif re.match(self.TIMEPROG_PATH, parsed_path.path.lower()):
                # query for a specific time program of the heat pump (including all time program entries)
                m = re.match(self.TIMEPROG_PATH, parsed_path.path.lower())
                try:
                    idx = int(m.group(1))
                except ValueError as ex:
                    # for an invalid time program index: HTTP response 400 = Bad Request
                    raise HttpGetException(400, str(ex))
                time_prog = hp.get_time_prog(idx, with_entries=True)
                result = time_prog.as_json()
                _logger.debug("{}".format(time_prog))

            elif re.match(self.TIMEPROGS_PATH, parsed_path.path.lower()):
                # query for the list of available time programs of the heat pump
                time_progs = hp.get_time_progs()
                result = []
                for time_prog in time_progs:
                    result.append(time_prog.as_json(with_entries=False))
                    _logger.debug("{}".format(time_prog))

            elif re.match(self.PARAM_PATH, parsed_path.path.lower()):
                # query and/or set parameter values of the heat pump
                qsl = urlparse.parse_qsl(parsed_path.query,
                                         keep_blank_values=True)
                _logger.info(qsl)
                result = {}
                if not qsl:
                    # query for all "known" parameters
                    for name in HtParams.keys():
                        value = hp.get_param(name)
                        # convert boolean values to 0/1 (if desired)
                        if args.boolasint and HtParams[
                                name].data_type == HtDataTypes.BOOL:
                            value = 1 if value else 0
                        result.update({name: value})
                        _logger.debug("{}: {}".format(name, value))
                else:
                    # query and/or set specific parameter values of the heat pump
                    params = {}
                    try:
                        # check if all requested/given parameter names are known and all passed values are valid
                        for query in qsl:
                            name, value = query  # value is '' (blank string) for non given values
                            # try to convert the passed value (if given) to the specific data type
                            value = HtParams[name].from_str(
                                value) if value else None
                            params.update({name: value})
                    except KeyError as ex:
                        # for unknown parameter name: HTTP response 404 = Not Found
                        raise HttpGetException(404, str(ex))
                    except ValueError as ex:
                        # for an invalid parameter value: HTTP response 400 = Bad Request
                        raise HttpGetException(400, str(ex))
                    # query/set all requested parameter values
                    for name, value in params.items():
                        if value is None:
                            # query for the value of the requested parameter
                            value = hp.get_param(name)
                        else:
                            # set the parameter of the heat pump to the passed value
                            value = hp.set_param(name, value)
                        # convert boolean values to 0/1 (if desired)
                        if args.boolasint and HtParams[
                                name].data_type == HtDataTypes.BOOL:
                            value = 1 if value else 0
                        result.update({name: value})
                        _logger.debug("{}: {}".format(name, value))

            elif parsed_path.path.lower() == "/":
                # query for some properties of the connected heat pump
                property_id = hp.get_param(
                    "Liegenschaft") if "Liegenschaft" in HtParams else 0
                serial_number = hp.get_serial_number()
                software_version, _ = hp.get_version()
                dt, _ = hp.get_date_time()
                result = {
                    "property_id": property_id,
                    "serial_number": serial_number,
                    "software_version": software_version,
                    "datetime": dt.isoformat(),
                }
                _logger.debug(
                    "property_id: {}, serial_number: {}, software_version: {}, datetime: {}"
                    .format(property_id, serial_number, software_version,
                            dt.isoformat()))

            else:
                # for an invalid url request: HTTP response 400 = Bad Request
                raise HttpGetException(
                    400, "invalid url request {!r}".format(
                        parsed_path.path.lower()))

        except HttpGetException as ex:
            _logger.exception(ex)
            self.send_response(ex.response_code, str(ex))
            self.send_header("Content-Type", "application/json")
            self.end_headers()
        except Exception as ex:
            _logger.exception(ex)
            # HTTP response 500 = Internal Server Error
            self.send_response(500, str(ex))
            self.send_header("Content-Type", "application/json")
            self.end_headers()
        else:
            # HTTP response 200 = OK
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            message = json.dumps(result, indent=2, sort_keys=True)
            _logger.info(message)
            self.wfile.write(bytes(message, "utf8"))

        finally:
            hp.logout()  # logout() should not fail!
Beispiel #13
0
class TestHtParam:
    @pytest.mark.parametrize("str, data_type, exp_value", [
        ("TestString", HtDataTypes.STRING, "TestString"),
        ("0", HtDataTypes.BOOL, False),
        ("1", HtDataTypes.BOOL, True),
        ("True", HtDataTypes.BOOL, True),
        ("False", HtDataTypes.BOOL, False),
        ("true", HtDataTypes.BOOL, True),
        ("false", HtDataTypes.BOOL, False),
        ("yes", HtDataTypes.BOOL, True),
        ("no", HtDataTypes.BOOL, False),
        ("y", HtDataTypes.BOOL, True),
        ("n", HtDataTypes.BOOL, False),
        ("TRUE", HtDataTypes.BOOL, True),
        ("FALSE", HtDataTypes.BOOL, False),
        ("YES", HtDataTypes.BOOL, True),
        ("NO", HtDataTypes.BOOL, False),
        ("Y", HtDataTypes.BOOL, True),
        ("N", HtDataTypes.BOOL, False),
        ("123", HtDataTypes.INT, 123),
        ("-321", HtDataTypes.INT, -321),
        ("123.456", HtDataTypes.FLOAT, 123.456),
        ("-321.456", HtDataTypes.FLOAT, -321.456),
        ("789", HtDataTypes.FLOAT, 789),
        # -- should raise a 'ValueError':
        ("abc", HtDataTypes.BOOL, None),
        ("def", HtDataTypes.INT, None),
        ("--99", HtDataTypes.INT, None),
        ("12+55", HtDataTypes.INT, None),
        ("ghi", HtDataTypes.FLOAT, None),
        ("--99.0", HtDataTypes.FLOAT, None),
        ("12.3+55.9", HtDataTypes.FLOAT, None),
        # ...
    ])
    def test_from_str(self, str, data_type, exp_value):
        if exp_value is None:
            with pytest.raises(ValueError):
                HtParam.from_str(str, data_type)
        else:
            assert HtParam.from_str(str, data_type) == exp_value
        #assert 0

    @pytest.mark.parametrize("val, data_type, exp_str", [
        ("TestString", HtDataTypes.STRING, "TestString"),
        (False, HtDataTypes.BOOL, "0"),
        (True, HtDataTypes.BOOL, "1"),
        (123, HtDataTypes.INT, "123"),
        (-321, HtDataTypes.INT, "-321"),
        (123.456, HtDataTypes.FLOAT, "123.456"),
        (-321.456, HtDataTypes.FLOAT, "-321.456"),
        (789, HtDataTypes.FLOAT, "789.0"),
        (-789, HtDataTypes.FLOAT, "-789.0"),
        (789.0, HtDataTypes.FLOAT, "789.0"),
        (-789.0, HtDataTypes.FLOAT, "-789.0"),
        # ... add some more samples here!
    ])
    def test_to_str(self, val, data_type, exp_str):
        assert HtParam.to_str(val, data_type) == exp_str
        #assert 0

    @pytest.mark.parametrize("name, cmd", [(name, param.cmd()) for name, param in HtParams.items()])
    def test_cmd_format(self, name, cmd):
        m = re.match("^[S|M]P,NR=(\d+)$", cmd)
        assert m is not None, "non valid command string for parameter {!r} [{!r}]".format(name, cmd)