def select_input(self, input_name: str):
        """Switch the TV's input

        :param str input_name: Name of the input to select

        :rtype: str
        :return: Name of input selected.  Name will be the one provided in
            the configuration if present, otherwise it will be the driver
            default name for the input terminal.
        """
        # The Samsung in my office seems to not mind me trying to select inputs it doesn't have.
        # (ex. HDMI 4 on a TV with only 3 HDMI inputs). It just says "Not available" on the screen
        # and goes back to the last valid input selected.
        try:
            if input_name not in self.inputs:
                raise KeyError("Error: No input named '{}'".format(input_name))
            input_code = self.inputs[input_name]
        except KeyError as ke:
            logger.error(
                "select_input(): Exception occurred: {}".format(input_name))
            raise ke
        # Any other exception has already been logged.  Just send it upward.
        except Exception as e:
            raise e
        else:
            self.__cmd(self.Command.SELECT_INPUT, input_code)
            # We really have nothing to go on regarding whether this actually succeeded or not,
            # but let's assume it did and return the proper response.
            return key_for_value(self.inputs, input_code)
    def select_input(self, input_name: str = '1'):
        """Switch inputs

        :param str input_name: Name of the input to switch to
        :rtype str
        :return Name of the input switched to if no errors occurred.  Name will be
            the one provided in the configuration if present, otherwise it will be the
            driver default name for the input terminal.
        """
        try:
            if input_name not in self.inputs:
                raise KeyError("Error: No input named '{}'".format(input_name))
            input_value = self.inputs[input_name]
            logger.debug("Selecting input '{}' on relay {}".format(
                input_name, input_value))
            self._bus.write_byte_data(self._DEVICE_ADDR, input_value, 0xFF)
            time.sleep(self._activate_duration)
            self._bus.write_byte_data(self._DEVICE_ADDR, input_value, 0x00)

        except Exception as e:
            logger.error('select_input(): Exception occurred: {}'.format(
                e.args))
            raise e

        else:
            self._selected_input = key_for_value(self.inputs, input_value)
            return self._selected_input
Esempio n. 3
0
    def __determine_input(self, input_code):
        """Helper method for get_input_status()

        Separated from get_input_status() because the logic here can be reused later
        in get_status() to avoid having to call get_input_status() again.
        """
        if input_code not in self.status.keys():
            raise OutOfRangeError(
                "__determine_input(): unrecognized input value {}.\n"
                "This means our NEC documentation is definitely out of date. "
                "If you're seeing this, please notify IT's AV Services department."
                .format(input_code))

        input_string = self.status[input_code]
        # recommend always logging this
        logger.info('__determine_input(): {} : {}'.format(
            input_code, input_string))

        # NEC makes this more difficult than it should be. Input setting and getting
        # use different values, and the manual implies those values vary by model.
        # So we'll have to give it our best guess...

        # input_group (first number) should ordinarily be 0x01, 0x02, or 0x03
        # if it's 0x04 or 0x05, we're on one of the viewer or LAN inputs
        input_group = input_code[0]

        guess = ''

        if "Computer" in input_string:
            guess = 'RGB_' + str(input_group)
        elif "HDMI" in input_string or "HDBaseT" in input_string:
            guess = 'HDMI_' + str(input_group)
        elif "Video" in input_string:
            guess = 'VIDEO_1'
        elif "DisplayPort" in input_string:
            guess = 'DISPLAYPORT'
        elif "S-video" in input_string or "Component" in input_string:
            guess = 'VIDEO_2'
        elif "LAN" in input_string:
            guess = 'NETWORK'
        # Who knows/cares what the hell input it's on?  Some kind of viewer.
        elif "Viewer" in input_string or "SLOT" in input_string or "APPS" in input_string:
            guess = 'USB_VIEWER_A'

        if guess in self.inputs:
            return key_for_value(self.inputs, self.inputs[guess])
        else:
            raise OutOfRangeError(
                '__determine_input(): unable to reliably determine input from values: {}\n'
                'Our NEC documentation may be out of date. Please notify IT.'.
                format(input_code))
