예제 #1
0
 def get(self):
     """ Performs a fast query of a subset or all heat pump parameters representing a 'MP' data point. """
     _LOGGER.info("*** [GET] %s", request.url)
     params = list(request.args.keys())
     unknown = [name for name in params if name not in HtParams]
     if unknown:
         api.abort(
             404,
             "Parameter(s) {} not found".format(", ".join(
                 map(lambda name: "{!r}".format(name), unknown))),
         )
     invalid = [name for name in params if HtParams[name].dp_type != "MP"]
     if invalid:
         api.abort(
             400,
             "Parameter(s) {} doesn't represent a 'MP' data point".format(
                 ", ".join(map(lambda name: "{!r}".format(name), invalid))),
         )
     if not params:
         params = (name for name, param in HtParams.items()
                   if param.dp_type == "MP")
     with HtContext(ht_heatpump):
         res = ht_heatpump.fast_query(*params)
     for name, value in res.items():
         res[name] = bool_as_int(name, value)
     _LOGGER.debug("*** [GET] %s -> %s", request.url, res)
     return res
예제 #2
0
 async def test_fast_query(self, hthp: AioHtHeatpump):
     values = await hthp.fast_query_async()
     assert isinstance(values, dict), "'values' must be of type dict"
     assert len(values) == len(HtParams.of_type("MP"))
     for name, value in values.items():
         assert name in HtParams
         assert value is not None
         assert HtParams[name].in_limits(value)
예제 #3
0
 def test_fast_query_in_several_pieces(self, hthp: HtHeatpump):
     args = []
     cmd = ""
     mp_data_points = [(name, param) for name, param in HtParams.items()
                       if param.dp_type == "MP"]
     while len(cmd) < 255 * 2:  # request has do be done in 3 parts
         name, param = random.choice(mp_data_points)
         cmd += ",{}".format(param.dp_number)
         args.append(name)
     values = hthp.fast_query(*args)
     assert isinstance(values, dict), "'values' must be of type dict"
     assert not args or len(values) == len(set(args))
     for name, value in values.items():
         assert name in HtParams
         assert not args or name in args
         assert value is not None
         assert HtParams[name].in_limits(value)
예제 #4
0
파일: param.py 프로젝트: dstrigl/HtREST
 def get(self):
     """ Returns a subset or complete list of the known heat pump parameters with their current value. """
     _LOGGER.info("*** [GET] %s", request.url)
     params = list(request.args.keys())
     unknown = [name for name in params if name not in HtParams]
     if unknown:
         api.abort(
             404,
             "Parameter(s) {} not found".format(", ".join(
                 map(lambda name: "{!r}".format(name), unknown))),
         )
     if not params:
         params = HtParams.keys()
     with HtContext(ht_heatpump):
         res = {}
         for name in params:
             value = ht_heatpump.get_param(name)
             res.update({name: bool_as_int(name, value)})
     _LOGGER.debug("*** [GET] %s -> %s", request.url, res)
     return res
