Fancontrol

Aus wiki.frank-wulf.de
Version vom 28. Dezember 2017, 23:38 Uhr von Wulf (Diskussion | Beiträge) (C program fwcontrol.c)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Zur Navigation springen Zur Suche springen

This program monitors CPU and hard drive temperatures and changes fan speeds accordingly.

C program fwcontrol.c

// Author:  Frank Wulf
// Version: 1.1 (2017-12-28)
//
// This program can be used to control fan speeds automatically. It runs as
// a daemon and sets the PWM values of the fans based on CPU temperature
// and/or hard drive temperatures. The behaviour of this program can be
// configured in file /etc/fwcontrol.conf.
//
// Version history:
// 1.0   2017-11-17   Initial release
// 1.1   2017-12-28   Code optimized

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <scsi/sg.h>

#define BUFSIZE 256
#define MAX_FANS 10
#define MAX_STEP 10
#define MAX_HDD 32

#define SMART_BUFFER_SIZE 512
#define SMART_SENSE_BUFFER_SIZE 32
#define SMART_CMD_LENGTH 12

enum {
    ATA_OP_CHECKPOWERMODE1 = 0xe5,
    ATA_OP_CHECKPOWERMODE2 = 0x98,
    ATA_USING_LBA = (1 << 6),
    ATA_STAT_DRQ = (1 << 3),
    ATA_STAT_ERR = (1 << 0),
};

enum {
    SG_ATA_16 = 0x85,
    SG_ATA_16_LEN = 16,
    SG_ATA_PROTO_NON_DATA = (3 << 1)
};

enum {SG_CDB2_CHECK_COND = (1 << 5)};

enum {CPU, HDD};

struct s_fan {
    char name[20];
    char control_by_cpu;
    char control_by_hdd;
    char pwm_enable[50];
    char pwm_write[50];
    char cpu_input[50];
    unsigned char idle_pwm;
    short stop_delay;
    short decr_delay;
    short interval_cpu;
    short interval_hdd;
    char interpolate_cpu;
    char interpolate_hdd;
    short hyst_cpu;
    short hyst_hdd;
    short count_scan_hdd;
    short count_step_cpu;
    short count_step_hdd;
    time_t next_cpu_check;
    time_t next_hdd_check;
    char first_check;
    short cpu_temp;
    short hdd_temp;
    unsigned char cpu_pwm;
    unsigned char hdd_pwm;
    unsigned char actual_pwm;
    time_t change_time;
    time_t min_decr_time;
    time_t min_stop_time;
} fan[MAX_FANS];

struct s_temp_to_pwm {
    short temp;
    unsigned char pwm;
} data_pwm[2][MAX_FANS][MAX_STEP];

struct s_scan_hdd {
    char name[10];
} scan_hdd[MAX_FANS][MAX_HDD];

struct s_hdd_temp {
    char name[10];
    short temp;
} hdd_buffer[MAX_HDD];

short count_fans = 0;
time_t now;
time_t next_check;

typedef void (*sighandler_t)(int);

static sighandler_t
handle_signal(int sig_nr, sighandler_t signalhandler) {
    struct sigaction new_sig, old_sig;
    new_sig.sa_handler = signalhandler;
    sigemptyset(&new_sig.sa_mask);
    new_sig.sa_flags = SA_RESTART;
    if (sigaction(sig_nr, &new_sig, &old_sig) < 0)
        return SIG_ERR;
    return old_sig.sa_handler;
}

static void start_daemon(const char *log_name, int facility) {
    int i;
    pid_t pid;
    /* Fork off parent process */
    if ((pid = fork()) != 0)
        exit(EXIT_FAILURE);
    /* Create new SID for child process */
    if (setsid() < 0)
        exit(EXIT_FAILURE);
    /* Ignore signal SIGHUP */
    handle_signal(SIGHUP, SIG_IGN);
    /* Fork off child process */
    if ((pid = fork()) != 0)
        exit(EXIT_FAILURE);
    /* Change working directory */
    chdir("/");
    /* Change file mode mask */
    umask(0);
    /* Close all file descriptors */
    for (i = sysconf(_SC_OPEN_MAX); i > 0; i--)
        close(i);
    openlog(log_name, LOG_PID | LOG_CONS | LOG_NDELAY, facility );
}

