Exemplo n.º 1
0
    def _set_range_cmd(self, cmd, arg):
        """Implements the 'set-current-range' and 'set-voltage-range' commands."""

        auto_range_cmd = cmd.replace("-range", "-auto-range")
        if arg == "auto":
            self._command(auto_range_cmd, "on")
            return

        self._command(auto_range_cmd, "off")
        choices = self.commands[cmd]["choices"]
        if arg == choices[1] or arg == choices[-1]:
            # The first and the last current/voltage range availability depends on the crest factor.
            what = cmd.split("-")[1]
            crest = self._command("get-crest-factor")
            crest_needed = None
            if crest == "3" and arg == choices[1]:
                crest_needed = "6"
            elif crest == "6" and arg == choices[-1]:
                crest_needed = "3"
            if crest_needed:
                raise Error(
                    "%s range %s is only available when crest factor is %s, but currently "
                    "it is %s" % (what, arg, crest_needed, crest))

        self._command(cmd, arg, func=False)
Exemplo n.º 2
0
def process_config(secname=None, args=None):
    """
    Load and process the configuration files. First the '/etc/yokotool.conf' file is processed, then
    the '$HOME/.yokotool.conf' file. The optional 'secname' argument specifies the section of the
    configuration files to process. If the argument is not provided, the "default" section is
    processed instead.

    Once the configuration files are process the (supposedly) command-line arguments 'args' are
    merged into the resulting configuration dictionary. In case of a conflict the command-line
    arguments win. The configuration dictionary is returned at the end.
    """

    config = {}
    paths = []
    for cfgfile in iteratate_configs():
        paths.append(cfgfile.path)
        _process_config_file(cfgfile, secname, config)

    if not config and paths and secname:
        raise Error("section '%s' was not found in any for these configuration files:\n* %s" \
                    % (secname, "\n* ".join(paths)))

    if args:
        for name in _CONFIG_OPTIONS:
            if hasattr(args, name) and getattr(args, name) is not None:
                config[name] = getattr(args, name)

    if _LOG.getEffectiveLevel() == logging.DEBUG:
        import pprint
        _LOG.debug("the final configuration:\n%s",
                   pprint.pformat(config, indent=4))

    return config
Exemplo n.º 3
0
    def __init__(self, transport):
        """The class constructor."""

        # Call the base class constructor first.
        super(WT310, self).__init__(transport)

        self.max_data_items = _MAX_DATA_ITEMS

        self._populate_data_items(_WT310_DATA_ITEMS, _DITT)
        self._add_wt310_commands()
        self._populate_choices(_CHOICES)
        self._populate_raw_commands(_RAW_COMMANDS)
        self._populate_tweaks(_TWEAKS)
        self._populate_arg_verify_funcs()
        self._populate_errors_map_map()

        self._init_pmeter()

        # Verify that we are dealing with a WT310.
        ids = self._command("get-id")
        if "WT310" not in ids:
            raise Error("'%s' is not a WT310 power meter" % transport.devnode)

        # Set data format to ascii.
        self._command("set-data-format", "ascii")
        # Enable WT310 commands
        self._command("set-compat-mode", "WT300")
        # Enable verbose mode which makes the power meter reply with full strings instead of cut
        # ones.
        self._command("set-verbose-mode", "on")
Exemplo n.º 4
0
    def __init__(self, transport=None, **kwargs):
        """
        The class constructor. The optional 'transport' argument specifies the transport object to
        use. If it is not provided, the rest of the arguments are used to create the transport. The
        allowed keys in 'kwargs' are the same as the configuration file options (e.g., 'devnode',
        etc).
        """

        # Validate kwargs.
        for kwarg in kwargs:
            if kwarg not in _KWARGS:
                raise Error("unknown keyword argument '%s'" % kwargs)

        self._transport = transport
        if not transport:
            self._transport = Transport.Transport(**kwargs)

        pmtype = kwargs.get("pmtype", None)
        if pmtype:
            pmtype = pmtype.lower()
            for pmtypes, cls in _PMTYPE_CLASSES.items():
                if pmtype in pmtypes:
                    self._pmeter = cls(self._transport)
                    break
            else:
                msg = []
                for pmtypes, pmclass in _PMTYPE_CLASSES.items():
                    msg.append("* %s - %s" %
                               (", ".join(pmtypes), pmclass.name))
                raise Error(
                    "unsupported power meter type '%s', supported power meter types "
                    "are:\n%s" % (pmtype, "\n".join(msg)))
        else:
            errors = []
            self._pmeter = None
            for pmtypes, cls in _PMTYPE_CLASSES.items():
                pmtype = "/".join(pmtypes)
                try:
                    _LOG.debug("probing '%s'", pmtype)
                    self._pmeter = cls(self._transport)
                    break
                except Error as err:
                    errors.append((pmtype, cls, err))

            if not self._pmeter:
                self._probe_error(errors)