예제 #5
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 [%s]: %d, %s", 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 [%s]: %05d, %s",
                        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)) from ex
                time_prog = hp.get_time_prog(idx, with_entries=True)
                result = time_prog.as_json()
                _LOGGER.debug(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(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.bool_as_int
                            and HtParams[name].data_type == HtDataTypes.BOOL
                        ):
                            value = 1 if value else 0
                        result.update({name: value})
                        _LOGGER.debug("%s: %s", 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)) from ex
                    except ValueError as ex:
                        # for an invalid parameter value: HTTP response 400 = Bad Request
                        raise HttpGetException(400, str(ex)) from 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.bool_as_int
                            and HtParams[name].data_type == HtDataTypes.BOOL
                        ):
                            value = 1 if value else 0
                        result.update({name: value})
                        _LOGGER.debug("%s: %s", 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: %d, serial_number: %d, software_version: %s, datetime: %s",
                    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!
예제 #6
0
class TestAioHtHeatpump:
    @pytest.mark.run_if_connected
    def test_open_connection(self, hthp: AioHtHeatpump):
        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 = AioHtHeatpump(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 = AioHtHeatpump(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

    @pytest.mark.asyncio
    async def test_send_request(self, cmdopt_device: str, cmdopt_baudrate: int):
        hp = AioHtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        with pytest.raises(IOError):
            await hp.send_request_async(r"LIN")
        # assert 0

    @pytest.mark.asyncio
    async def test_read_response(self, cmdopt_device: str, cmdopt_baudrate: int):
        hp = AioHtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        with pytest.raises(IOError):
            await hp.read_response_async()
        # assert 0

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

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.asyncio
    async def test_get_version(self, hthp: AioHtHeatpump):
        version = await hthp.get_version_async()
        # ( "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
        await hthp.send_request_async(r"SP,NR=9")
        resp = await hthp.read_response_async()
        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")
    @pytest.mark.asyncio
    async def test_get_date_time(self, hthp: AioHtHeatpump):
        date_time = await hthp.get_date_time_async()
        # (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")
    @pytest.mark.asyncio
    async def test_set_date_time(self, hthp: AioHtHeatpump):
        pass  # TODO
        # assert 0

    @pytest.mark.asyncio
    async def test_set_date_time_raises_TypeError(
        self, cmdopt_device: str, cmdopt_baudrate: int
    ):
        hp = AioHtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        with pytest.raises(TypeError):
            await hp.set_date_time_async(123)  # type: ignore
        # assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.asyncio
    async def test_get_last_fault(self, hthp: AioHtHeatpump):
        fault = await hthp.get_last_fault_async()
        # (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 < await hthp.get_fault_list_size_async()
        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")
    @pytest.mark.asyncio
    async def test_get_fault_list_size(self, hthp: AioHtHeatpump):
        size = await hthp.get_fault_list_size_async()
        assert isinstance(size, int), "'size' must be of type int"
        assert size >= 0
        # assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.asyncio
    async def test_get_fault_list(self, hthp: AioHtHeatpump):
        fault_list = await hthp.get_fault_list_async()
        # [ { "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"
        assert len(fault_list) == await hthp.get_fault_list_size_async()
        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 < await hthp.get_fault_list_size_async()
            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.skip(reason="test needs a rework")
    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.asyncio
    async def test_get_fault_list_in_several_pieces(self, hthp: AioHtHeatpump):
        args = []
        cmd = ""
        fault_list_size = await hthp.get_fault_list_size_async()
        while len(cmd) < 255 * 2:  # request has do be done in 3 parts
            item = random.randint(0, fault_list_size - 1)
            cmd += ",{}".format(item)
            args.append(item)
        fault_list = await hthp.get_fault_list_async(*args)
        # [ { "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"
        assert len(fault_list) == len(args)
        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 < 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.asyncio
    async def test_get_fault_list_with_index(self, hthp: AioHtHeatpump):
        size = await hthp.get_fault_list_size_async()
        assert isinstance(size, int), "'size' must be of type int"
        assert size >= 0
        for i in range(size):
            fault_list = await hthp.get_fault_list_async(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 < await hthp.get_fault_list_size_async()
            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.asyncio
    async def test_get_fault_list_with_index_raises_IOError(self, hthp: AioHtHeatpump):
        with pytest.raises(IOError):
            await hthp.get_fault_list_async(-1)
        with pytest.raises(IOError):
            await hthp.get_fault_list_async(await hthp.get_fault_list_size_async())
        # assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.asyncio
    async def test_get_fault_list_with_indices(self, hthp: AioHtHeatpump):
        size = await hthp.get_fault_list_size_async()
        for cnt in range(size + 1):
            indices = random.sample(range(size), cnt)
            fault_list = await hthp.get_fault_list_async(*indices)
            assert isinstance(fault_list, list), "'fault_list' must be of type list"
            assert len(fault_list) == (cnt if cnt > 0 else size)
            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 < await hthp.get_fault_list_size_async()
                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())
    @pytest.mark.asyncio
    async def test_get_param(self, hthp: AioHtHeatpump, name: str, param: HtParam):
        value = await hthp.get_param_async(name)
        assert value is not None, "'value' must not be None"
        assert param.in_limits(value)
        # assert 0

    @pytest.mark.asyncio
    async def test_get_param_raises_KeyError(
        self, cmdopt_device: str, cmdopt_baudrate: int
    ):
        hp = AioHtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        with pytest.raises(KeyError):
            await hp.get_param_async("BlaBlaBla")
        # assert 0

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

    @pytest.mark.asyncio
    async def test_set_param_raises_KeyError(
        self, cmdopt_device: str, cmdopt_baudrate: int
    ):
        hp = AioHtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        with pytest.raises(KeyError):
            await hp.set_param_async("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)
        ],
    )
    @pytest.mark.asyncio
    async def test_set_param_raises_ValueError(
        self, cmdopt_device: str, cmdopt_baudrate: int, name: str, param: HtParam
    ):
        # hp = AioHtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        # with pytest.raises(ValueError):
        #    await hp.set_param_async(name, param.min_val - 1, ignore_limits=False)  # type: ignore
        # with pytest.raises(ValueError):
        #    await hp.set_param_async(name, param.max_val + 1, ignore_limits=False)  # type: ignore
        pass  # TODO
        # assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.asyncio
    async def test_in_error(self, hthp: AioHtHeatpump):
        in_error = await hthp.in_error_async
        assert isinstance(in_error, bool), "'in_error' must be of type bool"
        assert in_error == await hthp.get_param_async("Stoerung")
        # assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.asyncio
    async def test_query(self, hthp: AioHtHeatpump):
        values = await hthp.query_async()
        # { "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 name, value in values.items():
            assert name in HtParams
            assert value is not None
            assert HtParams[name].in_limits(value)
        # assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.parametrize(
        "names",
        [
            random.sample(sorted(HtParams.keys()), cnt)
            for cnt in range(len(HtParams) + 1)
        ],
    )
    @pytest.mark.asyncio
    async def test_query_with_names(self, hthp: AioHtHeatpump, names: List[str]):
        values = await hthp.query_async(*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 name, value in values.items():
            assert name in HtParams
            assert not names or name in names
            assert value is not None
            assert HtParams[name].in_limits(value)
        # assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.asyncio
    async def test_fast_query(self, hthp: AioHtHeatpump):
        values = await hthp.fast_query_async()
        assert isinstance(values, dict), "'values' must be of type dict"
        assert len(values) == len(HtParams.of_type("MP"))
        for name, value in values.items():
            assert name in HtParams
            assert value is not None
            assert HtParams[name].in_limits(value)
        # assert 0

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

    @pytest.mark.asyncio
    async def test_fast_query_with_names_raises_KeyError(
        self, cmdopt_device: str, cmdopt_baudrate: int
    ):
        hp = AioHtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
        with pytest.raises(KeyError):
            await hp.fast_query_async("BlaBlaBla")
        # assert 0

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

    # @pytest.mark.skip(reason="test needs a rework")
    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.asyncio
    async def test_fast_query_in_several_pieces(self, hthp: AioHtHeatpump):
        args = []
        cmd = ""
        mp_data_points = [
            (name, param) for name, param in HtParams.items() if param.dp_type == "MP"
        ]
        while len(cmd) < 255 * 2:  # request has do be done in 3 parts
            name, param = random.choice(mp_data_points)
            cmd += ",{}".format(param.dp_number)
            args.append(name)
        values = await hthp.fast_query_async(*args)
        assert isinstance(values, dict), "'values' must be of type dict"
        assert not args or len(values) == len(set(args))
        for name, value in values.items():
            assert name in HtParams
            assert not args or name in args
            assert value is not None
            assert HtParams[name].in_limits(value)
        # assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.asyncio
    async def test_get_time_progs(self, hthp: AioHtHeatpump):
        time_progs = await hthp.get_time_progs_async()
        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()))
    @pytest.mark.asyncio
    async def test_get_time_prog(self, hthp: AioHtHeatpump, index: int):
        time_prog = await hthp.get_time_prog_async(index, with_entries=False)
        assert isinstance(
            time_prog, TimeProgram
        ), "'time_prog' must be of type TimeProgram"
        time_prog = await hthp.get_time_prog_async(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])
    @pytest.mark.asyncio
    async def test_get_time_prog_raises_IOError(self, hthp: AioHtHeatpump, index: int):
        with pytest.raises(IOError):
            await hthp.get_time_prog_async(index, with_entries=False)
        with pytest.raises(IOError):
            await hthp.get_time_prog_async(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)
        ],
    )
    @pytest.mark.asyncio
    async def test_get_time_prog_entry(
        self, hthp: AioHtHeatpump, index: int, day: int, num: int
    ):
        entry = await hthp.get_time_prog_entry_async(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
        ],
    )
    @pytest.mark.asyncio
    async def test_get_time_prog_entry_raises_IOError(
        self, hthp: AioHtHeatpump, index: int, day: int, num: int
    ):
        with pytest.raises(IOError):
            await hthp.get_time_prog_entry_async(index, day, num)
        # assert 0

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

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.asyncio
    async def test_set_time_prog(self, hthp: AioHtHeatpump):
        pass  # TODO