int read_conf(void) {
    FILE *fp;
    char *name, *value, buf[BUFSIZE];
    int i = -1, j, k;

    now = time(NULL);
    if ((fp = fopen("/etc/fwcontrol.conf", "r")) == NULL) {
        syslog(LOG_ERR, "Error opening configuration file\n");
        return -1;
    }

    while (fgets(buf, BUFSIZE, fp) != NULL) {
        if (buf[0] == '[') {
            if (++i >= MAX_FANS)
                break;
            count_fans = i + 1;
            strcpy(fan[i].name, strtok(buf, "[]\n"));
            fan[i].first_check = 1;
            k = 0;
        } else if (i >= 0) {
            name = strtok(buf, "=");
            value = strtok(NULL, "=");
            value = strtok(value, "\n");
            if (strcmp(name, "control_by_cpu") == 0) {
                fan[i].control_by_cpu = atoi(value);
                fan[i].next_cpu_check = now;
            } else if (strcmp(name, "control_by_hdd") == 0) {
                fan[i].control_by_hdd = atoi(value);
                fan[i].next_hdd_check = now;
            } else if (strcmp(name, "pwm_enable") == 0) {
                strcpy(fan[i].pwm_enable, value);
            } else if (strcmp(name, "pwm_write") == 0) {
                strcpy(fan[i].pwm_write, value);
            } else if (strcmp(name, "cpu_input") == 0) {
                strcpy(fan[i].cpu_input, value);
            } else if (strcmp(name, "stop_delay") == 0) {
                fan[i].stop_delay = atoi(value);
            } else if (strcmp(name, "decrease_delay") == 0) {
                fan[i].decr_delay = atoi(value);
            } else if (strcmp(name, "interval_cpu") == 0) {
                fan[i].interval_cpu = atoi(value);
            } else if (strcmp(name, "interval_hdd") == 0) {
                fan[i].interval_hdd = atoi(value);
            } else if (strcmp(name, "interpolate_cpu") == 0) {
                fan[i].interpolate_cpu = atoi(value);
            } else if (strcmp(name, "interpolate_hdd") == 0) {
                fan[i].interpolate_hdd = atoi(value);
            } else if (strcmp(name, "hyst_cpu") == 0) {
                fan[i].hyst_cpu = atoi(value);
            } else if (strcmp(name, "hyst_hdd") == 0) {
                fan[i].hyst_hdd = atoi(value);
            } else if (strcmp(name, "idle_pwm") == 0) {
                fan[i].idle_pwm = atoi(value);
            } else if (strcmp(name, "scan_hdd") == 0) {
                value = strtok(value, ",");
                while ((value != NULL) && (++k <= MAX_HDD)) {
                    strcpy(scan_hdd[i][k-1].name, value);
                    value = strtok(NULL, ",");
                }
                fan[i].count_scan_hdd = k;
            } else if (strcmp(name, "temp_pwm_hdd") == 0) {
                j = 0;
                value = strtok(value, ",");
                while ((value != NULL) && (++j <= MAX_STEP)) {
                    data_pwm[HDD][i][j-1].temp = atoi(value);
                    value = strtok(NULL, ",");
                    data_pwm[HDD][i][j-1].pwm  = atoi(value);
                    value = strtok(NULL, ",");
                }
                fan[i].count_step_hdd = j;
            } else if (strcmp(name, "temp_pwm_cpu") == 0) {
                j = 0;
                value = strtok(value, ",");
                while ((value != NULL) && (++j <= MAX_STEP)) {
                    data_pwm[CPU][i][j-1].temp = atoi(value);
                    value = strtok(NULL, ",");
                    data_pwm[CPU][i][j-1].pwm  = atoi(value);
                    value = strtok(NULL, ",");
                }
                fan[i].count_step_cpu = j;
            }
        }
    }
    fclose(fp);

    return 0;
}

