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
 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
def hthp(cmdopt_device: str, cmdopt_baudrate: int):
    hthp = AioHtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
    try:
        hthp.open_connection()
        yield hthp  # provide the heat pump instance
    finally:
        hthp.close_connection()
 def test_open_connection(self, hthp: AioHtHeatpump):
     assert hthp.is_open
     with pytest.raises(IOError):
         hthp.open_connection()
async def reconnect(hthp: AioHtHeatpump):
    hthp.reconnect()
    await hthp.login_async()
    yield
    await hthp.logout_async()
async def main():
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s %(levelname)s [%(name)s|%(funcName)s]: %(message)s",
    )

    hp = AioHtHeatpump("/dev/ttyUSB0", baudrate=115200)
    hp.open_connection()
    await hp.login_async()

    rid = await hp.get_serial_number_async()
    print("connected successfully to heat pump with serial number {:d}".format(rid))
    ver = await hp.get_version_async()
    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 = await hp.query_async(*names)
        t_query += timer() - start
        start = timer()
        values = await hp.fast_query_async(*names)
        t_fast_query += timer() - start
    i += 1
    t_query = t_query / i
    t_fast_query = t_fast_query / i

    print("\n" + "-" * 100)
    print(
        "AioHtHeatpump.query({:d})      execution time: {:.3f} sec".format(
            len(names), t_query
        )
    )
    print(
        "AioHtHeatpump.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(sorted(names), random.randint(0, len(names)))
        print("{!s}".format(rand_names))
        # fast query for the given parameter(s)
        values = await hp.fast_query_async(*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 ...")

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

    sys.exit(0)
 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)
 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)
 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()
