Skip to content

Commit

Permalink
Initial support for Locked Rotor Alarm fans.
Browse files Browse the repository at this point in the history
New commands:
 * CONFigure:FANx:RPMMOde
 * CONFigure:FANx:RPMMOde?
 * CONFigure:MBFANx:RPMMOde
 * CONFigure:MBFANx:RPMMOde?
  • Loading branch information
tjko committed Jun 27, 2024
1 parent fa69a51 commit 7101296
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 37 deletions.
70 changes: 70 additions & 0 deletions commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Fanpico supports following commands:
* [CONFigure:FANx:PWMCoeff?](#configurefanxpwmcoeff-1)
* [CONFigure:FANx:RPMFactor](#configurefanxrpmfactor)
* [CONFigure:FANx:RPMFactor?](#configurefanxrpmfactor-1)
* [CONFigure:FANx:RPMMOde](#configurefanxrpmmode)
* [CONFigure:FANx:RPMMOde?](#configurefanxrpmmode-1)
* [CONFigure:FANx:SOUrce](#configurefanxsource)
* [CONFigure:FANx:SOUrce?](#configurefanxsource-1)
* [CONFigure:FANx:PWMMap](#configurefanxpwmmap)
Expand All @@ -38,6 +40,8 @@ Fanpico supports following commands:
* [CONFigure:MBFANx:RPMCoeff?](#configurembfanxrpmcoeff-1)
* [CONFigure:MBFANx:RPMFactor](#configurembfanxrpmfactor)
* [CONFigure:MBFANx:RPMFactor?](#configurembfanxrpmfactor-1)
* [CONFigure:MBFANx:RPMMOde](#configurembfanxrpmmode)
* [CONFigure:MBFANx:RPMMOde?](#configurembfanxrpmmode-1)
* [CONFigure:MBFANx:SOUrce](#configurembfanxsource)
* [CONFigure:MBFANx:SOUrce?](#configurembfanxsource-1)
* [CONFigure:MBFANx:RPMMap](#configurembfanxrpmmap)
Expand Down Expand Up @@ -400,6 +404,39 @@ CONF:FAN1:RPMF?
4
```

#### CONFigure:FANx:RPMMOde
Configure what type tachometer signal the fan is sending.

Supported signal types:

Signal Type|Setting|Notes
-----------|-------|-----
Tachometer|TACHO|Fan is sending normal tachometer pulses to indicate rotation speed.
Locked Rotor (Alarm)|LRA,<low rpm>,<high rpm>|Parameters indicate mapping from LRA to RPM speeds (corresponding to LOW and HIGH signal received from the fan).

Default: TACHO (fan sends standard tachometer pulses to indicate rotation speed)

Example: Fan is not sending tachometer signal but Locked Rotor Alam signal (LRA) on tachometer pin (we map LOW signal to mean 0 RPM and HIGH signal to mean 2000 RPM)
```
CONF:FAN1:RPMMODE LRA,0,2000
```

Example: Fan is sending Locked Rotor (Alarm) signal (LRA), where polarity in reversed (HIGH signal indicates lockup/failure):
```
CONF:FAN2:RPMMODE LRA,2000,0
```

#### CONFigure:FANx:RPMMOde?
Query current RPM tachometer signal settings for a fan.

Example:
```
CONF:FAN1:RPMMODE?
TACHO
CONF:FAN2:RPMMODE?
LRA,0,2000
```

#### CONFigure:FANx:SOUrce
Configure source for the PWM signal of a fan.

Expand Down Expand Up @@ -625,6 +662,39 @@ CONF:MBFAN1:RPMF?
4
```

#### CONFigure:MBFANx:RPMMOde
Configure what type tachometer signal is sent (out) to motherboard.

Supported signal types:

Signal Type|Setting|Notes
-----------|-------|-----
Tachometer|TACHO|Send normal tachometer pulses to indicate rotation speed.
Locked Rotor (Alarm)|LRA,<treshold rpm>,<locked signal>|Send Locked Rotor Alarm signal, when RPM is below treshold RPM speed send the indicated signal (HIGH/LOW).

Default: TACHO (standard tachometer pulses to indicate rotation speed)

Example: Send 'LOW' Locked Rotor (Alarm) signal (when RPM drops below 200 RPM)
```
CONF:MBFAN1:RPMMODE LRA,200,LOW
```

Example: Send 'HIGH' Locked Rotor (Alarm) signal (when RPM drops below 500 RPM)
```
CONF:MBFAN2:RPMMODE LRA,500,HIGH
```

#### CONFigure:FANx:RPMMOde?
Query current RPM tachometer signal settings for a fan.

Example:
```
CONF:FAN1:RPMMODE?
TACHO
CONF:FAN2:RPMMODE?
LRA,0,2000
```

#### CONFigure:MBFANx:SOUrce
Configure source for the Tachometer (RPM) signal for a motheboard fan (output) port.

Expand Down
97 changes: 97 additions & 0 deletions src/command.c
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,52 @@ int cmd_fan_rpm_factor(const char *cmd, const char *args, int query, char *prev_
return 1;
}

int cmd_fan_rpm_mode(const char *cmd, const char *args, int query, char *prev_cmd)
{
int ret = 0;
int fan, val;
struct fan_output *f;
char *tok, *saveptr, *param;

fan = atoi(&prev_cmd[3]) - 1;
if (fan < 0 || fan >= FAN_COUNT)
return 1;
f = &conf->fans[fan];

if (query) {
printf("%s", rpm_mode2str(f->rpm_mode));
if (f->rpm_mode == RMODE_LRA)
printf("%d,%d", f->lra_low, f->lra_high);
printf("\n");
} else {
param = strdup(args);
if ((tok = strtok_r(param, ",", &saveptr)) != NULL) {
val = str2rpm_mode(tok);
if (val != f->rpm_mode) {
log_msg(LOG_NOTICE, "fan%d: rpm_mode change '%s' --> '%s'",
fan + 1, rpm_mode2str(f->rpm_mode), rpm_mode2str(val));
f->rpm_mode = val;
}
if (f->rpm_mode == RMODE_LRA) {
if ((tok = strtok_r(NULL, ",", &saveptr)) != NULL) {
if (str_to_int(tok, &val, 10)) {
f->lra_low = clamp_int(val, 0, 100000);
}
if ((tok = strtok_r(NULL, ",", &saveptr)) != NULL) {
if (str_to_int(tok, &val, 10)) {
f->lra_high = clamp_int(val, 0, 100000);
}
}
}
}
} else {
ret = 2;
}
free(param);
}
return ret;
}

int cmd_fan_source(const char *cmd, const char *args, int query, char *prev_cmd)
{
int fan;
Expand Down Expand Up @@ -1038,6 +1084,55 @@ int cmd_mbfan_rpm_map(const char *cmd, const char *args, int query, char *prev_c
return ret;
}

int cmd_mbfan_rpm_mode(const char *cmd, const char *args, int query, char *prev_cmd)
{
int ret = 0;
int fan, val;
char *tok, *saveptr, *param;
struct mb_input *m;

fan = atoi(&prev_cmd[5]) - 1;
if (fan < 0 || fan >= MBFAN_COUNT)
return 1;
m = &conf->mbfans[fan];


if (query) {
printf("%s", rpm_mode2str(m->rpm_mode));
if (m->rpm_mode == RMODE_LRA)
printf("%d,%s", m->lra_treshold, (m->lra_invert ? "HIGH" : "LOW"));
printf("\n");
} else {
param = strdup(args);
if ((tok = strtok_r(param, ",", &saveptr)) != NULL) {
val = str2rpm_mode(tok);
if (val != m->rpm_mode) {
log_msg(LOG_NOTICE, "mbfan%d: rpm_mode change '%s' -> '%s'",
fan + 1, rpm_mode2str(m->rpm_mode), rpm_mode2str(val));
m->rpm_mode = val;
}
if (m->rpm_mode == RMODE_LRA) {
if ((tok = strtok_r(NULL, ",", &saveptr)) != NULL) {
if (str_to_int(tok, &val, 10)) {
m->lra_treshold = clamp_int(val, 0, 100000);
}
if ((tok = strtok_r(NULL, ",", &saveptr)) != NULL) {
bool invert = false;
if (!strncasecmp(tok, "HIGH", 1))
invert = true;
m->lra_invert = invert;
}
}
}
} else {
ret = 2;
}
free(param);
}

return ret;
}

int cmd_mbfan_source(const char *cmd, const char *args, int query, char *prev_cmd)
{
int fan;
Expand Down Expand Up @@ -2526,6 +2621,7 @@ const struct cmd_t fan_c_commands[] = {
{ "PWMCoeff", 4, NULL, cmd_fan_pwm_coef },
{ "PWMMap", 4, NULL, cmd_fan_pwm_map },
{ "RPMFactor", 4, NULL, cmd_fan_rpm_factor },
{ "RPMMOde", 5, NULL, cmd_fan_rpm_mode },
{ "SOUrce", 3, NULL, cmd_fan_source },
{ 0, 0, 0, 0 }
};
Expand All @@ -2538,6 +2634,7 @@ const struct cmd_t mbfan_c_commands[] = {
{ "RPMCoeff", 4, NULL, cmd_mbfan_rpm_coef },
{ "RPMFactor", 4, NULL, cmd_mbfan_rpm_factor },
{ "RPMMap", 4, NULL, cmd_mbfan_rpm_map },
{ "RPMMOde", 5, NULL, cmd_mbfan_rpm_mode },
{ "SOUrce", 3, NULL, cmd_mbfan_source },
{ 0, 0, 0, 0 }
};
Expand Down
73 changes: 62 additions & 11 deletions src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,26 @@ const char* vsmode2str(enum vsensor_modes mode)
return "manual";
}

int str2rpm_mode(const char *s)
{
int ret = RMODE_TACHO;

if (s) {
if (!strncasecmp(s, "lra", 5))
ret = RMODE_LRA;
}

return ret;
}

const char* rpm_mode2str(enum rpm_modes mode)
{
if (mode == RMODE_LRA)
return "lra";

return "tacho";
}

int str2tacho_source(const char *s)
{
int ret = 0;
Expand Down Expand Up @@ -443,6 +463,9 @@ void clear_config(struct fanpico_config *cfg)
f->s_type = PWM_FIXED;
f->s_id = 0;
f->map.points = 0;
f->rpm_mode = RMODE_TACHO;
f->lra_low = 0;
f->lra_high = 1000;
f->rpm_factor = 2;
f->filter = FILTER_NONE;
f->filter_ctx = NULL;
Expand All @@ -454,6 +477,9 @@ void clear_config(struct fanpico_config *cfg)
m->name[0] = 0;
m->min_rpm = 0;
m->max_rpm = 0;
m->rpm_mode = RMODE_TACHO;
m->lra_treshold = 200;
m->lra_invert = false;
m->rpm_coefficient = 0.0;
m->rpm_factor = 2;
m->s_type = TACHO_FIXED;
Expand Down Expand Up @@ -689,8 +715,11 @@ cJSON *config_to_json(const struct fanpico_config *cfg)
cJSON_AddItemToObject(o, "source_type", cJSON_CreateString(pwm_source2str(f->s_type)));
cJSON_AddItemToObject(o, "source_id", cJSON_CreateNumber(f->s_id));
cJSON_AddItemToObject(o, "pwm_map", pwm_map2json(&f->map));
cJSON_AddItemToObject(o, "rpm_factor", cJSON_CreateNumber(f->rpm_factor));
cJSON_AddItemToObject(o, "filter", filter2json(f->filter, f->filter_ctx));
cJSON_AddItemToObject(o, "rpm_mode", cJSON_CreateString(rpm_mode2str(f->rpm_mode)));
cJSON_AddItemToObject(o, "rpm_factor", cJSON_CreateNumber(f->rpm_factor));
cJSON_AddItemToObject(o, "lra_low", cJSON_CreateNumber(f->lra_low));
cJSON_AddItemToObject(o, "lra_high", cJSON_CreateNumber(f->lra_high));
cJSON_AddItemToArray(fans, o);
}
cJSON_AddItemToObject(config, "fans", fans);
Expand All @@ -709,8 +738,12 @@ cJSON *config_to_json(const struct fanpico_config *cfg)
cJSON_AddItemToObject(o, "name", cJSON_CreateString(m->name));
cJSON_AddItemToObject(o, "min_rpm", cJSON_CreateNumber(m->min_rpm));
cJSON_AddItemToObject(o, "max_rpm", cJSON_CreateNumber(m->max_rpm));
cJSON_AddItemToObject(o, "rpm_mode", cJSON_CreateString(rpm_mode2str(m->rpm_mode)));
cJSON_AddItemToObject(o, "rpm_coefficient", cJSON_CreateNumber(m->rpm_coefficient));
cJSON_AddItemToObject(o, "rpm_factor", cJSON_CreateNumber(m->rpm_factor));
cJSON_AddItemToObject(o, "lra_treshold", cJSON_CreateNumber(m->lra_treshold));
if (m->lra_invert != false)
cJSON_AddItemToObject(o, "lra_invert", cJSON_CreateNumber(m->lra_invert));
cJSON_AddItemToObject(o, "source_type", cJSON_CreateString(tacho_source2str(m->s_type)));
cJSON_AddItemToObject(o, "source_id", cJSON_CreateNumber(m->s_id));
if (m->s_type == TACHO_MIN || m->s_type == TACHO_MAX || m->s_type == TACHO_AVG)
Expand Down Expand Up @@ -1007,16 +1040,25 @@ int json_to_config(cJSON *config, struct fanpico_config *cfg)
name = cJSON_GetStringValue(cJSON_GetObjectItem(item, "name"));
if (name) strncopy(f->name, name ,sizeof(f->name));

f->min_pwm = cJSON_GetNumberValue(cJSON_GetObjectItem(item, "min_pwm"));
f->max_pwm = cJSON_GetNumberValue(cJSON_GetObjectItem(item, "max_pwm"));
f->pwm_coefficient = cJSON_GetNumberValue(
cJSON_GetObjectItem(item,"pwm_coefficient"));
if ((r = cJSON_GetObjectItem(item, "min_pwm")))
f->min_pwm = cJSON_GetNumberValue(r);
if ((r = cJSON_GetObjectItem(item, "max_pwm")))
f->max_pwm = cJSON_GetNumberValue(r);
if ((r = cJSON_GetObjectItem(item, "pwm_coefficient")))
f->pwm_coefficient = cJSON_GetNumberValue(r);
f->s_type = str2pwm_source(cJSON_GetStringValue(
cJSON_GetObjectItem(item, "source_type")));
f->s_id = cJSON_GetNumberValue(cJSON_GetObjectItem(item, "source_id"));
if ((r = cJSON_GetObjectItem(item, "pwm_map")))
json2pwm_map(r, &f->map);
f->rpm_factor = cJSON_GetNumberValue(cJSON_GetObjectItem(item,"rpm_factor"));
f->rpm_mode = str2rpm_mode(cJSON_GetStringValue(
cJSON_GetObjectItem(item, "rpm_mode")));;
if ((r = cJSON_GetObjectItem(item,"rpm_factor")))
f->rpm_factor = cJSON_GetNumberValue(r);
if ((r = cJSON_GetObjectItem(item,"lra_low")))
f->lra_low = cJSON_GetNumberValue(r);
if ((r = cJSON_GetObjectItem(item,"lra_high")))
f->lra_high = cJSON_GetNumberValue(r);
if ((r = cJSON_GetObjectItem(item, "filter")))
json2filter(r, &f->filter, &f->filter_ctx);
}
Expand All @@ -1032,11 +1074,20 @@ int json_to_config(cJSON *config, struct fanpico_config *cfg)
name = cJSON_GetStringValue(cJSON_GetObjectItem(item, "name"));
if (name) strncopy(m->name, name ,sizeof(m->name));

m->min_rpm = cJSON_GetNumberValue(cJSON_GetObjectItem(item, "min_rpm"));
m->max_rpm = cJSON_GetNumberValue(cJSON_GetObjectItem(item, "max_rpm"));
m->rpm_coefficient = cJSON_GetNumberValue(
cJSON_GetObjectItem(item, "rpm_coefficient"));
m->rpm_factor = cJSON_GetNumberValue(cJSON_GetObjectItem(item,"rpm_factor"));
if ((r = cJSON_GetObjectItem(item, "min_rpm")))
m->min_rpm = cJSON_GetNumberValue(r);
if ((r = cJSON_GetObjectItem(item, "max_rpm")))
m->max_rpm = cJSON_GetNumberValue(r);
m->rpm_mode = str2rpm_mode(cJSON_GetStringValue(
cJSON_GetObjectItem(item, "rpm_mode")));;
if ((r = cJSON_GetObjectItem(item, "rpm_coefficient")))
m->rpm_coefficient = cJSON_GetNumberValue(r);
if ((r = cJSON_GetObjectItem(item,"rpm_factor")))
m->rpm_factor = cJSON_GetNumberValue(r);
if ((r = cJSON_GetObjectItem(item,"lra_treshold")))
m->lra_treshold = cJSON_GetNumberValue(r);
if ((r = cJSON_GetObjectItem(item,"lra_invert")))
m->lra_invert = cJSON_GetNumberValue(r);
m->s_type = str2tacho_source(cJSON_GetStringValue(
cJSON_GetObjectItem(item, "source_type")));
m->s_id = cJSON_GetNumberValue(cJSON_GetObjectItem(item, "source_id"));
Expand Down
8 changes: 7 additions & 1 deletion src/fanpico.c
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,13 @@ void update_outputs(struct fanpico_state *state, const struct fanpico_config *co
state->mbfan_freq_prev[i],
state->mbfan_freq[i]);
state->mbfan_freq_prev[i] = state->mbfan_freq[i];
set_tacho_output_freq(i, state->mbfan_freq[i]);
if (cfg->mbfans[i].rpm_mode == RMODE_TACHO) {
set_tacho_output_freq(i, state->mbfan_freq[i]);
} else {
int rpm = state->mbfan_freq[i] * 60 / cfg->mbfans[i].rpm_factor;
bool lra = (rpm < cfg->mbfans[i].lra_treshold ? true : false);
set_lra_output(i, cfg->mbfans[i].lra_invert ? !lra : lra);
}
}
}
}
Expand Down
Loading

0 comments on commit 7101296

Please sign in to comment.