static inline int sgio_send_for_status(int fd, unsigned char cmd, unsigned char *rv) {
    unsigned char cdb[SG_ATA_16_LEN], sb[32], *desc, status, error;
    sg_io_hdr_t io_hdr;

    memset(&cdb, 0, sizeof(cdb));
    memset(&sb, 0, sizeof(sb));
    memset(&io_hdr, 0, sizeof(io_hdr));

    cdb[0] = SG_ATA_16;
    cdb[1] = SG_ATA_PROTO_NON_DATA;
    cdb[2] = SG_CDB2_CHECK_COND;
    cdb[13] = ATA_USING_LBA;
    cdb[14] = cmd;

    io_hdr.cmd_len = SG_ATA_16_LEN;
    io_hdr.interface_id = 'S';
    io_hdr.mx_sb_len = sizeof(sb);
    io_hdr.dxfer_direction = SG_DXFER_NONE;
    io_hdr.dxfer_len = 0;
    io_hdr.dxferp = NULL;
    io_hdr.cmdp = cdb;
    io_hdr.sbp = sb;
    io_hdr.pack_id = 0;
    io_hdr.timeout = 500; // Milliseconds

    if (ioctl(fd, SG_IO, &io_hdr) == -1) {
        syslog(LOG_ERR, "ioctl() failed (cmd %u, %s)\n", cmd, strerror(errno));
        return -1;
    }

    desc = sb + 8;
    status = desc[13];
    error = desc[3];
    if (rv)
        *rv = desc[5];

    if (status & (ATA_STAT_ERR | ATA_STAT_DRQ)) {
        syslog(LOG_ERR, "SG_IO command %u failed (status %u, error %u)\n",
          cmd, status, error);
        return -1;
    }

    return 0;
}

static inline char get_hdd_status(char *name) {
    int fd, ret;
    unsigned char state;

    if ((fd = open(name, O_RDONLY)) < 0) {
        syslog(LOG_ERR, "Error opening file %s!\n", name);
        return -1;
    }

    ret = 1;
    if (sgio_send_for_status(fd, ATA_OP_CHECKPOWERMODE1, &state) &&
        sgio_send_for_status(fd, ATA_OP_CHECKPOWERMODE2, &state))
        ret = 0;
    close(fd);

    return (state == 0) ? 0 : 1; // 0 = Sleeping, 1 = Running
}

static inline short get_hdd_temp(char *name) {
    short hdd_temp;
    int fd;
    unsigned char buffer[SMART_BUFFER_SIZE] = "";
    unsigned char sense_buffer[SMART_SENSE_BUFFER_SIZE];
    unsigned char smart_read_cdb[SMART_CMD_LENGTH] =
                  {0xa1, 0x0c, 0x0e, 0xd0, 1, 0, 0x4f, 0xc2, 0, 0xb0, 0, 0};

    sg_io_hdr_t io_hdr;

    if ((fd = open(name, O_RDONLY)) < 0) {
        syslog(LOG_ERR, "Error opening file %s!\n", name);
        return -1;
    }

    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    io_hdr.interface_id = 'S';
    io_hdr.cmd_len = SMART_CMD_LENGTH;
    io_hdr.mx_sb_len = SMART_SENSE_BUFFER_SIZE;
    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
    io_hdr.dxfer_len = SMART_BUFFER_SIZE;
    io_hdr.dxferp = buffer;
    io_hdr.cmdp = smart_read_cdb;
    io_hdr.sbp = sense_buffer;
    io_hdr.timeout = 500; // Milliseconds

    if (ioctl(fd, SG_IO, &io_hdr) < 0) {
        syslog(LOG_ERR, "ioctl() call for reading temperature failed\n");
        close(fd);
        return -1;
    }

    for (register int i = 2; i < 361; i+=12)
        if ((int)buffer[i] == 194) {
            hdd_temp = ((long long int)((buffer[i+5])|
                       (buffer[i+6]<<8)|
                       (buffer[i+7]<<16)|
                       (buffer[i+8]<<24)|
                       ((long long int)buffer[i+9]<<32)|
                       ((long long int)buffer[i+10]<<40))) & 0xFF;
            break;
        }
    close(fd);

    return hdd_temp;
}

