Esempio n. 1
0
 def set_color(self,
               channel,
               mode,
               colors,
               time_per_color=1,
               time_off=None,
               alert_threshold=_HIGH_TEMPERATURE,
               alert_color=[255, 0, 0],
               speed=3,
               non_volatile=False,
               **kwargs):
     """Set the color mode for a specific channel."""
     # keyword arguments may have been forwarded from cli args and need parsing
     colors = list(colors)
     self._begin_transaction()
     if mode == 'rainbow':
         if isinstance(speed, str):
             speed = int(speed)
         self._write([0x23, clamp(speed, 1, 6)])
         # make sure to clear blinking or... chaos
         self._configure_device(alert_temp=clamp(alert_threshold, 0, 100),
                                color3=alert_color)
     elif mode == 'fading':
         self._configure_device(fading=True,
                                color1=colors[0],
                                color2=colors[1],
                                interval1=clamp(time_per_color, 1, 255),
                                alert_temp=clamp(alert_threshold, 0, 100),
                                color3=alert_color)
         self._write([0x23, 0])
     elif mode == 'blinking':
         if time_off is None:
             time_off = time_per_color
         self._configure_device(blinking=True,
                                color1=colors[0],
                                interval1=clamp(time_off, 1, 255),
                                interval2=clamp(time_per_color, 1, 255),
                                alert_temp=clamp(alert_threshold, 0, 100),
                                color3=alert_color)
         self._write([0x23, 0])
     elif mode == 'fixed':
         self._configure_device(color1=colors[0],
                                alert_temp=clamp(alert_threshold, 0, 100),
                                color3=alert_color)
         self._write([0x23, 0])
     elif mode == 'blackout':  # stronger than just 'off', suppresses alerts and rainbow
         self._configure_device(blackout=True,
                                alert_temp=clamp(alert_threshold, 0, 100),
                                color3=alert_color)
     else:
         raise KeyError(f'unknown lighting mode {mode}')
     self._end_transaction_and_read()
     if non_volatile:
         self._persist()
Esempio n. 2
0
 def set_fixed_speed(self, channel, duty, **kwargs):
     """Set channel to a fixed speed duty."""
     duty = clamp(duty, _MIN_FAN_DUTY, 100)
     _LOGGER.info('ensuring fan control is in software mode')
     self._set_fan_control_mode(FanControlMode.SOFTWARE)
     _LOGGER.info('setting fan PWM duty to %d%%', duty)
     self._exec(WriteBit.WRITE, CMD.FAN_COMMAND_1, [duty])
Esempio n. 3
0
 def set_fixed_speed(self, channel, duty, **kwargs):
     """Set channel to a fixed speed duty."""
     channel = channel.lower()
     if channel.startswith('fan'):
         duty = clamp(duty, 0, 100)
         # Need to write curve for each fan in channel
         for i in self._get_fan_indexes(channel):
             _LOGGER.info('setting speed for fan %d to %d', i + 1, duty)
             self._write([_PRO_CMD_WRITE_FAN_SPEED, i, duty])
             self._end_transaction_and_read(32)
     elif channel == 'pump':
         # Fixed speed pump is not supported, so set guess a curve from the duty value given
         # less than 50 == quiet (0), less than 80 == balanced (1), greater than 80 == proformance (2)
         # arbitrary values are arbitrary.
         # The values used for duty come from  OCL, but they way they build the enum used is not 100% clear
         if duty < 50:
             duty = 0
         elif duty < 80:
             duty = 1
         else:
             duty = 2
         self._write([_PRO_CMD_WRITE_PUMP_MODE, duty])
         msg = self._end_transaction_and_read(5)
         _LOGGER.debug('pump speed response %s',
                       ' '.join(format(i, '02x') for i in msg))
         if (msg[0] != _PRO_CMD_WRITE_PUMP_MODE or msg[1] != 0x12
                 or msg[2] != 0x34):
             raise ValueError(
                 'bad response on pump mode for CorsairAsetekPro')
         # OCL Reads the mode after setting it, so do the same
         self._write([_PRO_CMD_READ_PUMP_MODE])
         self._end_transaction_and_read(4)
     else:
         raise KeyError(f'Unknow channel: {channel}')