Esempio n. 4
0
    def get_input_status(self):
        """Get the current input terminal

        :rtype: str
        :returns: Name of the current input terminal shown.  Name will
            be the one provided in the configuration if present there, otherwise
            it will be the driver default name for the input terminal.
        """
        try:
            result = self.__cmd(cmd=self.Command.INPUT_STATUS)
        except Exception as e:
            logger.error('get_input_status(): Exception occurred: {}'.format(
                e.args))
            raise e
        else:
            # result is b'%1INPT=##\r' where ## is the input terminal
            data = result[7:].rstrip()
            if data in self.inputs.values():
                return key_for_value(self.inputs, data)
Esempio n. 5
0
    def get_input_set(self):
        """Get available input terminals.

        :rtype: list[str]
        :returns: A list containing the names of the input terminals
            available on this projector.
        """
        try:
            result = self.__cmd(cmd=self.Command.INPUT_LIST)
        except Exception as e:
            logger.error('get_input_set(): Exception occurred: {}'.format(
                e.args))
            raise e
        else:
            ins = result[7:].split()
            inputs_available = set()
            for i in ins:
                if i in self.inputs.values():
                    inputs_available.add(key_for_value(self.inputs, i))
            return inputs_available
Esempio n. 6
0
    def select_input(self, input_name):
        """Switch to an input terminal

        :param str input_name: The name of the input to select.
        :rtype: str
        :returns: Name of input selected if successful.  Name will be the one
            provided in the configuration if present there, otherwise it will
            be the driver default name for the input terminal.
        """
        try:
            if input_name not in self.inputs:
                raise KeyError("Error: No input named '{}'".format(input_name))
            result = self.__cmd(self.Command.SWITCH_INPUT,
                                self.inputs[input_name])
        except Exception as e:
            logger.error('select_input(): Exception occurred: {}'.format(
                e.args))
            raise e
        else:
            return key_for_value(self.inputs, self.inputs[input_name])
Esempio n. 7
0
    def get_input_status(self):
        """Get the input(s) assigned to our output(s)

        This tries to detect which inputs are routed to which outputs using a few different query commands.
        Returns a list of input names. If the switcher only has a single output, the list will contain
        a single str: the name of the input routed to that output.

        :rtype: list[str]
        :returns: List of names of inputs corresponding to the current routing assignments
            for each output.  The names will be the ones provided in the configuration if
            present there, otherwise they will be the driver default input terminal names.
        """
        try_in_order = [
            b'#ROUTE? 1,*\r', b'#AV? *\r', b'#VID? *\r', b'#AV? 1\r',
            b'#VID? 1\r'
        ]

        try:
            self.open_connection()
            self.comms.reset_input_buffer()

            for cmd in try_in_order:
                response = self.__try_cmd(cmd)
                logger.debug("input_status: response: '{}'".format(response))
                if response == self.Error.CMD_UNAVAILABLE:
                    continue
                elif response == self.Error.PARAM_OUT_OF_RANGE:
                    raise OutOfRangeError(
                        'Error: Parameter out of range: {}'.format(
                            cmd.decode()))
                elif response == self.Error.SYNTAX:
                    raise BadCommandError(
                        'Error: Bad Protocol 3000 syntax: {}'.format(
                            cmd.decode()))
                elif b'ERR' in response:
                    raise Exception(
                        'An unknown error was reported by the switcher: {}'.
                        format(response.decode()))
                else:
                    if b'ROUTE' in response:
                        # If '#ROUTE? 1,*' worked, the result should look like:
                        # b'~01@ROUTE 1,1,1\r\n~01@ROUTE 1,2,1\r\n'
                        #                 ^                  ^ We want the 3rd number.
                        routes = response.split(b'\r\n')
                        inputs = []
                        for route in routes:
                            match = re.search(rb'~\d+@ROUTE\s+\d+,\d+,(\d+)',
                                              route)
                            if match:
                                input_name = key_for_value(
                                    self.inputs,
                                    match.group(1).decode())
                                inputs.append(input_name)
                        return inputs
                    elif b'VID' in response or b'AV' in response:
                        # If '#VID? *' worked, the result should look like:
                        # b'~01@VID 1>1,1>2\r\n'
                        #           ^   ^ We want the 1st number.
                        # (I assume #AV is similar, though our switchers don't support it)
                        match = re.search(rb'~\d+@(VID|AV)\s+([0-9>,]+)\r\n',
                                          response)
                        if match:
                            routing_info = match.group(2)
                            routes = routing_info.split(b',')
                            inputs = []
                            for route in routes:
                                route_match = re.search(rb'(\d+)>\d+', route)
                                if route_match:
                                    input_name = key_for_value(
                                        self.inputs,
                                        route_match.group(1).decode())
                                    inputs.append(input_name)
                            return inputs

        except Exception as e:
            logger.error('get_input_status(): Exception occurred: {}'.format(
                e.args),
                         exc_info=True)
            return None

        finally:
            self.close_connection()