static inline short get_cpu_temp(char *name) {
    FILE *fp;
    char buf[BUFSIZE];
    short cpu_temp;

    if ((fp = fopen(name, "r")) == NULL) {
        syslog(LOG_ERR, "Error opening file %s!\n", name);
        return -1;
    }
    if (fgets(buf, BUFSIZE, fp) != NULL)
        cpu_temp = atoi(buf) / 1000;
    fclose(fp);

    return cpu_temp;
}

int set_fan_mode(void) {
    FILE *fp;

    for (int i = 0; i < count_fans; i++) {
        if ((fp = fopen(fan[i].pwm_enable, "w")) == NULL) {
            syslog(LOG_ERR, "Error opening file %s!\n", fan[i].pwm_enable);
            return -1;
        }
        fprintf(fp, "1");
        fclose(fp);
    }

    return 0;
}

static inline int set_fan_speed(char *name, unsigned char value) {
    FILE *fp;

    if ((fp = fopen(name, "w")) == NULL) {
        syslog(LOG_ERR, "Error opening file %s!\n", name);
        return -1;
    }
    fprintf(fp, "%d", value);
    fclose(fp);

    return 0;
}

static inline unsigned char calc_pwm(char type, int fan, short temp, char interpolate,
short count_step) {
    unsigned char pwm = 0;
    short j = 0;

    while (temp >= data_pwm[type][fan][j].temp) {
        pwm = data_pwm[type][fan][j].pwm;
        if (++j >= count_step)
            break;
    }

    if ((interpolate == 1) && (j > 0) && (j < count_step))
        pwm = ((float)data_pwm[type][fan][j].pwm -
              data_pwm[type][fan][j-1].pwm) /
              (data_pwm[type][fan][j].temp - data_pwm[type][fan][j-1].temp) *
              (temp - data_pwm[type][fan][j-1].temp) +
              data_pwm[type][fan][j-1].pwm + 0.5;

    return pwm;
}