Esempio n. 4
0
 def _send_set_cooling(self):
     assert len(self._fan_names) <= 2, 'cannot yet fit all fan data'
     data = bytearray(_SET_COOLING_DATA_LENGTH)
     data[0: len(_SET_COOLING_DATA_PREFIX)] = _SET_COOLING_DATA_PREFIX
     data[_PROFILE_LENGTH_OFFSET] = _PROFILE_LENGTH
     for fan, (imode, iduty, iprofile) in zip(self._fan_names, _FAN_OFFSETS):
         mode = _FanMode(self._data.load(f'{fan}_mode', of_type=int))
         if mode is _FanMode.FIXED_DUTY:
             stored = self._data.load(f'{fan}_duty', of_type=int, default=100)
             duty = clamp(stored, 0, 100)
             data[iduty] = fraction_of_byte(percentage=duty)
             _LOGGER.info('setting %s to %i%% duty cycle', fan, duty)
         elif mode is _FanMode.CUSTOM_PROFILE:
             stored = self._data.load(f'{fan}_profile', of_type=list, default=[])
             profile = _prepare_profile(stored)  # ensures correct len(profile)
             pairs = ((temp, fraction_of_byte(percentage=duty)) for temp, duty in profile)
             data[iprofile: iprofile + _PROFILE_LENGTH * 2] = itertools.chain(*pairs)
             _LOGGER.info('setting %s to follow profile %r', fan, profile)
         else:
             raise ValueError(f'Unsupported fan {mode}')
         data[imode] = mode.value
     pump_mode = _PumpMode(self._data.load('pump_mode', of_type=int))
     data[_PUMP_MODE_OFFSET] = pump_mode.value
     _LOGGER.info('setting pump mode to %s', pump_mode.name.lower())
     return self._send_command(_FEATURE_COOLING, _CMD_SET_COOLING, data=data)
Esempio n. 5
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)
Esempio n. 6
0
    def _generate_cooling_payload(self, fan_names):

        data = bytearray(_SET_COOLING_DATA_LENGTH)
        data[0:len(_SET_COOLING_DATA_PREFIX)] = _SET_COOLING_DATA_PREFIX
        data[_PROFILE_LENGTH_OFFSET] = _PROFILE_LENGTH

        for fan, (imode, iduty, iprofile) in zip(fan_names, _FAN_OFFSETS):
            mode = _FanMode(self._data.load(f'{fan}_mode', of_type=int))
            if mode is _FanMode.FIXED_DUTY:
                stored = self._data.load(f'{fan}_duty',
                                         of_type=int,
                                         default=100)
                duty = clamp(stored, 0, 100)
                data[iduty] = fraction_of_byte(percentage=duty)
                _LOGGER.info('setting %s to %d%% duty cycle', fan, duty)
            elif mode is _FanMode.CUSTOM_PROFILE:
                stored = self._data.load(f'{fan}_profile',
                                         of_type=list,
                                         default=[])
                profile = _prepare_profile(
                    stored)  # ensures correct len(profile)
                pairs = ((temp, fraction_of_byte(percentage=duty))
                         for temp, duty in profile)
                data[iprofile:iprofile +
                     _PROFILE_LENGTH * 2] = itertools.chain(*pairs)
                _LOGGER.info('setting %s to follow profile %r', fan, profile)
            else:
                raise ValueError(f'unsupported fan {mode}')
            data[imode] = mode.value

        return data
