Beispiel #1
0
    def set_color(self, channel, mode, colors, non_volatile=False, **kwargs):
        """Set the lighting mode, when applicable, color.

        The table bellow summarizes the available channels, modes and their
        associated number of required colors.

        | Channel  | Mode      | Required colors |
        | -------- | --------- | --------------- |
        | led      | off       |               0 |
        | led      | fixed     |               1 |
        | led      | flash     |               1 |
        | led      | breathing |               1 |
        | led      | rainbow   |               0 |

        The settings configured on the device are normally volatile, and are
        cleared whenever the graphics card is powered down (OS and UEFI power
        saving settings can affect when this happens).

        It is possible to store them in non-volatile controller memory by
        passing `non_volatile=True`.  But as this memory has some unknown yet
        limited maximum number of write cycles, volatile settings are
        preferable, if the use case allows for them.

        """

        check_unsafe('smbus', error=True, **kwargs)

        assert self._address != self._SENTINEL_ADDRESS, \
               'invalid address (probing may not have had access to SMbus)'

        colors = list(colors)

        try:
            mode = self.Mode[mode]
        except KeyError:
            raise ValueError(f'invalid mode: {mode!r}') from None

        if len(colors) < mode.required_colors:
            raise ValueError(f'{mode} mode requires {mode.required_colors} colors')

        if len(colors) > mode.required_colors:
            _LOGGER.debug('too many colors, dropping to %d', mode.required_colors)
            colors = colors[:mode.required_colors]

        if mode == self.Mode.OFF:
            self._smbus.write_byte_data(self._address, self._REG_MODE,
                                        self.Mode.FIXED.value)
            self._smbus.write_byte_data(self._address, self._REG_RED, 0x00)
            self._smbus.write_byte_data(self._address, self._REG_GREEN, 0x00)
            self._smbus.write_byte_data(self._address, self._REG_BLUE, 0x00)
        else:
            self._smbus.write_byte_data(self._address, self._REG_MODE, mode.value)
            for r, g, b in colors:
                self._smbus.write_byte_data(self._address, self._REG_RED, r)
                self._smbus.write_byte_data(self._address, self._REG_GREEN, g)
                self._smbus.write_byte_data(self._address, self._REG_BLUE, b)

        if non_volatile:
            self._smbus.write_byte_data(self._address, self._REG_APPLY,
                                        self._ASUS_GPU_APPLY_VAL)
Beispiel #2
0
    def set_color(self, channel, mode, colors, non_volatile=False, **kwargs):
        """Set the RGB lighting mode and, when applicable, color.

        The table bellow summarizes the available channels, modes and their
        associated number of required colors.

        | Channel  | Mode      | Required colors |
        | -------- | --------- | --------------- |
        | led      | off       |               0 |
        | led      | fixed     |               1 |
        | led      | breathing |               1 |
        | led      | rainbow   |               0 |

        The settings configured on the device are normally volatile, and are
        cleared whenever the graphics card is powered down (OS and UEFI power
        saving settings can affect when this happens).

        It is possible to store them in non-volatile controller memory by
        passing `non_volatile=True`.  But as this memory has some unknown yet
        limited maximum number of write cycles, volatile settings are
        preferable, if the use case allows for them.
        """

        check_unsafe('smbus', error=True, **kwargs)

        colors = list(colors)

        try:
            mode = self.Mode[mode]
        except KeyError:
            raise ValueError(f'invalid mode: {mode!r}') from None

        if len(colors) < mode.required_colors:
            raise ValueError(
                f'{mode} mode requires {mode.required_colors} colors')

        if len(colors) > mode.required_colors:
            _LOGGER.debug('too many colors, dropping to %d',
                          mode.required_colors)
            colors = colors[:mode.required_colors]

        self._smbus.write_byte_data(self._address, self._REG_MODE, mode.value)

        for r, g, b in colors:
            self._smbus.write_byte_data(self._address, self._REG_RED, r)
            self._smbus.write_byte_data(self._address, self._REG_GREEN, g)
            self._smbus.write_byte_data(self._address, self._REG_BLUE, b)

        if non_volatile:
            # the following write always fails, but nonetheless induces persistence
            try:
                self._smbus.write_byte_data(self._address, self._REG_PERSIST,
                                            self._PERSIST)
            except OSError as err:
                _LOGGER.debug(
                    'expected OSError when writing to _REG_PERSIST: %s', err)