Esempio n. 8
0
    def select_input(self, input_name='1', output_name='ALL'):
        """Select an input to route to the specified output

        Tries several Protocol 3000 routing/switching commands in order until one succeeds:
        #ROUTE, #AV, or #VID.

        :param str input_name: Name of input to route.
            Default is '1'.
        :param str output_name: Name of output to route to.
            Default is 'ALL'.
        :rtype: str
        :returns: Name of input selected if no errors are reported.  Name will be the one
            provided in the configuration if present there, otherwise it will be the driver
            default name for the input terminal.
        """
        try:
            if input_name not in self.inputs:
                raise KeyError("Error: No input named '{}'".format(input_name))
            if output_name not in self.outputs:
                raise KeyError(
                    "Error: No output named '{}'".format(output_name))

            in_value = self.inputs[input_name]
            out_value = self.outputs[output_name]

            # AV I placed before VID because if a device has separate routable audio & video,
            # we'd prefer to route them both together here.  Handling them separately is
            # way too complicated and beyond the scope of what we're trying to do.
            # Since none of our switchers appear to support #AV, it means an extra half-second
            # delay in our app, but oh well.
            try_in_order = [
                '#ROUTE 1,{},{}\r'.format(out_value, in_value),
                '#AV {}>{}\r'.format(in_value, out_value),
                '#VID {}>{}\r'.format(in_value, out_value)
            ]

            self.open_connection()
            self.comms.reset_input_buffer()

            for cmd in try_in_order:
                response = self.__try_cmd(cmd.encode())
                logger.debug("select_input(): response: '{}'".format(response))
                if response == self.Error.CMD_UNAVAILABLE:
                    # try the next one
                    continue
                elif response == self.Error.PARAM_OUT_OF_RANGE:
                    raise OutOfRangeError(
                        'Error: Input or output number out of range - '
                        'input={}, output={}'.format(in_value, out_value))
                elif response == self.Error.SYNTAX:
                    raise BadCommandError(
                        'Error: Bad Protocol 3000 syntax: {}'.format(cmd))
                elif b'ERR' in response:
                    raise Exception(
                        'An unknown error was reported by the switcher: {}'.
                        format(response.decode()))
                else:
                    # no errors reported, our command probably worked
                    return key_for_value(self.inputs, self.inputs[input_name])

        except Exception as e:
            logger.error('select_input(): Exception occurred: {}'.format(
                e.args),
                         exc_info=True)
            raise e
        finally:
            self.close_connection()