예제 #7
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)
예제 #8
0
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),
            # -- should raise a 'TypeError':
            (123, HtDataTypes.BOOL, None, False),
            (123, HtDataTypes.INT, None, False),
            (123, HtDataTypes.FLOAT, None, False),
            (123.123, HtDataTypes.BOOL, None, False),
            (123.123, HtDataTypes.INT, None, False),
            (123.123, HtDataTypes.FLOAT, None, False),
            (True, HtDataTypes.BOOL, None, False),
            (True, HtDataTypes.INT, None, False),
            (True, HtDataTypes.FLOAT, None, False),
            (False, HtDataTypes.BOOL, None, False),
            (False, HtDataTypes.INT, None, False),
            (False, HtDataTypes.FLOAT, None, False),
            (None, HtDataTypes.BOOL, None, False),
            (None, HtDataTypes.INT, None, False),
            (None, HtDataTypes.FLOAT, None, False),
            # ...
        ],
    )
    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((TypeError, ValueError)):
                HtParam.from_str(s, data_type, strict)
        else:
            assert HtParam.from_str(s, data_type, strict) == exp_value
        # assert 0

    @pytest.mark.parametrize("data_type",
                             [None, "", 123, 123.123, True, False])
    def test_from_str_static_assert(self, data_type):
        with pytest.raises(AssertionError):
            HtParam.from_str("", data_type)
        # 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"),
            # -- should raise a 'TypeError':
            (None, HtDataTypes.BOOL, None),
            ("abc", HtDataTypes.BOOL, None),
            (123, HtDataTypes.BOOL, None),
            (123.123, HtDataTypes.BOOL, None),
            (None, HtDataTypes.INT, None),
            ("abc", HtDataTypes.INT, None),
            (123.123, HtDataTypes.INT, None),
            (None, HtDataTypes.FLOAT, None),
            ("abc", HtDataTypes.FLOAT, None),
            # ...
        ],
    )
    def test_to_str_static(self, val: HtParamValueType, data_type: HtDataTypes,
                           exp_str: str):
        if exp_str is None:
            with pytest.raises(TypeError):
                HtParam.to_str(val, data_type)
        else:
            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(
        "data_type, min_val, max_val",
        [
            # -- should raise a 'TypeError':
            (HtDataTypes.BOOL, False, 123),
            (HtDataTypes.BOOL, False, 123.123),
            (HtDataTypes.BOOL, 123, True),
            (HtDataTypes.BOOL, 123.123, True),
            (HtDataTypes.BOOL, False, ""),
            (HtDataTypes.BOOL, "", True),
            (HtDataTypes.BOOL, "", ""),
            (HtDataTypes.BOOL, None, 123),
            (HtDataTypes.BOOL, 123, None),
            (HtDataTypes.INT, 123, 123.123),
            (HtDataTypes.INT, 122.123, 123),
            (HtDataTypes.INT, 122.123, 123.123),
            (HtDataTypes.INT, 123, ""),
            (HtDataTypes.INT, "", 123),
            (HtDataTypes.INT, "", ""),
            (HtDataTypes.INT, 123, True),
            (HtDataTypes.INT, False, 123),
            (HtDataTypes.INT, True, False),
            (HtDataTypes.INT, False, True),
            (HtDataTypes.INT, None, 123.123),
            (HtDataTypes.INT, 123.123, None),
            (HtDataTypes.FLOAT, 123, ""),
            (HtDataTypes.FLOAT, "", 123),
            (HtDataTypes.FLOAT, 123.123, ""),
            (HtDataTypes.FLOAT, "", 123.123),
            (HtDataTypes.FLOAT, "", ""),
            (HtDataTypes.FLOAT, 123, True),
            (HtDataTypes.FLOAT, False, 123),
            (HtDataTypes.FLOAT, 123.123, True),
            (HtDataTypes.FLOAT, False, 123.123),
            (HtDataTypes.FLOAT, True, False),
            (HtDataTypes.FLOAT, False, True),
            (HtDataTypes.FLOAT, None, True),
            (HtDataTypes.FLOAT, True, None),
            # ...
        ],
    )
    def test_set_limits_raises_TypeError(self, data_type, min_val, max_val):
        param = HtParam("MP", 123, "r-", data_type)
        with pytest.raises(TypeError):
            param.set_limits(min_val, 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)
        # assert 0

    @pytest.mark.parametrize(
        "val, data_type",
        [
            (False, HtDataTypes.BOOL),
            (True, HtDataTypes.BOOL),
            (123, HtDataTypes.INT),
            (-321, HtDataTypes.INT),
            (123.456, HtDataTypes.FLOAT),
            (-321.456, HtDataTypes.FLOAT),
            (789, HtDataTypes.FLOAT),
            (-789, HtDataTypes.FLOAT),
            (789.0, HtDataTypes.FLOAT),
            (-789.0, HtDataTypes.FLOAT),
            # ...
        ],
    )
    def test_check_value_type(self, val, data_type):
        HtParam.check_value_type(val, data_type)
        # assert 0

    @pytest.mark.parametrize(
        "val, data_type",
        [
            # -- should raise a 'TypeError':
            (None, HtDataTypes.BOOL),
            ("abc", HtDataTypes.BOOL),
            (123, HtDataTypes.BOOL),
            (123.123, HtDataTypes.BOOL),
            (None, HtDataTypes.INT),
            ("abc", HtDataTypes.INT),
            (123.123, HtDataTypes.INT),
            (None, HtDataTypes.FLOAT),
            ("abc", HtDataTypes.FLOAT),
            # ...
        ],
    )
    def test_check_value_type_raises_TypeError(self, val, data_type):
        with pytest.raises(TypeError):
            HtParam.check_value_type(val, data_type)
예제 #9
0
 def test_dump(self):
     assert HtParams.dump() is None
예제 #10
0
 def test_get(self, name: str, param: HtParam):
     assert HtParams.get(name) == param
예제 #11
0
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

    def test_definition_file(self):
        assert HtParams.definition_file == path.normpath(
            path.join(path.dirname(path.abspath(__file__)), "../htheatpump/",
                      "htparams.csv"))
        # assert 0

    @pytest.mark.run_if_connected
    @pytest.mark.usefixtures("reconnect")
    @pytest.mark.parametrize("name, param", HtParams.items())
    @pytest.mark.asyncio
    async def test_validate_param(self, hthp: AioHtHeatpump, name: str,
                                  param: HtParam):
        await hthp.send_request_async(param.cmd())
        resp = await hthp.read_response_async()
        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)