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()
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])
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}')
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)
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)
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
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()
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()
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)
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()
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])
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()
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
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
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)
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()
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)
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}')
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()
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])
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
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)
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])
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()
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])
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])
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)
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())
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()
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)