Beispiel #10
0
async def main_async():
    parser = argparse.ArgumentParser(
        description=textwrap.dedent(
            """\
            Heliotherm heat pump KNX gateway, v{}.

              https://github.com/dstrigl/htknx
            """.format(
                __version__
            )
        ),
        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(
        "config_file",
        default=os.path.normpath(os.path.join(os.path.dirname(__file__), "htknx.yaml")),
        type=str,
        nargs="?",
        help="the filename under which the gateway settings can be found, default: %(default)s",
    )
    parser.add_argument(
        "--logging-config",
        default=os.path.normpath(
            os.path.join(os.path.dirname(__file__), "logging.conf")
        ),
        type=str,
        help="the filename under which the logging configuration can be found, default: %(default)s",
    )

    # parse the passed arguments
    args = parser.parse_args()
    print(args)

    try:
        # load logging config from file
        logging.config.fileConfig(args.logging_config, disable_existing_loggers=False)
    except Exception as ex:
        _LOGGER.error(
            "Failed to read logging config file '%s': %s", args.config_file, ex
        )
        sys.exit(1)

    try:
        # load the settings from the config file
        config = Config()
        _LOGGER.info("Load settings from '%s'.", args.config_file)
        config.read(args.config_file)
        _LOGGER.debug("Config: %s", config.__dict__)
    except Exception as ex:
        _LOGGER.error(
            "Failed to read gateway config file '%s': %s", args.config_file, ex
        )
        sys.exit(1)

    _LOGGER.info("Start Heliotherm heat pump KNX gateway v%s.", __version__)
    try:
        # create objects to establish connection to the heat pump and the KNX bus
        hthp = AioHtHeatpump(**config.heat_pump)
        xknx = XKNX(**config.knx)

        group_addresses: Dict[str, str] = {}

        # create data points
        data_points: Dict[str, HtDataPoint] = {}
        for dp_name, dp_conf in config.data_points.items():
            data_points[dp_name] = HtDataPoint.from_config(xknx, hthp, dp_name, dp_conf)
            _LOGGER.debug("DP: %s", data_points[dp_name])
            ga = str(data_points[dp_name].group_address)
            if ga in group_addresses:
                raise RuntimeError(
                    "Multiple use of the same KNX group address"
                    f" {ga!r} ({group_addresses[ga]!r} and {dp_name!r})"
                )
            group_addresses[ga] = dp_name

        # create notifications
        notifications: Dict[str, Type[Notification]] = {}
        for notif_name, notif_conf in config.notifications.items():
            if notif_name == "on_malfunction":
                notifications[notif_name] = HtFaultNotification.from_config(
                    xknx, hthp, notif_name, notif_conf
                )
                _LOGGER.debug("NOTIF: %s", notifications[notif_name])
            else:
                _LOGGER.warning("Invalid notification '%s'", notif_name)
                # assert 0, "Invalid notification"
            ga = str(notifications[notif_name].group_address)
            if ga in group_addresses:
                raise RuntimeError(
                    "Multiple use of the same KNX group address"
                    f" {ga!r} ({group_addresses[ga]!r} and {notif_name!r})"
                )
            group_addresses[ga] = notif_name

        # open the connection to the Heliotherm heat pump and login
        hthp.open_connection()
        await hthp.login_async()
        rid = await hthp.get_serial_number_async()
        _LOGGER.info("Connected successfully to heat pump with serial number %d.", rid)
        ver = await hthp.get_version_async()
        _LOGGER.info("Software version = %s (%d)", *ver)

        # start the KNX module which connects to the KNX/IP gateway
        await xknx.start()

        # create and start the publisher
        with HtPublisher(hthp, data_points, notifications, **config.general):
            # Wait until Ctrl-C was pressed
            await xknx.loop_until_sigint()

        # stop the KNX module
        await xknx.stop()

    except Exception as ex:
        _LOGGER.error("Failed to start Heliotherm heat pump KNX gateway: %s", ex)
        sys.exit(1)
    finally:
        await hthp.logout_async()  # try to logout for an ordinary cancellation (if possible)
        hthp.close_connection()

    sys.exit(0)
Beispiel #11
0
async def main_async():
    parser = argparse.ArgumentParser(
        description=textwrap.dedent("""\
            Command line tool to create a backup of the Heliotherm heat pump data points.

            Example:

              $ python3 htbackup_async.py --baudrate 9600 --csv backup.csv
              'SP,NR=0' [Language]: VAL='0', MIN='0', MAX='4'
              'SP,NR=1' [TBF_BIT]: VAL='0', MIN='0', MAX='1'
              'SP,NR=2' [Rueckruferlaubnis]: VAL='1', MIN='0', MAX='1'
              ...
            """),
        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",
                        type=str,
                        help="write the result to the specified JSON file")

    parser.add_argument("-c",
                        "--csv",
                        type=str,
                        help="write the result to the specified CSV file")

    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(
        "--without-values",
        action="store_true",
        help=
        "store heat pump data points without their current value (keep it blank)",
    )

    parser.add_argument(
        "--max-retries",
        default=2,
        type=int,
        choices=range(11),
        help=
        "maximum number of retries for a data point request (0..10), default: %(default)s",
    )

    args = parser.parse_args()

    # activate logging with level DEBUG in verbose mode
    log_format = "%(asctime)s %(levelname)s [%(name)s|%(funcName)s]: %(message)s"
    if args.verbose:
        logging.basicConfig(level=logging.DEBUG, format=log_format)
    else:
        logging.basicConfig(level=logging.WARNING, format=log_format)

    hp = AioHtHeatpump(args.device, baudrate=args.baudrate)
    try:
        hp.open_connection()
        await hp.login_async()

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

        result = {}
        with Timer() as timer:
            for dp_type in ("SP", "MP"):  # for all known data point types
                result.update({dp_type: {}})
                i = 0  # start at zero for each data point type
                while True:
                    success = False
                    retry = 0
                    while not success and retry <= args.max_retries:
                        data_point = "{},NR={:d}".format(dp_type, i)
                        # send request for data point to the heat pump
                        await hp.send_request_async(data_point)
                        # ... and wait for the response
                        try:
                            resp = await hp.read_response_async()
                            # search for pattern "NAME=...", "VAL=...", "MAX=..." and "MIN=..." inside the answer
                            m = re.match(
                                r"^{},.*NAME=([^,]+).*VAL=([^,]+).*MAX=([^,]+).*MIN=([^,]+).*$"
                                .format(data_point),
                                resp,
                            )
                            if not m:
                                raise IOError(
                                    "invalid response for query of data point {!r} [{}]"
                                    .format(data_point, resp))
                            # extract name, value, min and max
                            name, value, min_val, max_val = (
                                g.strip() for g in m.group(1, 2, 4, 3))
                            if args.without_values:
                                value = ""  # keep it blank (if desired)
                            print("{!r} [{}]: VAL={!r}, MIN={!r}, MAX={!r}".
                                  format(data_point, name, value, min_val,
                                         max_val))
                            # store the determined data in the result dict
                            result[dp_type].update({
                                i: {
                                    "name": name,
                                    "value": value,
                                    "min": min_val,
                                    "max": max_val,
                                }
                            })
                            success = True
                        except Exception as e:
                            retry += 1
                            _LOGGER.warning(
                                "try #%d/%d for query of data point '%s' failed: %s",
                                retry,
                                args.max_retries + 1,
                                data_point,
                                e,
                            )
                            # try a reconnect, maybe this will help
                            hp.reconnect()  # perform a reconnect
                            try:
                                await hp.login_async(max_retries=0
                                                     )  # ... and a new login
                            except Exception:
                                pass  # ignore a potential problem
                    if not success:
                        _LOGGER.error(
                            "query of data point '%s' failed after %s try/tries",
                            data_point,
                            retry,
                        )
                        break
                    else:
                        i += 1
        exec_time = timer.elapsed

        if args.json:  # write result to JSON file
            with open(args.json, "w") as jsonfile:
                json.dump(result, jsonfile, indent=4, sort_keys=True)

        if args.csv:  # write result to CSV file
            with open(args.csv, "w") as csvfile:
                fieldnames = ["type", "number", "name", "value", "min", "max"]
                writer = csv.DictWriter(csvfile,
                                        delimiter=",",
                                        fieldnames=fieldnames)
                writer.writeheader()
                for dp_type, content in sorted(result.items(), reverse=True):
                    for i, data in content.items():
                        row_data = {"type": dp_type, "number": i}
                        row_data.update(data)
                        writer.writerow({n: row_data[n] for n in fieldnames})

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

    except Exception as ex:
        _LOGGER.exception(ex)
        sys.exit(1)
    finally:
        await hp.logout_async(
        )  # try to logout for an ordinary cancellation (if possible)
        hp.close_connection()

    sys.exit(0)
Beispiel #12
0
async def main_async():
    parser = argparse.ArgumentParser(
        description=textwrap.dedent(
            """\
            Command line tool to query for the time programs of the heat pump.

            Example:

              $ python3 httimeprog_async.py --device /dev/ttyUSB1 --baudrate 9600
              idx=0, name='Warmwasser', ead=7, nos=2, ste=15, nod=7, entries=[]
              idx=1, name='Zirkulationspumpe', ead=7, nos=2, ste=15, nod=7, entries=[]
              idx=2, name='Heizung', ead=7, nos=3, ste=15, nod=7, entries=[]
              idx=3, name='Mischer 1', ead=7, nos=3, ste=15, nod=7, entries=[]
              idx=4, name='Mischer 2', ead=7, nos=3, ste=15, nod=7, entries=[]
            """
        ),
        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(
        "-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(
        "-j",
        "--json",
        type=str,
        help="write the time program entries to the specified JSON file",
    )

    parser.add_argument(
        "-c",
        "--csv",
        type=str,
        help="write the time program entries to the specified CSV file",
    )

    parser.add_argument(
        "index",
        type=int,
        nargs="?",
        help="time program index to query for (omit to get the list of available time programs of the heat pump)",
    )

    parser.add_argument(
        "day",
        type=int,
        nargs="?",
        help="number of day of a specific time program to query for",
    )

    parser.add_argument(
        "entry",
        type=int,
        nargs="?",
        help="number of entry of a specific day of a time program to query for",
    )

    args = parser.parse_args()

    # activate logging with level DEBUG in verbose mode
    log_format = "%(asctime)s %(levelname)s [%(name)s|%(funcName)s]: %(message)s"
    if args.verbose:
        logging.basicConfig(level=logging.DEBUG, format=log_format)
    else:
        logging.basicConfig(level=logging.WARNING, format=log_format)

    hp = AioHtHeatpump(args.device, baudrate=args.baudrate)
    try:
        hp.open_connection()
        await hp.login_async()

        rid = await hp.get_serial_number_async()
        if args.verbose:
            _LOGGER.info(
                "connected successfully to heat pump with serial number %d", rid
            )
        ver = await hp.get_version_async()
        if args.verbose:
            _LOGGER.info("software version = %s (%d)", *ver)

        if args.index is not None and args.day is not None and args.entry is not None:
            # query for a specific time program entry of the heat pump
            with Timer() as timer:
                time_prog_entry = await hp.get_time_prog_entry_async(
                    args.index, args.day, args.entry
                )
            exec_time = timer.elapsed
            print(
                "[idx={:d}, day={:d}, entry={:d}]: {!s}".format(
                    args.index, args.day, args.entry, time_prog_entry
                )
            )

            # write time program entry to JSON file
            if args.json:
                with open(args.json, "w") as jsonfile:
                    json.dump(
                        time_prog_entry.as_json(), jsonfile, indent=4, sort_keys=True
                    )
            # write time program entry to CSV file
            if args.csv:
                with open(args.csv, "w") as csvfile:
                    csvfile.write(
                        "# idx={:d}, day={:d}, entry={:d}".format(
                            args.index, args.day, args.entry
                        )
                    )
                    fieldnames = ["state", "start", "end"]
                    writer = csv.DictWriter(
                        csvfile, delimiter=",", fieldnames=fieldnames
                    )
                    writer.writeheader()
                    writer.writerow(time_prog_entry.as_json())

        elif args.index is not None and args.day is not None:
            # query for the entries of a specific day of a time program of the heat pump
            with Timer() as timer:
                time_prog = await hp.get_time_prog_async(args.index, with_entries=True)
            exec_time = timer.elapsed
            print("[idx={:d}]: {!s}".format(args.index, time_prog))
            day_entries = time_prog.entries_of_day(args.day)
            for num in range(len(day_entries)):
                print(
                    "[day={:d}, entry={:d}]: {!s}".format(
                        args.day, num, day_entries[num]
                    )
                )

            # write time program entries of the specified day to JSON file
            if args.json:
                with open(args.json, "w") as jsonfile:
                    json.dump(
                        [entry.as_json() for entry in day_entries],
                        jsonfile,
                        indent=4,
                        sort_keys=True,
                    )
            # write time program entries of the specified day to CSV file
            if args.csv:
                with open(args.csv, "w") as csvfile:
                    csvfile.write("# {!s}\n".format(time_prog))
                    fieldnames = ["day", "entry", "state", "start", "end"]
                    writer = csv.DictWriter(
                        csvfile, delimiter=",", fieldnames=fieldnames
                    )
                    writer.writeheader()
                    for num in range(len(day_entries)):
                        row = {"day": args.day, "entry": num}
                        row.update(day_entries[num].as_json())
                        writer.writerow(row)

        elif args.index is not None:
            # query for the entries of a specific time program of the heat pump
            with Timer() as timer:
                time_prog = await hp.get_time_prog_async(args.index, with_entries=True)
            exec_time = timer.elapsed
            print("[idx={:d}]: {!s}".format(args.index, time_prog))
            for (day, num) in [
                (day, num)
                for day in range(time_prog.number_of_days)
                for num in range(time_prog.entries_a_day)
            ]:
                entry = time_prog.entry(day, num)
                print("[day={:d}, entry={:d}]: {!s}".format(day, num, entry))

            # write time program entries to JSON file
            if args.json:
                with open(args.json, "w") as jsonfile:
                    json.dump(time_prog.as_json(), jsonfile, indent=4, sort_keys=True)
            # write time program entries to CSV file
            if args.csv:
                with open(args.csv, "w") as csvfile:
                    csvfile.write("# {!s}\n".format(time_prog))
                    fieldnames = ["day", "entry", "state", "start", "end"]
                    writer = csv.DictWriter(
                        csvfile, delimiter=",", fieldnames=fieldnames
                    )
                    writer.writeheader()
                    for (day, num) in [
                        (day, num)
                        for day in range(time_prog.number_of_days)
                        for num in range(time_prog.entries_a_day)
                    ]:
                        row = {"day": day, "entry": num}
                        row.update(time_prog.entry(day, num).as_json())
                        writer.writerow(row)

        else:
            # query for all available time programs of the heat pump
            with Timer() as timer:
                time_progs = await hp.get_time_progs_async()
            exec_time = timer.elapsed
            for time_prog in time_progs:
                print("{!s}".format(time_prog))

            keys = ["index", "name", "ead", "nos", "ste", "nod"]
            data = []
            for time_prog in time_progs:
                data.append(time_prog.as_json(with_entries=False))
            # write time programs to JSON file
            if args.json:
                with open(args.json, "w") as jsonfile:
                    json.dump(data, jsonfile, indent=4, sort_keys=True)
            # write time programs to CSV file
            if args.csv:
                with open(args.csv, "w") as csvfile:
                    writer = csv.DictWriter(csvfile, delimiter=",", fieldnames=keys)
                    writer.writeheader()
                    writer.writerows(data)

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

    except Exception as ex:
        _LOGGER.exception(ex)
        sys.exit(1)
    finally:
        await hp.logout_async()  # try to logout for an ordinary cancellation (if possible)
        hp.close_connection()

    sys.exit(0)
Beispiel #13
0
async def main_async():
    parser = argparse.ArgumentParser(
        description=textwrap.dedent("""\
            Command line tool to query for parameters of the Heliotherm heat pump the fast way.
            Note: Only parameters representing a "MP" data point are supported!

            Example:

              $ python3 htfastquery_async.py --device /dev/ttyUSB1 "Temp. Vorlauf" "Temp. Ruecklauf"
              Temp. Ruecklauf [MP,04]: 25.2
              Temp. Vorlauf   [MP,03]: 25.3
            """),
        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(
        "--bool-as-int",
        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 representing a MP data point",
    )

    args = parser.parse_args()

    # activate logging with level DEBUG in verbose mode
    log_format = "%(asctime)s %(levelname)s [%(name)s|%(funcName)s]: %(message)s"
    if args.verbose:
        logging.basicConfig(level=logging.DEBUG, format=log_format)
    else:
        logging.basicConfig(level=logging.WARNING, format=log_format)

    hp = AioHtHeatpump(args.device, baudrate=args.baudrate)
    try:
        hp.open_connection()
        await hp.login_async()

        rid = await hp.get_serial_number_async()
        if args.verbose:
            _LOGGER.info(
                "connected successfully to heat pump with serial number %d",
                rid)
        ver = await hp.get_version_async()
        if args.verbose:
            _LOGGER.info("software version = %s (%d)", *ver)

        # fast query for the given parameter(s)
        with Timer() as timer:
            values = await hp.fast_query_async(*args.name)
        exec_time = timer.elapsed
        for name, val in values.items():
            if args.bool_as_int and HtParams[
                    name].data_type == HtDataTypes.BOOL:
                values[name] = 1 if val else 0

        # 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(values) > 1:
                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)),
                    ))
            elif len(values) == 1:
                print(next(iter(values.values())))

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

    except Exception as ex:
        _LOGGER.exception(ex)
        sys.exit(1)
    finally:
        await hp.logout_async(
        )  # try to logout for an ordinary cancellation (if possible)
        hp.close_connection()

    sys.exit(0)
