Ejemplo n.º 1
0
 def set_speed_profile(self, channel, profile, **kwargs):
     """Set channel to use a speed profile."""
     if channel != 'pump':
         assert False, 'kraken X3 devices only support changing pump speeds'
     header = [0x72, 0x01, 0x00, 0x00]
     norm = normalize_profile(profile, _CRITICAL_TEMPERATURE)
     interp = [(interpolate_profile(norm, t)) for t in range(20, 60)]
     LOGGER.debug('setting pump curve: %s',
                  [(num + 20, duty) for (num, duty) in enumerate(interp)])
     self._write(header + interp)
Ejemplo n.º 2
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)
Ejemplo n.º 3
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])
Ejemplo n.º 4
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()
Ejemplo n.º 5
0
def control(device, channels, profiles, sensors, update_interval):
    LOGGER.info('device: %s on bus %s and address %s', device.description, device.bus, device.address)
    for channel, profile, sensor in zip(channels, profiles, sensors):
        LOGGER.info('channel: %s following profile %s on %s', channel, str(profile), sensor)

    averages = [None] * len(channels)
    cutoff_freq = 1 / update_interval / 10
    alpha = 1 - math.exp(-2 * math.pi * cutoff_freq)
    LOGGER.info('update interval: %d s; cutoff frequency (low-pass): %.2f Hz; ema alpha: %.2f',
                update_interval, cutoff_freq, alpha)

    try:
        # more efficient and safer API, but only supported by very few devices
        apply_duty = device.set_instantaneous_speed
    except AttributeError:
        apply_duty = device.set_fixed_speed

    LOGGER.info('starting...')
    failures = 0
    while True:
        try:
            sensor_data = read_sensors(device)
            for i, (channel, profile, sensor) in enumerate(zip(channels, profiles, sensors)):
                # compute the exponential moving average (ema), used as a low-pass filter (lpf)
                ema = averages[i]
                sample = sensor_data[sensor]
                if ema is None:
                    ema = sample
                else:
                    ema = alpha * sample + (1 - alpha) * ema
                averages[i] = ema

                # interpolate on sensor ema and apply corresponding duty
                duty = interpolate_profile(profile, ema)
                LOGGER.info('%s control: lpf(%s) = lpf(%.1f°C) = %.1f°C => duty := %d%%',
                            channel, sensor, sample, ema, duty)
                apply_duty(channel, duty)
            failures = 0
        except Exception as err:
            failures += 1
            LOGGER.error(err)
            if failures >= MAX_FAILURES:
                LOGGER.critical('too many failures in a row: %d', failures)
                raise
        time.sleep(update_interval)