static inline int control_fan_speed(void) {
    struct stat device;
    unsigned char new_pwm;
    char sw_checked_cpu, sw_checked_hdd, sw_hdd_buffered, last_cpu_input[50];
    short cpu_temp, hdd_temp, dev_temp, count_hdd_buffer = 0;
    time_t last_cpu_check = 0;

    now = time(NULL);
    next_check = 0;
    for (int i = 0; i < count_fans; i++) {
        sw_checked_cpu = 0;
        sw_checked_hdd = 0;
        if ((fan[i].control_by_cpu == 1) && (now >= fan[i].next_cpu_check)) {
            if ((last_cpu_check != now) ||
               (strcmp(last_cpu_input, fan[i].cpu_input) != 0)) {
                // Get CPU temperature
                cpu_temp = get_cpu_temp(fan[i].cpu_input);
                last_cpu_check = now;
                strcpy(last_cpu_input, fan[i].cpu_input);
            }

            if (!((cpu_temp < fan[i].cpu_temp) &&
               (((fan[i].cpu_temp - cpu_temp) < fan[i].hyst_cpu) ||
               (now < fan[i].min_decr_time))) &&
               (cpu_temp != fan[i].cpu_temp)) {
                // Determine PWM value
                fan[i].cpu_pwm = calc_pwm(CPU, i, cpu_temp,
                                 fan[i].interpolate_cpu, fan[i].count_step_cpu);
                fan[i].cpu_temp = cpu_temp;
            }

            while ((fan[i].next_cpu_check += fan[i].interval_cpu) <= now);
            sw_checked_cpu = 1;
        }

        if ((fan[i].control_by_hdd == 1) && (now >= fan[i].next_hdd_check)) {
            hdd_temp = 0;
            for (int j = 0; j < fan[i].count_scan_hdd; j++) {
                dev_temp = 0;
                sw_hdd_buffered = 0;
                for (int k = 0; k < count_hdd_buffer; k++)
                    if (strcmp(hdd_buffer[k].name, scan_hdd[i][j].name) == 0) {
                        dev_temp = hdd_buffer[k].temp;
                        sw_hdd_buffered = 1;
                        break;
                    }

                if (sw_hdd_buffered == 0) {
                    // Check if hard drive exists
                    if (!((stat(scan_hdd[i][j].name, &device) != 0) ||
                       ((device.st_mode & S_IFMT) != S_IFBLK)))
                        if (get_hdd_status(scan_hdd[i][j].name) == 1)
                            // Get temperature from hard drive
                            dev_temp = get_hdd_temp(scan_hdd[i][j].name);

                    strcpy(hdd_buffer[count_hdd_buffer].name,
                          scan_hdd[i][j].name);
                    hdd_buffer[count_hdd_buffer].temp = dev_temp;
                    ++count_hdd_buffer;
                }
                if (dev_temp > hdd_temp)
                    hdd_temp = dev_temp;
            }

            if (!((hdd_temp < fan[i].hdd_temp) &&
               (((fan[i].hdd_temp - hdd_temp) < fan[i].hyst_hdd) ||
               (now < fan[i].min_decr_time))) &&
               (hdd_temp != fan[i].hdd_temp)) {
                // Determine PWM value
                fan[i].hdd_pwm = calc_pwm(HDD, i, hdd_temp,
                                 fan[i].interpolate_hdd, fan[i].count_step_hdd);
                fan[i].hdd_temp = hdd_temp;
            }

            while ((fan[i].next_hdd_check += fan[i].interval_hdd) <= now);
            sw_checked_hdd = 1;
        }

        if ((fan[i].control_by_cpu == 1) &&
           ((next_check == 0) || (next_check > fan[i].next_cpu_check)))
            next_check = fan[i].next_cpu_check;
        if ((fan[i].control_by_hdd == 1) &&
           ((next_check == 0) || (next_check > fan[i].next_hdd_check)))
            next_check = fan[i].next_hdd_check;

        if ((sw_checked_cpu == 1) || (sw_checked_hdd == 1)) {
            if (fan[i].cpu_pwm > fan[i].hdd_pwm)
                new_pwm = fan[i].cpu_pwm;
            else
                new_pwm = fan[i].hdd_pwm;

            if (fan[i].first_check == 1)
                // First check since program start
                fan[i].first_check = 0;
            else if (new_pwm == fan[i].actual_pwm)
                continue;
            else if (new_pwm < fan[i].actual_pwm)
                // Check if decrease delay time has passed
                if (now < fan[i].min_decr_time)
                    continue;
                else if ((fan[i].control_by_cpu == 1) &&
                        (sw_checked_cpu == 0) && (new_pwm < fan[i].cpu_pwm))
                    // CPU check skipped and new PWM would be lower than
                    // calculated for last CPU check
                    if (fan[i].cpu_pwm != fan[i].actual_pwm)
                        new_pwm = fan[i].cpu_pwm;
                    else
                        continue;
                else if ((fan[i].control_by_hdd == 1) &&
                        (sw_checked_hdd == 0) && (new_pwm < fan[i].hdd_pwm))
                    // Hard drive check skipped and new PWM would be lower than
                    // calculated for last hard drive check
                    if (fan[i].hdd_pwm != fan[i].actual_pwm)
                        new_pwm = fan[i].hdd_pwm;
                    else
                        continue;
                else if (new_pwm == 0)
                    //  Check if stop delay time has passed
                    if (now < fan[i].min_stop_time)
                        if ((fan[i].idle_pwm != 0) &&
                           (fan[i].idle_pwm != fan[i].actual_pwm))
                            // Set fan speed to idle
                            new_pwm = fan[i].idle_pwm;
                        else
                            continue;

            // Set new fan speed
            set_fan_speed(fan[i].pwm_write, new_pwm);
            syslog(LOG_NOTICE,
              "%s: PWM changed from %d to %d (CPU: %d°C, HDD: %d°C)\n",
              fan[i].name, fan[i].actual_pwm, new_pwm, fan[i].cpu_temp,
              fan[i].hdd_temp);
            if (fan[i].actual_pwm < new_pwm)
                fan[i].min_decr_time = now + fan[i].decr_delay;
            if ((fan[i].actual_pwm == 0) && (new_pwm != 0))
                fan[i].min_stop_time = now + fan[i].stop_delay;
            fan[i].actual_pwm = new_pwm;
            fan[i].change_time = now;
        }
    }

    return 0;
}