Exemplo n.º 5
0
def parse_duration(htime, default_unit="s"):
    """
    This function does the opposite to what human_time does - parses the human time string and
    returns integer number of seconds. However, this function does not support milliseconds. Default
    unit can be passed to handle cases where 'htime' is an integer. So for example, 'htime' could
    simply '1', and if 'default_unit' is 's', then this will be interpreted as '1s'.
    """

    htime = htime.strip()

    if htime.isdigit():
        htime += default_unit

    keys = ["hours", "minutes", "seconds"]
    tokens = {}

    rest = htime.lower()
    for spec, key in zip(list("hms"), keys):
        split = rest.split(spec, 1)
        if len(split) > 1:
            tokens[key] = split[0]
            rest = split[1]
        else:
            rest = split[0]

    if rest.strip():
        if not tokens and is_int(rest):
            # This is the case when no "hms" specifiers were given, in which case we assume the
            # given integer is the amount of seconds.
            tokens["seconds"] = rest
        else:
            raise Error(
                "bad time length specification '%s', cannot parse this part: %s"
                % (htime, rest))

    for key, val in tokens.items():
        if not is_int(val):
            raise Error(
                "bad amount of %s '%s' in time length specification '%s'" %
                (key, val, htime))

    hours = int(tokens.get("hours", 0))
    mins = int(tokens.get("minutes", 0))
    secs = int(tokens.get("seconds", 0))
    return hours * 60 * 60 + mins * 60 + secs
Exemplo n.º 6
0
    def write(self, data):
        """Write command directly to the device."""

        try:
            os.write(self._fd, bytes(data, 'utf-8'))
        except OSError as err:
            raise Error("error while writing to device \"{}\": {}".format(
                self._devnode, err))

        self._dbg("sent: {}".format(data))
Exemplo n.º 7
0
    def _verify_argument(self, cmd, arg):
        """Verify whether or not 'arg' argument is valid for 'cmd' command."""

        if cmd not in self._assortments:
            return True

        if "assortment" in self._assortments[cmd]:
            assortment = self._assortments[cmd]["assortment"]
            if arg not in assortment:
                raise Error("unacceptable argument \"{}\", "
                            "use: {}".format(arg, ", ".join(assortment)))

        if "verify-func" in self._assortments[cmd]:
            func = self._assortments[cmd]["verify-func"]
            if not func(arg):
                raise Error("unacceptable argument \"{}\", "
                            "use: {}".format(
                                arg, self._assortments[cmd]["text-descr"]))

        return True
Exemplo n.º 8
0
    def __init__(self, transport):
        """The class constructor."""

        # Call the base class constructor first.
        super(WT310, self).__init__(transport)

        self._pmtype = None
        self.max_data_items = _MAX_DATA_ITEMS

        self._populate_data_items(_WT310_DATA_ITEMS, _DITT)
        self._add_wt310_commands()
        self._populate_choices(_CHOICES)
        self._populate_raw_commands(_RAW_COMMANDS)
        self._populate_tweaks(_TWEAKS)
        self._populate_arg_verify_funcs()
        self._populate_errors_map_map()

        self._init_pmeter()

        # Verify that we are dealing with a WT310.
        ids = self._command("get-id")
        split_ids = ids.split(",")
        if len(ids) < 2:
            raise Error(
                "'%s' has ID string '%s' and it does not look like a WT310 power meter"
                % (transport.devnode, ids))

        self.pmtype = split_ids[1].strip().lower()
        if not any([self.pmtype.startswith(pmtype)
                    for pmtype in self.pmtypes]):
            raise Error(
                "'%s' has ID string '%s' and it does not look like a WT310 power meter"
                % (transport.devnode, ids))

        # Set data format to ascii.
        self._command("set-data-format", "ascii")
        # Enable WT310 commands
        self._command("set-compat-mode", "WT300")
        # Enable verbose mode which makes the power meter reply with full strings instead of cut
        # ones.
        self._command("set-verbose-mode", "on")