Beispiel #3
0
    def set_speed_profile(self,
                          channel,
                          profile,
                          temperature_sensor=1,
                          **kwargs):
        """Set fan or fans to follow a speed duty profile.

        Valid channel values are 'fanN', where N >= 1 is the fan number, and
        'fan', to simultaneously configure all fans.  Unconfigured fan channels
        may default to 100% duty.

        Up to six (temperature, duty) pairs can be supplied in `profile`,
        with temperatures in Celsius and duty values in percentage.  The last
        point should set the fan to 100% duty cycle, or be omitted; in the
        latter case the fan will be set to max out at 60°C.
        """

        # send fan num, temp sensor, check to make sure it is actually enabled, and do not let the user send external sensor
        # 6 2-byte big endian temps (celsius * 100), then 6 2-byte big endian rpms
        # need to figure out how to find out what the max rpm is for the given fan

        if self._fan_count == 0:
            raise NotSupportedByDevice()

        profile = list(profile)

        criticalTemp = _CRITICAL_TEMPERATURE_HIGH if check_unsafe(
            'high_tempature', **kwargs) else _CRITICAL_TEMPERATURE
        profile = _prepare_profile(profile, criticalTemp)

        # fan_type = kwargs['fan_type'] # need to make sure this is set
        temp_sensor = clamp(temperature_sensor, 1, self._temp_probs)

        sensors = self._data.load('temp_sensors_connected',
                                  default=[0] * self._temp_probs)

        if sensors[temp_sensor - 1] != 1:
            raise ValueError('the specified tempature sensor is not connected')

        buf = bytearray(26)
        buf[1] = temp_sensor - 1  # 0  # use temp sensor 1

        for i, entry in enumerate(profile):
            temp = entry[0] * 100
            rpm = entry[1]

            # convert both values to 2 byte big endian values
            buf[2 + i * 2] = temp.to_bytes(2, byteorder='big')[0]
            buf[3 + i * 2] = temp.to_bytes(2, byteorder='big')[1]
            buf[14 + i * 2] = rpm.to_bytes(2, byteorder='big')[0]
            buf[15 + i * 2] = rpm.to_bytes(2, byteorder='big')[1]

        fan_channels = self._get_hw_fan_channels(channel)
        fan_modes = self._data.load('fan_modes', default=[0] * self._fan_count)

        for fan in fan_channels:
            mode = fan_modes[fan]
            if mode == _FAN_MODE_DC or mode == _FAN_MODE_PWM:
                buf[0] = fan
                self._send_command(_CMD_SET_FAN_PROFILE, buf)
Beispiel #4
0
    def get_status(self, verbose=False, **kwargs):
        """Get a status report.

        Returns a list of `(property, value, unit)` tuples.
        """

        # only RGB lighting information can be fetched for now; as that isn't
        # super interesting, only enable it in verbose mode

        if not verbose:
            return []

        if not check_unsafe('smbus', **kwargs):
            _LOGGER.warning("%s: nothing returned, requires unsafe feature 'smbus'",
                            self.description)
            return []

        mode = self.Mode(self._smbus.read_byte_data(self._address, self._REG_MODE))
        status = [('Mode', mode, '')]

        if mode.required_colors > 0:
            r = self._smbus.read_byte_data(self._address, self._REG_RED)
            g = self._smbus.read_byte_data(self._address, self._REG_GREEN)
            b = self._smbus.read_byte_data(self._address, self._REG_BLUE)
            status.append(('Color', f'{r:02x}{g:02x}{b:02x}', ''))

        return status