int main (int argc, char **argv) {
    start_daemon ("fwcontrol", LOG_LOCAL0);
    syslog(LOG_NOTICE, "fwcontrol started ...\n");
    read_conf();
    set_fan_mode();
    while (1) {
        control_fan_speed();
        sleep(next_check - now);
    }
    syslog(LOG_NOTICE, "fwcontrol terminated.\n");
    closelog();
    return EXIT_SUCCESS;
}

Configuration file /etc/fwcontrol.conf

# Configuration file for fwcontrol
#
# Parameter         Value       Description
# ================+===========+=========================================
# control_by_cpu  | 0 or 1    | If set to 1 the fan is controlled by
#                 |           | CPU temperature.
# control_by_hdd  | 0 or 1    | If set to 1 the fan is controlled by
#                 |           | hard drive temperature.
# pwm_enable      | filename  | Used to set the fan mode.
# pwm_write       | filename  | Used to change the fan speed.
# cpu_input       | filename  | Used to read the CPU temperature sensor.
# temp_pwm_cpu    | temp,pwm  | Defines the PWM value for a given CPU
#                 |           | temperature. Up to 10 data pairs are
#                 |           | possible: temp1,pwm1,temp2,pwm2,...
# interpolate_cpu | 0 or 1    | If set to 1 the program calculates
#                 |           | intermediate values between the data
#                 |           | points defined in "temp_cpu".
# hyst_cpu        | temp      | CPU temperature must be decreased by at
#                 |           | least this value until fan speed can be
#                 |           | reduced.
# scan_hdd        | device(s) | Defines which hard drives should be
#                 |           | checked. Up to 32 hard drives possible:
#                 |           | hdd1,hdd2,hdd3,...
# temp_pwm_hdd    | temp,pwm  | Defines the PWM value for a given hard
#                 |           | drive temperature. Up to 10 data pairs
#                 |           | are possible: temp1,pwm1,temp2,pwm2,...
# interpolate_hdd | 0 or 1    | If set to 1 the program calculates
#                 |           | intermediate values between the data
#                 |           | points defined in "temp_hdd".
# hyst_hdd        | temp      | Hard drive temperature must be decreased
#                 |           | by at least this value until fan speed
#                 |           | can be reduced.
# idle_pwm        | 0-255     | If the fan should be stopped but stop
#                 |           | delay time has not yet passed, then the
#                 |           | fan speed is set to this value.
# decrease_delay  | seconds   | Defines how many seconds must have
#                 |           | passed since last speed increase until
#                 |           | the speed can be decreased.
# stop_delay      | seconds   | Defines how many seconds must have
#                 |           | passed since start of the fan until the
#                 |           | fan can be stopped.
# interval_cpu    | seconds   | Check CPU temperature every n seconds.
# interval_hdd    | seconds   | Check hard drive temperatures every
#                 |           | n seconds.
# ================+===========+=========================================
[CPU Cooler]
control_by_cpu=1
control_by_hdd=0
pwm_enable=/sys/class/hwmon/hwmon2/pwm2_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm2
cpu_input=/sys/class/hwmon/hwmon1/temp1_input
temp_pwm_cpu=0,20,29,20,30,46,41,46,80,255
interpolate_cpu=1
hyst_cpu=3
idle_pwm=20
decrease_delay=30
stop_delay=120
interval_cpu=3

[Front left]
control_by_cpu=1
control_by_hdd=1
pwm_enable=/sys/class/hwmon/hwmon2/pwm3_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm3
cpu_input=/sys/class/hwmon/hwmon1/temp1_input
scan_hdd=/dev/sdb
temp_pwm_cpu=0,78,30,140,41,140,60,255
temp_pwm_hdd=33,140,38,255
interpolate_cpu=1
interpolate_hdd=1
hyst_cpu=3
hyst_hdd=2
idle_pwm=78
decrease_delay=30
stop_delay=120
interval_cpu=15
interval_hdd=60