Exemplo n.º 9
0
    def read(self, size=4096):
        """Read an arbitrary amount of data directly from the device."""

        try:
            data = os.read(self._fd, size).decode("utf-8")
        except OSError as err:
            raise Error(
                "error while reading from device \"{}\": \"{}\"".format(
                    self._devnode, err))

        self._dbg("received: {}".format(data))

        return data
Exemplo n.º 10
0
    def ioctl(self, fobj, operation):
        """
        Execute specific IOCTL to ensure that we are dealing with the expected type of character
        device.
        """

        try:
            ioctl(fobj, operation)
        except IOError as err:
            if err.errno == errno.ENOTTY:
                raise Error("'%s' is not a '%s' device" % (self.devnode, self.name))
            raise TransportError("ioctl '%#X' for device '%s' failed:\n%s"
                                 % (operation, self.devnode, err))
Exemplo n.º 11
0
    def get_argument_help(self, cmd):
        """Return a user-friendly help message for the power meter command 'cmd'."""

        if cmd not in self._assortments:
            raise Error(
                "command \"{}\" does not support arguments".format(cmd))

        if "text-descr" in self._assortments[cmd]:
            return self._assortments[cmd]["text-descr"]
        elif "assortment" in self._assortments[cmd]:
            return ", ".join(self._assortments[cmd]["assortment"])
        else:
            return "no help text for \"{}\", please report a bug".format(cmd)
Exemplo n.º 12
0
    def command(self, cmd, arg=None):
        """
        Execute the power meter command 'cmd' with argument 'arg'. Return the command response or
        'None' if the command has no response. 'cmd' should be a string, 'arg' should be of the type
        the specific command expects. In most of the cases 'arg' is a string or 'None', but it may
        also be a list in case of the 'configure-data-items' command.
        """

        try:
            return self._pmeter.command(cmd, arg)
        except ErrorBadArgument:
            raise Error("bad argument '%s' for command '%s', use:\n%s" %
                        (arg, cmd, self.get_argument_help(cmd)))
Exemplo n.º 13
0
def iteratate_configs():
    """
    Iterate over configuration files and yied the 'configparser' objects.
    """

    user_cfgfile = os.path.join(os.path.expanduser("~"), USER_CFG_FILE_NAME)
    for path in (SYSTEM_CFG_FILE, user_cfgfile):
        if os.path.isfile(path):
            try:
                cfgfile = configparser.ConfigParser()
                cfgfile.read(path)
            except configparser.Error as err:
                raise Error("failed to parse configuration file '%s':\n%s" % (path, err))
            cfgfile.path = path
            yield cfgfile
Exemplo n.º 14
0
    def _probe_error(self, errors):
        """TODO"""

        import textwrap

        wrapper = textwrap.TextWrapper(width=79)

        msg = "unknown type of the device '%s'. Here is the log of all the attempts to recognize " \
              "the device type." % self._transport.devnode
        lines = wrapper.wrap(msg)

        wrapper.initial_indent = " * "
        wrapper.subsequent_indent = "   "
        for pmtype, cls, err in errors:
            msg = "%s: %s" % (pmtype, cls, err)
            lines += wrapper.wrap(msg)

        raise Error("\n".join(lines))
Exemplo n.º 15
0
def _math_response_tweak(_, value):
    """
    Remove the '1' part from a math function name. Additionally, add a comma between the math
    function name and the element number which makes math functions look consistent with data item
    names.
    """

    parts = value.split(";")
    if parts[0] == "ARITHMETIC":
        return parts[1].lower()
    elif parts[1] == "A,1":
        return "cfi"
    elif parts[1] == "V,1":
        return "cfv"
    elif parts[1] == "W,1":
        return "avw"

    raise Error("unknown power meter math function '%s'" % value)