Beispiel #5
0
 def connect(self, **kwargs):
     """Connect to the device."""
     if not check_unsafe('smbus', **kwargs):
         _LOGGER.warning("SMBus: disabled, requires unsafe feature 'smbus'")
         return
     self._smbus.open()
     return self
Beispiel #6
0
    def get_status(self, **kwargs):
        """Get a status report.

        Returns a list of `(property, value, unit)` tuples.
        """

        if not check_unsafe(*self._UNSAFE, **kwargs):
            _LOGGER.warning("%s: disabled, requires unsafe features '%s'",
                            self.description, ','.join(self._UNSAFE))
            return []

        treg = self._read_temperature_register()

        # discard flags bits and interpret remaining bits as 2s complement
        treg = treg & 0x1fff
        if treg > 0x0fff:
            treg -= 0x2000

        # should always be supported
        resolution, bits = (.25, 10)

        multiplier = treg >> (12 - bits)

        return [
            ('Temperature', resolution * multiplier, '°C'),
        ]
Beispiel #7
0
    def probe(cls, smbus, vendor=None, product=None, address=None, match=None,
              release=None, serial=None, **kwargs):

        ADDRESSES = [0x29, 0x2a, 0x60]
        ASUS_GPU_MAGIC_VALUE = 0x1589

        if (vendor and vendor != ASUS) \
                or (address and int(address, base=16) not in ADDRESSES) \
                or smbus.parent_subsystem_vendor != ASUS \
                or smbus.parent_vendor != NVIDIA \
                or smbus.parent_driver != 'nvidia' \
                or release or serial:  # will never match: always None
            return

        supported = [
            (NVIDIA_RTX_2080_TI_REV_A, ASUS_STRIX_RTX_2080_TI_OC,
                'ASUS Strix RTX 2080 Ti OC'),
        ]

        for (dev_id, sub_dev_id, desc) in supported:
            if (product and product != sub_dev_id) \
                    or (match and match.lower() not in desc.lower()) \
                    or smbus.parent_subsystem_device != sub_dev_id \
                    or smbus.parent_device != dev_id \
                    or not smbus.description.startswith('NVIDIA i2c adapter 1 '):
                continue

            selected_address = None

            if check_unsafe('smbus', **kwargs):
                for address in ADDRESSES:
                    val1 = 0
                    val2 = 0

                    smbus.open()
                    try:
                        val1 = smbus.read_byte_data(address, 0x20)
                        val2 = smbus.read_byte_data(address, 0x21)
                    except:
                        pass
                    smbus.close()

                    if val1 << 8 | val2 == ASUS_GPU_MAGIC_VALUE:
                        selected_address = address
                        break
            else:
                selected_address = cls._SENTINEL_ADDRESS
                _LOGGER.debug('unsafe features not enabled, using sentinel address')

            if selected_address is not None:
                dev = cls(smbus, desc, vendor_id=ASUS, product_id=dev_id,
                          address=selected_address)
                _LOGGER.debug('instanced driver for %s at address %02x',
                              desc, selected_address)
                yield dev
Beispiel #8
0
 def connect(self, **kwargs):
     """Connect to the device."""
     if not check_unsafe('smbus', **kwargs):
         # do not raise yet: some driver APIs may not access the bus after
         # all, and allowing the device to pseudo-connect is convenient
         # given the current API structure; APIs that do access the bus
         # should check for the 'smbus' feature themselves and, if
         # necessary, raise UnsafeFeaturesNotEnabled(*requirements)
         # (see also: check_unsafe(..., error=True))
         _LOGGER.debug("SMBus is disabled, missing unsafe feature 'smbus'")
         return
     self._smbus.open()
     return self