Esempio n. 7
0
    def set_color(self, channel, mode, colors, speed='normal', **kwargs):
        """Set the color mode for a specific channel.

        Up to eight individual channels are available, named 'led1' through
        'led8'.  In addition to these, the 'sync' channel can be used to apply
        the same settings to all channels.

        The table bellow summarizes the available channels.

        | Mode         | Colors required | Speed is customizable |
        | ------------ | --------------- | --------------------- |
        | off          |            zero |                    no |
        | fixed        |             one |                    no |
        | pulse        |             one |                   yes |
        | flash        |             one |                   yes |
        | double-flash |             one |                   yes |
        | color-cycle  |            zero |                   yes |

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

        `speed`, when supported by the `mode`, can be one of: `slowest`,
        `slower`, `normal` (default), `faster`, `fastest` or `ludicrous`.
        """

        mode = _COLOR_MODES[mode]
        colors = iter(colors)

        if mode.takes_color:
            try:
                r, g, b = next(colors)
                single_color = (b, g, r)
            except StopIteration:
                raise ValueError(f'one color required for mode={mode.name}') from None
        else:
            single_color = (0, 0, 0)
        remaining = sum(1 for _ in colors)
        if remaining:
            _LOGGER.warning('too many colors for mode=%s, dropping %d', mode.name, remaining)

        brightness = clamp(100, 0, mode.max_brightness)  # hardcode this for now
        data = [_REPORT_ID, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, mode.value, brightness, 0x00]
        data += single_color
        data += [0x00, 0x00, 0x00, 0x00, 0x00]
        if mode.speed_values:
            data += mode.speed_values[speed]
        else:
            data += [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
        data += [0x00, 0x00, mode.cycle_count, int(mode.pulses), mode.flash_count]

        if channel == 'sync':
            selected_channels = _COLOR_CHANNELS.values()
        else:
            selected_channels = (_COLOR_CHANNELS[channel],)
        for addr1, addr2 in selected_channels:
            data[1:3] = addr1, addr2
            self._send_feature_report(data)
        self._execute_report()
Esempio n. 8
0
 def set_fixed_speed(self, channel, duty, **kwargs):
     """Set channel to a fixed speed duty."""
     if channel == 'fan':
         # While devices seem to recognize a specific channel for fixed fan
         # speeds (mtype == 0x12), its use can later conflict with custom
         # profiles.
         # Note for a future self: the conflict can be cleared with
         # *another* call to initialize(), i.e.  with another
         # configuration command.
         LOGGER.info('using a flat profile to set %s to a fixed duty',
                     channel)
         self.set_speed_profile(channel,
                                [(0, duty),
                                 (_CRITICAL_TEMPERATURE - 1, duty)])
         return
     mtype, dmin, dmax = _FIXED_SPEED_CHANNELS[channel]
     duty = clamp(duty, dmin, dmax)
     total_levels = _MAX_PUMP_SPEED_CODE - _MIN_PUMP_SPEED_CODE + 1
     level = round((duty - dmin) / (dmax - dmin) * total_levels)
     effective_duty = round(dmin + level * (dmax - dmin) / total_levels)
     LOGGER.info('setting %s PWM duty to %i%% (level %i)', channel,
                 effective_duty, level)
     self._begin_transaction()
     self._write([mtype, _MIN_PUMP_SPEED_CODE + level])
     self._end_transaction_and_read()
Esempio n. 9
0
 def set_fixed_speed(self, channel, duty, **kwargs):
     """Set channel to a fixed speed duty."""
     mtype, dmin, dmax = _LEGACY_FIXED_SPEED_CHANNELS[channel]
     duty = clamp(duty, dmin, dmax)
     self._data.store(f'{channel}_duty', duty)
     self._set_all_fixed_speeds()
     _warn_on_unsupported_option(**kwargs)
Esempio n. 10
0
 def _set_all_fixed_speeds(self):
     self._begin_transaction()
     for channel in ['pump', 'fan']:
         mtype, dmin, dmax = _LEGACY_FIXED_SPEED_CHANNELS[channel]
         duty = clamp(self._data.load_int('{}_duty'.format(channel), default=dmax), dmin, dmax)
         LOGGER.info('setting %s duty to %i%%', channel, duty)
         self._write([mtype, duty])
     return self._end_transaction_and_read()
Esempio n. 11
0
 def set_instantaneous_speed(self, channel, duty, **kwargs):
     """Set channel to speed, but do not ensure persistence."""
     if not self.supports_cooling:
         raise NotSupportedByDevice()
     cbase, dmin, dmax = _SPEED_CHANNELS[channel]
     duty = clamp(duty, dmin, dmax)
     LOGGER.info('setting %s PWM duty to %i%%', channel, duty)
     self._write([0x2, 0x4d, cbase & 0x70, 0, duty])
Esempio n. 12
0
 def set_color(self, channel, mode, colors, time_per_color=None, time_off=None,
               alert_threshold=_HIGH_TEMPERATURE, alert_color=[255, 0, 0],
               **kwargs):
     """Set the color mode for a specific channel."""
     # keyword arguments may have been forwarded from cli args and need parsing
     colors = list(colors)
     self._begin_transaction()
     if mode == 'fading':
         if time_per_color is None:
             time_per_color = 5
         self._configure_device(fading=True, color1=colors[0], color2=colors[1],
                                interval1=clamp(time_per_color, 1, 255),
                                alert_temp=clamp(alert_threshold, 0, 100), color3=alert_color)
     elif mode == 'blinking':
         if time_per_color is None:
             time_per_color = 1
         if time_off is None:
             time_off = time_per_color
         self._configure_device(blinking=True, color1=colors[0],
                                interval1=clamp(time_off, 1, 255),
                                interval2=clamp(time_per_color, 1, 255),
                                alert_temp=clamp(alert_threshold, 0, 100), color3=alert_color)
     elif mode == 'fixed':
         self._configure_device(color1=colors[0], alert_temp=clamp(alert_threshold, 0, 100),
                                color3=alert_color)
     elif mode == 'blackout':  # stronger than just 'off', suppresses alerts and rainbow
         self._configure_device(blackout=True, alert_temp=clamp(alert_threshold, 0, 100),
                                color3=alert_color)
     else:
         raise KeyError('Unsupported lighting mode {}'.format(mode))
     self._end_transaction_and_read()
Esempio n. 13
0
def _prepare_profile(original):
    clamped = ((temp, clamp(duty, 0, 100)) for temp, duty in original)
    normal = normalize_profile(clamped, _CRITICAL_TEMPERATURE)
    missing = _PROFILE_LENGTH - len(normal)
    if missing < 0:
        raise ValueError(f'too many points in profile (remove {-missing})')
    if missing > 0:
        normal += missing * [(_CRITICAL_TEMPERATURE, 100)]
    return normal
Esempio n. 14
0
def _prepare_profile(original, critcalTempature):
    clamped = ((temp, clamp(duty, 0, _MAX_FAN_RPM)) for temp, duty in original)
    normal = normalize_profile(clamped, critcalTempature, _MAX_FAN_RPM)
    missing = _PROFILE_LENGTH - len(normal)
    if missing < 0:
        raise ValueError(f'too many points in profile (remove {-missing})')
    if missing > 0:
        normal += missing * [(critcalTempature, _MAX_FAN_RPM)]
    return normal
Esempio n. 15
0
 def set_fixed_speed(self, channel, duty, **kwargs):
     """Set channel to a fixed speed."""
     if channel == 'sync':
         selected_channels = self._speed_channels
     else:
         selected_channels = {channel: self._speed_channels[channel]}
     for cname, (cid, dmin, dmax) in selected_channels.items():
         duty = clamp(duty, dmin, dmax)
         _LOGGER.info('setting %s duty to %i%%', cname, duty)
         self._write_fixed_duty(cid, duty)
Esempio n. 16
0
    def set_fixed_speed(self, channel, duty, **kwargs):
        """Set channel to a fixed speed duty."""

        channel = channel.lower()
        duty = clamp(duty, 0, 100)

        for i in self._fan_indexes(channel):
            _LOGGER.info('setting speed for fan %d to %d', i + 1, duty)
            self._post([_CMD_WRITE_FAN_SPEED, i, duty], read_length=32)
        self.device.release()
Esempio n. 17
0
 def set_speed_profile(self, channel, profile, **kwargs):
     """Set channel to use a speed profile."""
     cid, dmin, dmax = self._speed_channels[channel]
     header = [0x72, cid, 0x00, 0x00]
     norm = normalize_profile(profile, _CRITICAL_TEMPERATURE)
     stdtemps = list(range(20, _CRITICAL_TEMPERATURE + 1))
     interp = [clamp(interpolate_profile(norm, t), dmin, dmax) for t in stdtemps]
     for temp, duty in zip(stdtemps, interp):
         _LOGGER.info('setting %s PWM duty to %d%% for liquid temperature >= %d°C',
                      channel, duty, temp)
     self._write(header + interp)
Esempio n. 18
0
 def set_fixed_speed(self, channel, duty, **kwargs):
     """Set channel to a fixed speed duty."""
     channel = channel.lower()
     if channel.startswith('fan'):
         duty = clamp(duty, 0, 100)
         # Need to write curve for each fan in channel
         for i in self._get_fan_indexes(channel):
             _LOGGER.info('setting speed for fan %d to %d', i + 1, duty)
             self._write([_CMD_WRITE_FAN_SPEED, i, duty])
             self._end_transaction_and_read(32)
     elif channel == 'pump':
         raise NotSupportedByDevice()
     else:
         raise KeyError(f'Unknow channel: {channel}')
Esempio n. 19
0
 def set_speed_profile(self, channel, profile, **kwargs):
     """Set channel to use a speed profile."""
     if not self.supports_cooling_profiles:
         raise NotImplementedError()
     cbase, dmin, dmax = _SPEED_CHANNELS[channel]
     # ideally we could just call normalize_profile (optionally followed by autofill_profile),
     # but Kraken devices currently require the same set of temperatures on both channels
     stdtemps = range(20, 62, 2)
     tmp = normalize_profile(profile, _CRITICAL_TEMPERATURE)
     norm = [(t, interpolate_profile(tmp, t)) for t in stdtemps]
     for i, (temp, duty) in enumerate(norm):
         duty = clamp(duty, dmin, dmax)
         LOGGER.info('setting %s PWM duty to %i%% for liquid temperature >= %i°C',
                      channel, duty, temp)
         self._write([0x2, 0x4d, cbase + i, temp, duty])
     self.device.release()
Esempio n. 20
0
 def set_speed_profile(self, channel, profile, **kwargs):
     """Set channel to use a speed profile."""
     if not self.supports_cooling_profiles:
         raise NotSupportedByDevice()
     norm = normalize_profile(profile, _CRITICAL_TEMPERATURE)
     # due to a firmware limitation the same set of temperatures must be
     # used on both channels; we reduce the number of writes by trimming the
     # interval and/or resolution to the most useful range
     stdtemps = list(range(20, 50)) + list(range(50, 60, 2)) + [60]
     interp = [(t, interpolate_profile(norm, t)) for t in stdtemps]
     cbase, dmin, dmax = _SPEED_CHANNELS[channel]
     for i, (temp, duty) in enumerate(interp):
         duty = clamp(duty, dmin, dmax)
         LOGGER.info('setting %s PWM duty to %i%% for liquid temperature >= %i°C',
                      channel, duty, temp)
         self._write([0x2, 0x4d, cbase + i, temp, duty])
Esempio n. 21
0
 def _prepare_profile(self, profile, min_duty, max_duty, max_points):
     opt = list(profile)
     size = len(opt)
     if size < 1:
         raise ValueError('At least one PWM point required')
     elif size > max_points:
         raise ValueError('Too many PWM points ({}), only 6 supported'.format(size))
     for i, (temp, duty) in enumerate(opt):
         opt[i] = (temp, clamp(duty, min_duty, max_duty))
     missing = max_points - size
     if missing:
         # Some issues were observed when padding with (0°C, 0%), though
         # they were hard to reproduce.  So far it *seems* that in some
         # instances the device will store the last "valid" profile index
         # somewhere, and would need another call to initialize() to clear
         # that up.  Padding with (CRIT, 100%) appears to avoid all issues,
         # at least within the reasonable range of operating temperatures.
         LOGGER.info('filling missing %i PWM points with (60°C, 100%%)', missing)
         opt = opt + [(_CRITICAL_TEMPERATURE, 100)]*missing
     return opt
Esempio n. 22
0
    def set_fixed_speed(self, channel, duty, **kwargs):
        channels = CommanderCore._parse_channels(channel)

        with self._wake_device_context():
            # Set hardware speed mode
            res = self._read_data(_MODE_HW_SPEED_MODE, _DATA_TYPE_HW_SPEED_MODE)
            device_count = res[0]

            data = bytearray(res[0:device_count + 1])
            for chan in channels:
                data[chan + 1] = 0x00  # Set the device's hardware mode to fixed percent
            self._write_data(_MODE_HW_SPEED_MODE, _DATA_TYPE_HW_SPEED_MODE, data)

            # Set speed
            res = self._read_data(_MODE_HW_FIXED_PERCENT, _DATA_TYPE_HW_FIXED_PERCENT)
            device_count = res[0]
            data = bytearray(res[0:device_count * 2 + 1])
            duty_le = int.to_bytes(clamp(duty, 0, 100), length=2, byteorder="little", signed=False)
            for chan in channels:
                i = chan * 2 + 1
                data[i: i + 2] = duty_le  # Update the device speed
            self._write_data(_MODE_HW_FIXED_PERCENT, _DATA_TYPE_HW_FIXED_PERCENT, data)
Esempio n. 23
0
    def set_fixed_speed(self, channel, duty, **kwargs):
        """Set fan or fans to a fixed speed duty.

        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.

        Different commands for sending fixed percent (0x23) and fixed rpm (0x24)
        Probably want to use fixed percent for this untill the rpm flag is enabled.
        Can only send one fan command at a time, if fan mode is unset will need to send 6?
        messages (or 1 per enabled fan)
        """

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

        duty = clamp(duty, 0, 100)
        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:
                self._send_command(_CMD_SET_FAN_DUTY, [fan, duty])
Esempio n. 24
0
 def set_fixed_speed(self, channel, duty, **kwargs):
     """Set channel to a fixed speed duty."""
     duty = clamp(duty, 20, 100)
     self.set_speed_profile(channel, [(i, duty) for i in range(20, 60)])
     self.device.release()
Esempio n. 25
0
    def set_color(self,
                  channel,
                  mode,
                  colors,
                  direction='forward',
                  speed='medium',
                  start_led=1,
                  maximum_leds=1,
                  **kwargs):
        """Set the color of each LED.

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

        | Channel  | Mode        | Num colors |
        | -------- | ----------- | ---------- |
        | led      | off         |          0 |
        | led      | fixed       |          1 |
        | led      | color_shift |          2 |
        | led      | color_pulse |          2 |
        | led      | color_wave  |          2 |
        | led      | visor       |          2 |
        | led      | blink       |          2 |
        | led      | marquee     |          1 |
        | led      | sequential  |          1 |
        | led      | rainbow     |          0 |
        | led      | rainbow2    |          0 |
        """

        # a special mode to clear the current led settings.
        # this is usefull if the the user wants to use a led mode for multiple devices
        if mode == 'clear':
            self._data.store('saved_effects', None)
            return

        colors = list(colors)
        expanded = colors[:3]
        c = itertools.chain(*((r, g, b) for r, g, b in expanded))
        colors = list(c)

        direction = map_direction(direction, _LED_DIRECTION_FORWARD,
                                  _LED_DIRECTION_BACKWARD)
        speed = _LED_SPEED_SLOW if speed == 'slow' else _LED_SPEED_FAST if speed == 'fast' else _LED_SPEED_MEDIUM
        start_led = clamp(start_led, 1, 204) - 1
        num_leds = clamp(maximum_leds, 1, 204 - start_led - 1)
        random_colors = 0x00 if mode == 'off' or len(colors) != 0 else 0x01
        mode_val = _MODES.get(mode, -1)

        if mode_val == -1:
            raise ValueError(f'mode "{mode}" is not valid')

        # FIXME clears on 'off', while the docs only mention this behavior for 'clear'
        saved_effects = [] if mode == 'off' else self._data.load(
            'saved_effects', default=[])

        for led_channel in self._get_hw_led_channels(channel):

            lighting_effect = {
                'channel': led_channel,
                'start_led': start_led,
                'num_leds': num_leds,
                'mode': mode_val,
                'speed': speed,
                'direction': direction,
                'random_colors': random_colors,
                'colors': colors
            }

            saved_effects += [lighting_effect]

            # check to make sure that too many LED effects are not being sent.
            # the max seems to be 8 as found here https://github.com/liquidctl/liquidctl/issues/154#issuecomment-762372583
            if len(saved_effects) > 8:
                _LOGGER.warning(
                    f'too many lighting effects. Run `liquidctl set {channel} color clear` to reset the effect'
                )
                return

            # start sending the led commands
            self._send_command(_CMD_RESET_LED_CHANNEL, [led_channel])
            self._send_command(_CMD_BEGIN_LED_EFFECT, [led_channel])
            self._send_command(_CMD_SET_LED_CHANNEL_STATE, [led_channel, 0x01])

        # FIXME clears on 'off', while the docs only mention this behavior for 'clear'
        self._data.store('saved_effects',
                         None if mode == 'off' else saved_effects)

        for effect in saved_effects:
            config = [
                effect.get('channel'),
                effect.get('start_led'),
                effect.get('num_leds'),
                effect.get('mode'),
                effect.get('speed'),
                effect.get('direction'),
                effect.get('random_colors'), 0xff
            ] + effect.get('colors')
            self._send_command(_CMD_LED_EFFECT, config)

        self._send_command(_CMD_LED_COMMIT, [0xff])
Esempio n. 26
0
    def set_color(self,
                  channel,
                  mode_str,
                  colors,
                  direction='forward',
                  speed='medium',
                  start_led=1,
                  maximum_leds=1,
                  **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        | Num colors |
        | -------- | ----------- | ---------- |
        | led      | off         |          0 |
        | led      | fixed       |          1 |
        | led      | color_shift |          2 |
        | led      | color_pulse |          2 |
        | led      | color_wave  |          2 |
        | led      | visor       |          2 |
        | led      | blink       |          2 |
        | led      | marquee     |          1 |
        | led      | sequential  |          1 |
        | led      | rainbow     |          0 |
        | led      | rainbow2    |          0 |

        """

        # a special mode to clear the current led settings.
        # this is usefull if the the user wants to use a led mode for multiple devices
        if mode_str == 'clear':
            self._data.store('saved_effects', None)
            return

        colors = list(colors)
        expanded = colors[:3]
        c = itertools.chain(*((r, g, b) for r, g, b in expanded))
        colors = list(c)

        direction = direction.lower()
        speed = speed.lower()
        channel = channel.lower()
        mode = mode_str.lower()

        # default to channel 1 if channel 2 is not specified.
        led_channel = 1 if channel == 'led2' else 0

        direction = _LED_DIRECTION_FORWARD if direction == 'forward' else _LED_DIRECTION_BACKWARD
        speed = _LED_SPEED_SLOW if speed == 'slow' else _LED_SPEED_FAST if speed == 'fast' else _LED_SPEED_MEDIUM
        start_led = clamp(start_led, 1, 96) - 1
        num_leds = clamp(
            maximum_leds, 1, 96 - start_led - 1
        )  # there is a current firmware limitation of 96 led's per channel
        random_colors = 0x00 if mode_str == 'off' or len(colors) != 0 else 0x01
        mode = _MODES.get(mode, -1)

        if mode == -1:
            raise ValueError(f'mode "{mode_str}" is not valid')

        lighting_effect = {
            'channel': led_channel,
            'start_led': start_led,
            'num_leds': num_leds,
            'mode': mode,
            'speed': speed,
            'direction': direction,
            'random_colors': random_colors,
            'colors': colors
        }

        saved_effects = [] if mode_str == 'off' else self._data.load(
            'saved_effects', default=[])
        saved_effects += [lighting_effect]

        self._data.store('saved_effects',
                         None if mode_str == 'off' else saved_effects)

        # start sending the led commands
        self._send_command(_CMD_RESET_LED_CHANNEL, [led_channel])
        self._send_command(_CMD_BEGIN_LED_EFFECT, [led_channel])
        self._send_command(_CMD_SET_LED_CHANNEL_STATE, [led_channel, 0x01])

        for effect in saved_effects:
            config = [
                effect.get('channel'),
                effect.get('start_led'),
                effect.get('num_leds'),
                effect.get('mode'),
                effect.get('speed'),
                effect.get('direction'),
                effect.get('random_colors'), 0xff
            ] + effect.get('colors')
            self._send_command(_CMD_LED_EFFECT, config)

        self._send_command(_CMD_LED_COMMIT, [0xff])
Esempio n. 27
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)
Esempio n. 28
0
    def _send_set_cooling(self):
        for fan in self._fan_names:
            fanIndex = 0
            if fan == 'fan1':
                fanIndex = 0
            else:
                fanIndex = 1

            mode = _FanMode(self._data.load(f'{fan}_mode', of_type=int))

            if mode is _FanMode.FIXED_DUTY:
                dataPackages =  list()
                stored = self._data.load(f'{fan}_duty', of_type=int, default=100)
                duty = clamp(stored, 0, 100)
                dataPackages.append(self._build_data_package(_COMMAND_FAN_SELECT, _OP_CODE_WRITE_ONE_BYTE, params=bytes([fanIndex])))
                dataPackages.append(self._build_data_package(_COMMAND_FAN_MODE, _OP_CODE_WRITE_ONE_BYTE, params=bytes([mode.value])))
                dataPackages.append(self._build_data_package(_COMMAND_FAN_FIXED_PWM, _OP_CODE_WRITE_ONE_BYTE, params=bytes([fraction_of_byte(percentage=duty)])))
                LOGGER.info('setting %s to %d%% duty cycle', fan, duty)
                self._send_command(dataPackages)

            elif mode is _FanMode.CUSTOM_PROFILE:
                stored = self._data.load(f'{fan}_profile', of_type=list, default=[])
                profile = _prepare_profile(stored)  # ensures correct len(profile)
                pairs = ((temp, fraction_of_byte(percentage=duty)) for temp, duty in profile)

                fanTemperatureData = list()
                fanTemperatureData.append(0x0A) # 'magical' 0x0A in front of curve definition packages
                
                fanDutyData = list()
                fanDutyData.append(0x0A) # 'magical' 0x0A in front of curve definition packages
                
                for temp, duty in profile:
                    fanTemperatureData.append(0x00)
                    fanTemperatureData.append(temp)
                    rpm = duty * _MAX_FAN_RPM / 100
                    fanDutyData.append(int(rpm % 255))
                    fanDutyData.append(int(rpm - (rpm % 255)) >> 8)
                
                # Send temperature profile
                self._send_command([self._build_data_package(_COMMAND_FAN_TEMP_TABLE, _OP_CODE_WRITE_THREE_BYTES, params=bytes(fanTemperatureData))])
                # Send duty cycle Profile
                self._send_command([self._build_data_package(_COMMAND_FAN_RPM_TABLE, _OP_CODE_WRITE_THREE_BYTES, params=bytes(fanDutyData))])

                # Change mode to custom Profile
                dataPackages =  list()
                dataPackages.append(self._build_data_package(_COMMAND_FAN_SELECT, _OP_CODE_WRITE_ONE_BYTE, params=bytes([fanIndex])))
                dataPackages.append(self._build_data_package(_COMMAND_FAN_MODE, _OP_CODE_WRITE_ONE_BYTE, params=bytes([mode.value])))
                self._send_command(dataPackages)

                LOGGER.info('setting %s to follow profile %r', fan, profile)
            else:
                raise ValueError(f'Unsupported fan {mode}')
        
        pump_mode = _PumpMode(self._data.load('pump_mode', of_type=int))
        
        dataPackages =  list()
        dataPackages.append(self._build_data_package(_COMMAND_FAN_SELECT, _OP_CODE_WRITE_ONE_BYTE, params=bytes([_PUMP_INDEX])))
        if pump_mode == _PumpMode.QUIET:
            dataPackages.append(self._build_data_package(_COMMAND_FAN_FIXED_RPM, _OP_CODE_WRITE_TWO_BYTES, params=bytes(_PUMP_DEFAULT_QUIET)))
        elif pump_mode == _PumpMode.EXTREME:
            dataPackages.append(self._build_data_package(_COMMAND_FAN_FIXED_RPM, _OP_CODE_WRITE_TWO_BYTES, params=bytes(_PUMP_DEFAULT_EXTREME)))
        self._send_command(dataPackages)
        
        LOGGER.info('setting pump mode to %s', pump_mode.name.lower())
Esempio n. 29
0
 def set_fixed_speed(self, channel, duty, **kwargs):
     """Set channel to a fixed speed duty."""
     mtype, dmin, dmax = _LEGACY_FIXED_SPEED_CHANNELS[channel]
     duty = clamp(duty, dmin, dmax)
     self._data.store_int('{}_duty'.format(channel), duty)
     self._set_all_fixed_speeds()
Esempio n. 30
0
 def _write_color_change_speed(self, mode, speed):
     """Send the speed to cycle colors on the RGB pump"""
     speed = clamp(speed, 1, 3)
     self._write([0x53, _PRO_SPEEDS[mode][int(speed) - 1]])
     self._end_transaction_and_read(3)