[Front right]
control_by_cpu=1
control_by_hdd=1
pwm_enable=/sys/class/hwmon/hwmon2/pwm4_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm4
cpu_input=/sys/class/hwmon/hwmon1/temp1_input
scan_hdd=/dev/sdc,/dev/sdd,/dev/sde,/dev/sdf
temp_pwm_cpu=55,110,60,255
temp_pwm_hdd=33,78,38,255
interpolate_cpu=1
interpolate_hdd=1
hyst_cpu=3
hyst_hdd=2
idle_pwm=78
decrease_delay=30
stop_delay=120
interval_cpu=15
interval_hdd=60

[Rear left]
control_by_cpu=1
control_by_hdd=1
pwm_enable=/sys/class/hwmon/hwmon2/pwm1_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm1
cpu_input=/sys/class/hwmon/hwmon1/temp1_input
scan_hdd=/dev/sdb,/dev/sdc,/dev/sdd,/dev/sde,/dev/sdf
temp_pwm_cpu=0,90,30,140,41,140,60,255
temp_pwm_hdd=33,140,38,255
interpolate_cpu=1
interpolate_hdd=1
hyst_cpu=3
hyst_hdd=2
idle_pwm=90
decrease_delay=30
stop_delay=120
interval_cpu=15
interval_hdd=60

[Rear right]
control_by_cpu=1
control_by_hdd=1
pwm_enable=/sys/class/hwmon/hwmon2/pwm5_enable
pwm_write=/sys/class/hwmon/hwmon2/pwm5
cpu_input=/sys/class/hwmon/hwmon1/temp1_input
scan_hdd=/dev/sdc,/dev/sdd,/dev/sde,/dev/sdf
temp_pwm_cpu=55,135,60,255
temp_pwm_hdd=37,135,40,255
interpolate_cpu=1
interpolate_hdd=1
hyst_cpu=3
hyst_hdd=2
idle_pwm=135
decrease_delay=30
stop_delay=120
interval_cpu=15
interval_hdd=60

Start fwcontrol during boot with script /etc/init.d/fwcontrol

#! /bin/sh
#

### BEGIN INIT INFO
# Provides:             fwcontrol
# Required-Start:       $syslog $remote_fs
# Required-Stop:        $syslog $remote_fs
# Default-Start:        2 3 4 5
# Default-Stop:
# Short-Description:    fwcontrol
# Description:          Control of fan speeds
### END INIT INFO


NAME="fwcontrol"
DESC="Control of fan speeds"
DAEMON=/usr/sbin/${NAME}
FWUSER=root

PATH=/sbin:/bin:/usr/sbin:/usr/bin

test -f $DAEMON || exit 0

if [ -f /etc/default/$NAME ]; then
   . /etc/default/$NAME
fi

mkdir -p /var/run
PIDFILE=/var/run/${NAME}.pid

RETVAL=0
case "$1" in
   start)
      echo -n "Starting ${DESC}: "
      start-stop-daemon --start --quiet --make-pidfile --pidfile ${PIDFILE} --chuid ${FWUSER} --exec ${DAEMON} -- ${BOPTIONS}
      RETVAL=$?
      echo "${NAME}"
      ;;
   stop)
      echo -n "Stopping ${DESC}: "
      start-stop-daemon --oknodo --stop --quiet --chuid ${FWUSER} --exec ${DAEMON} -- ${BOPTIONS}
      RETVAL=$?
      echo "${NAME}"
      ;;
   restart|force-reload)
      $0 stop
      sleep 5
      $0 start
      RETVAL=$?
      ;;
   *)
      echo "Usage: /etc/init.d/${NAME} {start|stop|restart|force-reload}" >&2
      exit 1
      ;;
esac
exit $RETVAL

Logfile Configuration

File /etc/rsyslog.d/10-fwcontrol.conf:

local0.*                        /var/log/fwcontrol.log
& stop

Logfile rotation configured in file /etc/logrotate.d/fwcontrol:

/var/log/fwcontrol.log {
        weekly
        rotate 4
        compress
        delaycompress
        notifempty
        missingok
}

Fancontrol Documentation