Beispiel #9
0
    def probe(cls,
              smbus,
              vendor=None,
              product=None,
              address=None,
              match=None,
              release=None,
              serial=None,
              **kwargs):

        ASUS_GPU_MAGIC_VALUE = 0x1589

        pre_probed = super().pre_probe(smbus, vendor, product, address, match,
                                       release, serial, **kwargs)

        for dev_id, sub_dev_id, desc in pre_probed:
            selected_address = None

            if check_unsafe('smbus', **kwargs):
                for address in cls._ADDRESSES:
                    val1 = 0
                    val2 = 0

                    smbus.open()
                    try:
                        val1 = smbus.read_byte_data(address, 0x20)
                        val2 = smbus.read_byte_data(address, 0x21)
                    except:
                        pass
                    smbus.close()

                    if val1 << 8 | val2 == ASUS_GPU_MAGIC_VALUE:
                        selected_address = address
                        break
            else:
                selected_address = cls._SENTINEL_ADDRESS
                _LOGGER.debug(
                    'unsafe features not enabled, using sentinel address')

            if selected_address is not None:
                dev = cls(smbus,
                          desc,
                          vendor_id=ASUS,
                          product_id=dev_id,
                          address=selected_address)
                _LOGGER.debug('instanced driver for %s at address %02x', desc,
                              selected_address)
                yield dev
Beispiel #10
0
    def get_status(self, verbose=False, **kwargs):
        """Get a status report.

        Returns a list of `(property, value, unit)` tuples.
        """

        # only RGB lighting information can be fetched for now; as that isn't
        # super interesting, only enable it in verbose mode

        if not verbose:
            return []

        if not check_unsafe('smbus', **kwargs):
            _LOGGER.warning(
                "%s: nothing to return, requires unsafe features "
                "'smbus'", self.description)
            return []

        assert self._address != self._SENTINEL_ADDRESS, \
               'invalid address (probing may not have had access to SMbus)'

        mode = self._smbus.read_byte_data(self._address, self._REG_MODE)
        red = self._smbus.read_byte_data(self._address, self._REG_RED)
        green = self._smbus.read_byte_data(self._address, self._REG_GREEN)
        blue = self._smbus.read_byte_data(self._address, self._REG_BLUE)

        # emulate `OFF` both ways
        if red == green == blue == 0:
            mode = 0

        mode = self.Mode(mode)
        status = [('Mode', mode, '')]

        if mode.required_colors > 0:
            status.append(('Color', f'{red:02x}{green:02x}{blue:02x}', ''))

        return status
Beispiel #11
0
    def set_color(self, channel, mode, colors, **kwargs):
        """Set the color of each LED.

        In reality the device does not have the concept of different channels
        or modes, but this driver provides a few for convenience.  Animations
        still require successive calls to this API.

        The 'led' channel can be used to address individual LEDs, and supports
        the 'super-fixed', 'fixed' and 'off' modes.

        In 'super-fixed' mode, each color in `colors` is applied to one
        individual LED, successively.  LEDs for which no color has been
        specified default to off/solid black.  This is closest to how the
        device works.

        In 'fixed' mode, all LEDs are set to the first color taken from
        `colors`.  The `off` mode is equivalent to calling this function with
        'fixed' and a single solid black color in `colors`.

        The `colors` argument should be an iterable of one or more `[red, blue,
        green]` triples, where each red/blue/green component is a value in the
        range 0–255.

        The table bellow summarizes the available channels, modes, and their
        associated maximum number of colors for each device family.

        | Channel  | Mode        | LEDs         | Platinum | Pro XT |
        | -------- | ----------- | ------------ | -------- | ------ |
        | led      | off         | synchronized |        0 |      0 |
        | led      | fixed       | synchronized |        1 |      1 |
        | led      | super-fixed | independent  |       24 |     16 |

        Note: lighting control of Pro XT devices is experimental and requires
        the `pro_xt_lighting` constant to be supplied in the `unsafe` iterable.
        """

        if 'Pro XT' in self.description:
            check_unsafe('pro_xt_lighting', error=True, **kwargs)

        channel, mode, colors = channel.lower(), mode.lower(), list(colors)
        self._check_color_args(channel, mode, colors)
        if mode == 'off':
            expanded = []
        elif (channel, mode) == ('led', 'super-fixed'):
            expanded = colors[:self._led_count]
        elif (channel, mode) == ('led', 'fixed'):
            expanded = list(itertools.chain(*([color] * self._led_count for color in colors[:1])))
        else:
            assert False, 'assumed unreacheable'

        if self._data.load('leds_enabled', of_type=int, default=0) == 0:
            # These hex strings are currently magic values that work but Im not quite sure why.
            d1 = bytes.fromhex("0101ffffffffffffffffffffffffff7f7f7f7fff00ffffffff00ffffffff00ffffffff00ffffffff00ffffffff00ffffffffffffffffffffffffffffff")
            d2 = bytes.fromhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627ffffffffffffffffffffffffffffffffffffffffff")
            d3 = bytes.fromhex("28292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4fffffffffffffffffffffffffffffffffffffffffff")

            # Send the magic messages to enable setting the LEDs to statuC values
            self._send_command(None, 0b001, data=d1)
            self._send_command(None, 0b010, data=d2)
            self._send_command(None, 0b011, data=d3)
            self._data.store('leds_enabled', 1)

        data1 = bytes(itertools.chain(*((b, g, r) for r, g, b in expanded[0:20])))
        data2 = bytes(itertools.chain(*((b, g, r) for r, g, b in expanded[20:])))
        self._send_command(_FEATURE_LIGHTING, _CMD_SET_LIGHTING1, data=data1)
        self._send_command(_FEATURE_LIGHTING, _CMD_SET_LIGHTING2, data=data2)