Esempio n. 9
0
    def parse(self, data: str):
        """Parse response and set internal state

        Parses a response, watching for updates to video mute status,
        power status, and input selection status.  If the logger is set
        to debug, also logs each response as well as its meaning, if known.

        :param str data: Data read from the serial device or socket
        """
        logger.debug('parse() called with data=%s', data)

        # If response contains ':' it doesn't fit the pattern we're looking for.
        # It's probably 'MAC:' or 'IP:' or some other message we see during bootup.
        if ':' in data:
            logger.debug('parse() says: %s', data)
            return

        # Another special case where it rattles off the firmware version numbers during boot.
        # It looks like this:
        # b'\r\nVTR1.21 \r\nVTX1.07 \r\nVPD1.10 \r\nVTO1.01 \r\nVTN1.00 \r\nVPC1.16\r\n\r\n>'
        # Yeah, we're not interested in pretty much any of that, but the main firmware
        # is the first one in case we want to log it for whatever reason.
        elif 'VTR' in data:
            logger.debug('parse() says: <FIRMWARE VERSION %s>', data.split()[0].strip())
            return

        # Now for the normal(ish) responses...
        # Split by whitespace
        data_parts = data.split()

        # Result has at least 3 parts... this should match most responses that we see, except for the
        # very terse '-' returned by a power status query ('Y 1 10') while the switcher is powered on.
        # (If data_parts[2] is present, it is the command # or function called)
        if len(data_parts) > 2 and int(data_parts[2]) in self.functions.keys():
            func = int(data_parts[2])

            # data_parts[3] if present is the (first) parameter
            if len(data_parts) > 3:

                # Special case for network functions 201-203 - they have 4 params (IPV4 octets)
                if len(data_parts) == 7:
                    addr = ".".join(data_parts[3:7])
                    logger.debug('parse() says: %s', self.functions[func].format(addr))
                    return

                # We'll use param in all other cases below, so it's defined here
                param = int(data_parts[3])

                # Special case for function 177 - auto switch input priority has 2 params
                if len(data_parts) > 4 and func == 177:
                    param2 = int(data_parts[4])
                    logger.debug('parse() says: %s', self.functions[func].format(
                        self.data[func][3][param], self.data[func][4][param2]
                    )
                                 )
                    return

                # If data[func] contains a dict key matching the value of param, get that value
                # and log a message to the logfile interpreting this response along with its parameter...
                # ie. '<INPUT HDMI 1>' or '<VIDEO BLANK OFF>' is what we'll see in the log,
                # instead of '<INPUT 3>' or '<VIDEO BLANK 0>'
                elif func in self.data and param in self.data[func]:
                    logger.debug('parse() says: %s', self.functions[func].format(self.data[func][param]))

                    # __This section is where the magic happens for tracking our switcher's state.__

                    # Video blank set command ('Y 0 8 [0|1]') / get command ('Y 1 8')
                    # Set response: 'Z 0 8 [0|1]' / Get response: 'Z 1 8 [0|1]'
                    if func == 8:
                        self._av_mute = bool(param)

                    # Power state set command ('Y 0 10 [0|1]') / get command ('Y 1 10')
                    # Set response: 'Z 0 10 [0|1]' / Get response: 'Z 1 10 0' or simply '-'.
                    # (We will only see 'Z 1 10' in the get response if the power is currently off.
                    # For whatever reason if the power is on it simply returns '-'.
                    # We check for that response in a special case below.
                    # So effectively, we could just write `self._power_status = False` here.)
                    elif func == 10:
                        self._power_status = bool(param)

                    # Input set command ('Y 0 30 [0-6]') / get command ('Y 1 30')
                    # Set response: 'Z 0 30 [0-6]' / Get response: 'Z 1 30 [0-6]'
                    elif func == 30:
                        self._input_status = key_for_value(self.inputs, param)

                    return
                # ... otherwise, just log the function name along with the bare value of param.
                # ie. '<BRIGHTNESS 50>', etc.
                else:
                    logger.debug('parse() says: %s', self.functions[func].format(param))
                    return

            # Response had no parameters? Just log the function called.
            # <MENU>, <UP>, <DOWN>, <LEFT>, <RIGHT>, and <ENTER>
            # keys on the front panel generate these responses.
            else:
                logger.debug('parse() says: %s', self.functions[func])
                return

        # Response is just "-".  This is what we get back when we send a power status query
        # ('Y 1 10') to the switcher while it's powered on.  We would expect to receive
        # 'Z 1 10 1' here but the VP-734 is a little quirky it seems...
        elif len(data_parts) == 1 and data_parts[0] == "-":
            self._power_status = True
            logger.debug('parse() says: Power query - power is ON')
            return

        # If data_parts[2] is not listed in our function dict,
        # it must be something we didn't care enough to implement,
        # so just log the bare response.
        else:
            logger.debug('parse() says: Unimplemented/unknown function %s', data)
            return