async def main_async():
    parser = argparse.ArgumentParser(
        description=textwrap.dedent(
            """\
            Command line tool to create a complete list of all Heliotherm heat pump parameters.

            Example:

              $ python3 htcomplparams_async.py --device /dev/ttyUSB1 --baudrate 9600 --csv
              connected successfully to heat pump with serial number 123456
              software version = 3.0.20 (273)
              'SP,NR=0' [Language]: VAL=0, MIN=0, MAX=4 (dtype=INT)
              'SP,NR=1' [TBF_BIT]: VAL=0, MIN=0, MAX=1 (dtype=BOOL)
              'SP,NR=2' [Rueckruferlaubnis]: VAL=1, MIN=0, MAX=1 (dtype=BOOL)
              ...
              write data to: /home/pi/prog/htheatpump/htparams-123456-3_0_20-273.csv
            """
        ),
        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(
        "-c",
        "--csv",
        type=str,
        help="write the result to the specified CSV file",
        nargs="?",
        const="",
    )

    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(
        "--max-retries",
        default=2,
        type=int,
        choices=range(11),
        help="maximum number of retries for a data point request (0..10), default: %(default)s",
    )

    args = parser.parse_args()

    # activate logging with level DEBUG in verbose mode
    log_format = "%(asctime)s %(levelname)s [%(name)s|%(funcName)s]: %(message)s"
    if args.verbose:
        logging.basicConfig(level=logging.DEBUG, format=log_format)
    else:
        logging.basicConfig(level=logging.WARNING, format=log_format)

    hp = AioHtHeatpump(args.device, baudrate=args.baudrate)
    try:
        hp.open_connection()
        await hp.login_async()

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

        result = {}
        with Timer() as timer:
            for dp_type in ("SP", "MP"):  # for all known data point types
                result.update({dp_type: {}})
                i = 0  # start at zero for each data point type
                while True:
                    success = False
                    retry = 0
                    while not success and retry <= args.max_retries:
                        data_point = "{},NR={:d}".format(dp_type, i)
                        # send request for data point to the heat pump
                        await hp.send_request_async(data_point)
                        # ... and wait for the response
                        try:
                            resp = await hp.read_response_async()
                            # search for pattern "NAME=...", "VAL=...", "MAX=..." and "MIN=..." inside the answer
                            m = re.match(
                                r"^{},.*NAME=([^,]+).*VAL=([^,]+).*MAX=([^,]+).*MIN=([^,]+).*$".format(
                                    data_point
                                ),
                                resp,
                            )
                            if not m:
                                raise IOError(
                                    "invalid response for query of data point {!r} [{}]".format(
                                        data_point, resp
                                    )
                                )
                            # extract name, value, min and max
                            name, value, min_val, max_val = (
                                g.strip() for g in m.group(1, 2, 4, 3)
                            )
                            # determine the data type of the data point
                            dtype = None
                            try:
                                min_val = HtParam.from_str(min_val, HtDataTypes.INT)
                                max_val = HtParam.from_str(max_val, HtDataTypes.INT)
                                value = HtParam.from_str(value, HtDataTypes.INT)
                                dtype = "INT"
                                if min_val == 0 and max_val == 1 and value in (0, 1):
                                    dtype = "BOOL"
                            except ValueError:
                                min_val = HtParam.from_str(
                                    min_val, HtDataTypes.FLOAT, strict=False
                                )
                                max_val = HtParam.from_str(
                                    max_val, HtDataTypes.FLOAT, strict=False
                                )
                                value = HtParam.from_str(
                                    value, HtDataTypes.FLOAT, strict=False
                                )
                                dtype = "FLOAT"
                            assert dtype is not None
                            # print the determined values
                            print(
                                "{!r} [{}]: VAL={}, MIN={}, MAX={} (dtype={})".format(
                                    data_point, name, value, min_val, max_val, dtype
                                )
                            )
                            # store the determined data in the result dict
                            result[dp_type].update(
                                {
                                    i: {
                                        "name": name,
                                        "value": value,
                                        "min": min_val,
                                        "max": max_val,
                                        "dtype": dtype,
                                    }
                                }
                            )
                            success = True
                        except Exception as e:
                            retry += 1
                            _LOGGER.warning(
                                "try #%d/%d for query of data point '%s' failed: %s",
                                retry,
                                args.max_retries + 1,
                                data_point,
                                e,
                            )
                            # try a reconnect, maybe this will help
                            hp.reconnect()  # perform a reconnect
                            try:
                                await hp.login_async(
                                    max_retries=0
                                )  # ... and a new login
                            except Exception:
                                pass  # ignore a potential problem
                    if not success:
                        _LOGGER.error(
                            "query of data point '%s' failed after %d try/tries",
                            data_point,
                            retry,
                        )
                        break
                    else:
                        i += 1
        exec_time = timer.elapsed

        if args.csv is not None:  # write result to CSV file
            filename = args.csv.strip()
            if filename == "":
                filename = os.path.join(
                    os.getcwd(),
                    "htparams-{}-{}-{}.csv".format(
                        rid, ver[0].replace(".", "_"), ver[1]
                    ),
                )
            print("write data to: " + filename)
            with open(filename, "w") as csvfile:
                header = (
                    "# name",
                    "data point type (MP;SP)",
                    "data point number",
                    "acl (r-;-w;rw)",
                    "dtype (BOOL;INT;FLOAT)",
                    "min",
                    "max",
                )
                writer = csv.writer(csvfile, delimiter=",")
                writer.writerow(header)
                for dp_type, content in sorted(result.items()):
                    for i, data in content.items():
                        row_data = (
                            data["name"],
                            dp_type,
                            str(i),
                            "r-",
                            data["dtype"],
                            str(data["min"]),
                            str(data["max"]),
                        )
                        writer.writerow(row_data)

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

    except Exception as ex:
        _LOGGER.exception(ex)
        sys.exit(1)
    finally:
        await hp.logout_async()  # try to logout for an ordinary cancellation (if possible)
        hp.close_connection()

    sys.exit(0)
Beispiel #15
0
async def main_async():
    parser = argparse.ArgumentParser(
        description=textwrap.dedent("""\
            Command line tool to set the value of a specific parameter of the heat pump.

            Example:

              $ python3 htset_async.py --device /dev/ttyUSB1 "HKR Soll_Raum" "21.5"
              21.5
            """),
        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("-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=1,
        action=ParamNameAction,
        help="parameter name (as defined in htparams.csv)",
    )

    parser.add_argument("value",
                        type=str,
                        nargs=1,
                        help="parameter value (as string)")

    args = parser.parse_args()

    # activate logging with level DEBUG in verbose mode
    log_format = "%(asctime)s %(levelname)s [%(name)s|%(funcName)s]: %(message)s"
    if args.verbose:
        logging.basicConfig(level=logging.DEBUG, format=log_format)
    else:
        logging.basicConfig(level=logging.WARNING, format=log_format)

    hp = AioHtHeatpump(args.device, baudrate=args.baudrate)
    try:
        hp.open_connection()
        await hp.login_async()

        rid = await hp.get_serial_number_async()
        if args.verbose:
            _LOGGER.info(
                "connected successfully to heat pump with serial number %d",
                rid)
        ver = await hp.get_version_async()
        if args.verbose:
            _LOGGER.info("software version = %s (%d)", *ver)

        # convert the passed value (as string) to the specific data type
        value = HtParams[args.name[0]].from_str(args.value[0])
        # set the parameter of the heat pump to the passed value
        with Timer() as timer:
            value = await hp.set_param_async(args.name[0], value, True)
        exec_time = timer.elapsed
        print(value)

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

    except Exception as ex:
        _LOGGER.exception(ex)
        sys.exit(1)
    finally:
        await hp.logout_async(
        )  # try to logout for an ordinary cancellation (if possible)
        hp.close_connection()

    sys.exit(0)
 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")
Beispiel #17
0
async def main_async():
    parser = argparse.ArgumentParser(
        description=textwrap.dedent(
            """\
            Command line tool to get and set date and time on the Heliotherm heat pump.

            To change date and/or time on the heat pump the date and time has to be passed
            in ISO 8601 format (YYYY-MM-DDTHH:MM:SS) to the program. It is also possible to
            pass an empty string, therefore the current date and time of the host will be
            used. If nothing is passed to the program the current date and time on the heat
            pump will be returned.

            Example:

              $ python3 htdatetime_async.py --device /dev/ttyUSB1 --baudrate 9600
              Tuesday, 2017-11-21T21:48:04
              $ python3 htdatetime_async.py -d /dev/ttyUSB1 -b 9600 "2008-09-03T20:56:35"
              Wednesday, 2008-09-03T20:56:35
            """
        ),
        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(
        "-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(
        "datetime",
        type=str,
        nargs="?",
        help="date and time in ISO 8601 format (YYYY-MM-DDTHH:MM:SS), if empty current date and time will be used, "
        "if not specified current date and time on the heat pump will be returned",
    )

    args = parser.parse_args()

    # activate logging with level DEBUG in verbose mode
    log_format = "%(asctime)s %(levelname)s [%(name)s|%(funcName)s]: %(message)s"
    if args.verbose:
        logging.basicConfig(level=logging.DEBUG, format=log_format)
    else:
        logging.basicConfig(level=logging.WARNING, format=log_format)

    hp = AioHtHeatpump(args.device, baudrate=args.baudrate)
    try:
        hp.open_connection()
        await hp.login_async()

        rid = await hp.get_serial_number_async()
        if args.verbose:
            _LOGGER.info(
                "connected successfully to heat pump with serial number %d", rid
            )
        ver = await hp.get_version_async()
        if args.verbose:
            _LOGGER.info("software version = %s (%d)", *ver)

        if args.datetime is None:
            # get current date and time on the heat pump
            with Timer() as timer:
                dt, wd = await hp.get_date_time_async()
            exec_time = timer.elapsed
            print("{}, {}".format(WEEKDAYS[wd - 1], dt.isoformat()))
        else:
            # set current date and time on the heat pump
            if not args.datetime:
                # no date and time given, so use the current date and time on the host
                dt = datetime.datetime.now()
            else:
                # otherwise translate the given string to a valid datetime object
                dt = datetime.datetime.strptime(args.datetime, "%Y-%m-%dT%H:%M:%S")
            with Timer() as timer:
                dt, wd = await hp.set_date_time_async(dt)
            exec_time = timer.elapsed
            print("{}, {}".format(WEEKDAYS[wd - 1], dt.isoformat()))

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

    except Exception as ex:
        _LOGGER.exception(ex)
        sys.exit(1)
    finally:
        await hp.logout_async()  # try to logout for an ordinary cancellation (if possible)
        hp.close_connection()

    sys.exit(0)
 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
def test_AioHtHeatpump_init_del(cmdopt_device: str, cmdopt_baudrate: int):
    hp = AioHtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate)
    assert not hp.is_open
    hp.open_connection()
    assert hp.is_open
    del hp  # AioHtHeatpump.__del__ should be executed here!
 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")
def test_AioHtHeatpump_enter_exit(cmdopt_device: str, cmdopt_baudrate: int):
    with AioHtHeatpump(device=cmdopt_device, baudrate=cmdopt_baudrate) as hp:
        assert hp is not None
        assert hp.is_open
    assert hp is not None
    assert not hp.is_open
Beispiel #22
0
async def main_async():
    parser = argparse.ArgumentParser(
        description=textwrap.dedent("""\
            Command line tool to query for the fault list of the heat pump.

            Example:

              $ python3 htfaultlist_async.py --device /dev/ttyUSB1
              #000 [2000-01-01T00:00:00]: 65534, Keine Stoerung
              #001 [2000-01-01T00:00:00]: 65286, Info: Programmupdate 1
              #002 [2000-01-01T00:00:00]: 65285, Info: Initialisiert
              #003 [2000-01-01T00:00:16]: 00009, HD Schalter
              #004 [2000-01-01T00:00:20]: 00021, EQ Motorschutz
            """),
        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("-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(
        "-l",
        "--last",
        action="store_true",
        help="print only the last fault message of the heat pump",
    )

    parser.add_argument("-j",
                        "--json",
                        type=str,
                        help="write the fault list to the specified JSON file")

    parser.add_argument("-c",
                        "--csv",
                        type=str,
                        help="write the fault list to the specified CSV file")

    parser.add_argument("index",
                        type=int,
                        nargs="*",
                        help="fault list index/indices to query for")

    args = parser.parse_args()

    # activate logging with level DEBUG in verbose mode
    log_format = "%(asctime)s %(levelname)s [%(name)s|%(funcName)s]: %(message)s"
    if args.verbose:
        logging.basicConfig(level=logging.DEBUG, format=log_format)
    else:
        logging.basicConfig(level=logging.WARNING, format=log_format)

    hp = AioHtHeatpump(args.device, baudrate=args.baudrate)
    try:
        hp.open_connection()
        await hp.login_async()

        rid = await hp.get_serial_number_async()
        if args.verbose:
            _LOGGER.info(
                "connected successfully to heat pump with serial number %d",
                rid)
        ver = await hp.get_version_async()
        if args.verbose:
            _LOGGER.info("software version = %s (%d)", *ver)

        if args.last:
            # query for the last fault message of the heat pump
            with Timer() as timer:
                idx, err, dt, msg = await hp.get_last_fault_async()
            exec_time = timer.elapsed
            fault_list = [{
                "index": idx,  # fault list index
                "error": err,  # error code
                "datetime": dt.isoformat(),  # date and time of the entry
                "message": msg,  # error message
            }]
            print("#{:03d} [{}]: {:05d}, {}".format(idx, dt.isoformat(), err,
                                                    msg))
        else:
            # query for the given fault list entries of the heat pump
            with Timer() as timer:
                fault_list = await hp.get_fault_list_async(*args.index)
            exec_time = timer.elapsed
            for entry in fault_list:
                entry["datetime"] = entry["datetime"].isoformat(
                )  # convert "datetime" dict entry to str
                print("#{:03d} [{}]: {:05d}, {}".format(
                    entry["index"],
                    entry["datetime"],
                    entry["error"],
                    entry["message"],
                ))

        if args.json:  # write fault list entries to JSON file
            with open(args.json, "w") as jsonfile:
                json.dump(fault_list, jsonfile, indent=4, sort_keys=True)

        if args.csv:  # write fault list entries to CSV file
            with open(args.csv, "w") as csvfile:
                fieldnames = ["index", "datetime", "error", "message"]
                writer = csv.DictWriter(csvfile,
                                        delimiter=",",
                                        fieldnames=fieldnames)
                writer.writeheader()
                for entry in fault_list:
                    writer.writerow({n: entry[n] for n in fieldnames})

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

    except Exception as ex:
        _LOGGER.exception(ex)
        sys.exit(1)
    finally:
        await hp.logout_async(
        )  # try to logout for an ordinary cancellation (if possible)
        hp.close_connection()

    sys.exit(0)
Beispiel #23
0
async def main_async():
    parser = argparse.ArgumentParser(
        description=textwrap.dedent("""\
            Command shell tool to send raw commands to the Heliotherm heat pump.

            For commands which deliver more than one response from the heat pump
            the expected number of responses can be defined by the argument "-r"
            or "--responses".

            Example:

              $ python3 htshell_async.py --device /dev/ttyUSB1 "AR,28,29,30" -r 3
              > 'AR,28,29,30'
              < 'AA,28,19,14.09.14-02:08:56,EQ_Spreizung'
              < 'AA,29,20,14.09.14-11:52:08,EQ_Spreizung'
              < 'AA,30,65534,15.09.14-09:17:12,Keine Stoerung'
            """),
        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(
        "-r",
        "--responses",
        default=1,
        type=int,
        help=
        "number of expected responses for each given command, default: %(default)s",
    )

    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(
        "cmd",
        type=str,
        nargs="+",
        help=
        "command(s) to send to the heat pump (without the preceding '~' and the trailing ';')",
    )

    args = parser.parse_args()

    # activate logging with level DEBUG in verbose mode
    log_format = "%(asctime)s %(levelname)s [%(name)s|%(funcName)s]: %(message)s"
    if args.verbose:
        logging.basicConfig(level=logging.DEBUG, format=log_format)
    else:
        logging.basicConfig(level=logging.WARNING, format=log_format)

    hp = AioHtHeatpump(args.device, baudrate=args.baudrate)
    try:
        hp.open_connection()
        await hp.login_async()

        rid = await hp.get_serial_number_async()
        if args.verbose:
            _LOGGER.info(
                "connected successfully to heat pump with serial number %d",
                rid)
        ver = await hp.get_version_async()
        if args.verbose:
            _LOGGER.info("software version = %s (%d)", *ver)

        with Timer() as timer:
            for cmd in args.cmd:
                # write the given command to the heat pump
                print("> {!r}".format(cmd))
                await hp.send_request_async(cmd)
                # and read all expected responses for this command
                for _ in range(args.responses):
                    resp = await hp.read_response_async()
                    print("< {!r}".format(resp))
        exec_time = timer.elapsed

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

    except Exception as ex:
        _LOGGER.exception(ex)
        sys.exit(1)
    finally:
        await hp.logout_async(
        )  # try to logout for an ordinary cancellation (if possible)
        hp.close_connection()

    sys.exit(0)