Beispiel #12
0
    def set_color(self, channel, mode, colors, speed='normal',
                  transition_ticks=None, stable_ticks=None, **kwargs):
        """Set the RGB lighting mode and, when applicable, color.

        The table bellow summarizes the available channels, modes and their
        associated number of required colors.

        | Channel  | Mode      | Colors |
        | -------- | --------- | ------ |
        | led      | off       |      0 |
        | led      | fixed     |      1 |
        | led      | breathing |    1–7 |
        | led      | fading    |    2–7 |

        The speed of the breathing and fading animations can be adjusted with
        `speed`; the allowed values are 'slowest', 'slower', 'normal'
        (default), 'faster' and 'fastest'.

        It is also possible to override the raw timing parameters through
        `transition_ticks` and `stable_ticks`; these should be integer values
        in the range 0–63.
        """

        check_unsafe(*self._UNSAFE, error=True, **kwargs)

        try:
            common = self.SpeedTimings[speed].value
            tp1 = tp2 = common
        except KeyError:
            raise ValueError(f'invalid speed preset: {speed!r}') from None

        if transition_ticks is not None:
            tp1 = clamp(transition_ticks, 0, 63)
        if stable_ticks is not None:
            tp2 = clamp(stable_ticks, 0, 63)

        colors = list(colors)

        try:
            mode = self.Mode[mode]
        except KeyError:
            raise ValueError(f'invalid mode: {mode!r}') from None

        if len(colors) < mode.min_colors:
            raise ValueError(f'{mode} mode requires {mode.min_colors} colors')

        if len(colors) > mode.max_colors:
            _LOGGER.debug('too many colors, dropping to %d', mode.max_colors)
            colors = colors[:mode.max_colors]

        self._compute_rgb_address()

        if mode == self.Mode.OFF:
            mode = self.Mode.FIXED
            colors = [[0x00, 0x00, 0x00]]

        def rgb_write(register, value):
            self._smbus.write_byte_data(self._rgb_address, register, value)

        if mode == self.Mode.FIXED:
            rgb_write(self._REG_RGB_TIMING1, 0x00)
        else:
            rgb_write(self._REG_RGB_TIMING1, tp1)
            rgb_write(self._REG_RGB_TIMING2, tp2)

        color_registers = range(self._REG_RGB_COLOR_START, self._REG_RGB_COLOR_END)
        color_components = itertools.chain(*colors)

        for register, component in zip(color_registers, color_components):
            rgb_write(register, component)

        rgb_write(self._REG_RGB_COLOR_COUNT, len(colors))

        if mode == self.Mode.BREATHING and len(colors) == 1:
            rgb_write(self._REG_RGB_MODE, self.Mode.FIXED.value)
        else:
            rgb_write(self._REG_RGB_MODE, mode.value)