Exemplo n.º 16
0
    def _command(self, cmd, arg=None, check_status=True, func=True):
        """
        Actually execute the command, the tweaks, and the status checks, unless 'check_status' is
        'False'. Some commands have both the "raw-cmd" and "func" keys in their 'self._command'
        description, and the 'func' argument whether the function should be executed or the raw
        command should be executed.
        """

        _LOG.debug(_cmd_to_str(cmd, arg))

        if func and "func" in self._commands[cmd]:
            return self._commands[cmd]["func"](cmd, arg)

        if arg is not None:
            arg = self._apply_input_tweaks(cmd, arg)

        raw_cmd = self._commands[cmd]["raw-cmd"]
        if arg is not None:
            raw_cmd += " %s" % arg

        try:
            self._transport.writeline(raw_cmd)
        except Transport.Error as err:
            raise type(
                err
            )("failed to write command '%s' to the power meter:\n%s\nRaw command "
              "was '%s'" % (cmd, err, raw_cmd))
        response = None
        if self._commands[cmd]["has-response"]:
            try:
                response = self._transport.readline()
            except Transport.Error as err:
                raise type(
                    err
                )("failed to read power meter response to '%s':\n%s\nRaw command was "
                  "'%s'" % (_cmd_to_str(cmd, arg), err, raw_cmd))
            response = self._apply_response_tweaks(cmd, response)

        if check_status:
            msg = self._check_error_status(cmd, arg)
            if msg:
                raise Error(msg)

        return response
Exemplo n.º 17
0
    def __init__(self, devnode='/dev/usbtmc0'):
        """
        The class constructor. The 'devnode' argument is the USBTMC device node to use as a
        transport.
        """

        self._fd = None
        self._close_mutex = threading.Lock()

        super().__init__(devnode)

        try:
            self._fd = os.open(self._devnode, os.O_RDWR)
        except OSError as err:
            raise Error("error opening device \"{}\": {}".format(
                self._devnode, err))

        # Make sure the device is a USBTMC device by invoking a USBTMC-specific IOCTL and checking
        # that it is supported.
        super().ioctl(self._fd, _USBTMC_IOCTL_CLEAR)
Exemplo n.º 18
0
    def _set_data_items(self, data_items):
        """Configure the power meter before reading data."""

        if len(data_items) > self._meter.max_data_items:
            raise Error("too many data items to read, "
                        "please, specify at most {} items".format(
                            self._meter.max_data_items))

        self._get_data_items_to_read(data_items)

        if self._data_items:
            for idx, data_item in enumerate(self._data_items, 1):
                self._meter._verify_argument("set-data-item{}".format(idx),
                                             data_item)

            self._meter.command("set-data-items-count", len(self._data_items))
            for idx, data_item in enumerate(self._data_items, 1):
                self._meter.command("set-data-item{}".format(idx), data_item)

        self._argcopy = data_items
Exemplo n.º 19
0
    def __init__(self, transport):
        """The class constructor."""

        # Call the base class constructor first.
        super(WT210, self).__init__(transport)

        self.pmtype = "wt210"
        self.max_data_items = _MAX_DATA_ITEMS

        # Indexes for the data items that were configured to be read by 'configure-data-items'.
        self._wt210_item_indexes = None
        # List of items configured to be read by 'configure-data-items'.
        self._wt210_items_to_read = None

        self._populate_data_items({}, _DITT)
        self._populate_choices(_CHOICES)
        self._populate_raw_commands(_RAW_COMMANDS)
        self._populate_tweaks(_TWEAKS)
        self._populate_arg_verify_funcs()
        self._populate_errors_map_map()

        try:
            self._init_pmeter()
        except ErrorBadResponse as err:
            msg = "%s\n%s" % (err,
                              "Did you switch your WT210 to the '488.2' mode?")
            raise ErrorBadResponse(msg=msg)

        # The ID string of WT210 does not seem to contain a "WT210", so we cannot easily figure out
        # the power meter type.
        ids = self._command("get-id")

        match = re.match(r"WT\d+", ids)
        if match and match.group(0).lower() != "wt210":
            raise Error("'%s' is not a WT210 power meter" % transport.devnode)

        self._command("set-remote-mode", "on")

        # Run a command which is specific to WT210 to verify we are really talking to a WT210.
        self._command("get-line-filter")
Exemplo n.º 20
0
    def _configure_data_items_cmd(self, _, items):
        """Set the data items that will be read by next "get-data" command."""

        self._items_to_read = items

        if len(items) > self.max_data_items:
            raise Error("too many data items, please, specify at most %s" %
                        self.max_data_items)

        for item in items:
            if item not in self._data_items:
                msg = "bad data item '%s'" % item
                raise ErrorBadArgument(None, None, msg=msg)

        # The interval is needed to compute the Joules virtual data item.
        self._interval = float(self._command("get-interval"))
        items = self._prepare_data_items_to_read(items)
        _LOG.debug("data items to read from power meter: %s", ",".join(items))

        # Configure a trigger for the "UPD" bit changing from 1 to 0, which happens when data update
        # finishes.
        self._command("set-eesr-filter-upd", "fall")
        return items