Skip to content

Commit

Permalink
Support for custom layout on OLED screens. (#50)
Browse files Browse the repository at this point in the history
* Update USB product string.

* Support for custom layout on OLED screens for the mbfan inputs
and sensors (right side of the screen).

New commands:
- SYS:DISP:LAYOUTR
- SYS:DISP:LAYOUTR?
  • Loading branch information
tjko committed Aug 17, 2023
1 parent 7574c50 commit b29f865
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 41 deletions.
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ endif()

target_compile_options(fanpico PRIVATE -Wall)
target_compile_definitions(fanpico PRIVATE USBD_MANUFACTURER="TJKO Industries")
target_compile_definitions(fanpico PRIVATE USBD_PRODUCT="FanPico-${FANPICO_BOARD}")
target_compile_definitions(fanpico PRIVATE USBD_PRODUCT="FanPico-${FANPICO_BOARD} Fan Controller")
target_compile_definitions(fanpico PRIVATE USBD_DESC_STR_MAX=32)
target_compile_definitions(fanpico PRIVATE PARAM_ASSERTIONS_ENABLE_ALL=1)
target_compile_definitions(fanpico PRIVATE PICO_MALLOC_PANIC=0)
target_compile_definitions(fanpico PRIVATE PICO_DEBUG_MALLOC=0)
Expand Down
48 changes: 47 additions & 1 deletion commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,10 @@ Fanpico supports following commands:
* [SYStem:SYSLOG?](#systemsyslog-1)
* [SYStem:DISPlay](#systemdisplay)
* [SYStem:DISPlay?](#systemdisplay)
* [SYStem:DISPlay:LAYOUTR](#systemdisplaylayoutr)
* [SYStem:DISPlay:LAYOUTR?](#systemdisplaylayoutr-1)
* [SYStem:DISPlay:THEMe](#systemdisplaytheme)
* [SYStem:DISPlay:THEME?](#systemdisplaytheme-1)
* [SYStem:DISPlay:THEMe?](#systemdisplaytheme-1)
* [SYStem:ECHO](#systemecho)
* [SYStem:ECHO?](#systemecho)
* [SYStem:FANS?](#systemfans)
Expand Down Expand Up @@ -1423,6 +1425,50 @@ SYS:DISP?
132x64,flip,invert,brightness=75
```

#### SYStem:DISPlay:LAYOUTR
Configure (OLED) Display layout for the right side of the screen.

Layout is specified as a comma delimited string descibing what to
display on each row (8 rows available if using 128x64 OLEd module, 10 rows available with 128x128 pixel modules).

Syntax: <R1>,<R2>,...<R8>

Wehre tow specifications can be one of the following:

Type|Description|Notes
----|-----------|-----
Mn|MBFan input n|n=1..4
Sn|Sensor input n|n=1..3
Vn|Virtual Sensor input n|n=1..8
-|Horizontal Line|
Ltext|Line with "text"|Max lenght 9 characters.


Default: <not set>

When this setting is not set following defaults are used based
on the OLED module size:

Screen Size|Available Rows|Default Configuration
-----------|--------------|---------------------
128x64|8|M1,M2,M3,M4,-,S1,S2,S3
128x128|10|LMB Inputs,M1,M2,M3,M4,-,LSensors,S1,S2,S3

Example: configure custom theme (for 128x64 display):
```
SYS:DISP:LAYOUTR M1,M2,-,S1,S2,S3,V1,V2
```

#### SYStem:DISPlay:THEMe?
Display currently configured (OLED) Display layout for the right side of the screen.

Example:
```
SYS:DISP:THEME?
M1,M2,-,S1,S2,S3,V1,V2
```


#### SYStem:DISPlay:THEMe
Configure (LCD) Display theme to use.

Expand Down
11 changes: 11 additions & 0 deletions src/command.c
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,16 @@ int cmd_display_theme(const char *cmd, const char *args, int query, char *prev_c
return 0;
}

int cmd_display_layout_r(const char *cmd, const char *args, int query, char *prev_cmd)
{
if (query) {
printf("%s\n", conf->display_layout_r);
} else {
strncopy(conf->display_layout_r, args, sizeof(conf->display_layout_r));
}
return 0;
}

int cmd_reset(const char *cmd, const char *args, int query, char *prev_cmd)
{
const char *msg[] = {
Expand Down Expand Up @@ -1989,6 +1999,7 @@ int cmd_spi(const char *cmd, const char *args, int query, char *prev_cmd)

struct cmd_t display_commands[] = {
{ "THEMe", 4, NULL, cmd_display_theme },
{ "LAYOUTR", 4, NULL, cmd_display_layout_r },
{ 0, 0, 0, 0 }
};
struct cmd_t wifi_commands[] = {
Expand Down
7 changes: 7 additions & 0 deletions src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ void clear_config(struct fanpico_config *cfg)
strncopy(cfg->name, "fanpico1", sizeof(cfg->name));
strncopy(cfg->display_type, "default", sizeof(cfg->display_type));
strncopy(cfg->display_theme, "default", sizeof(cfg->display_theme));
strncopy(cfg->display_layout_r, "", sizeof(cfg->display_layout_r));
#ifdef WIFI_SUPPORT
cfg->wifi_ssid[0] = 0;
cfg->wifi_passwd[0] = 0;
Expand Down Expand Up @@ -465,6 +466,8 @@ cJSON *config_to_json(const struct fanpico_config *cfg)
cJSON_AddItemToObject(config, "display_type", cJSON_CreateString(cfg->display_type));
if (strlen(cfg->display_theme) > 0)
cJSON_AddItemToObject(config, "display_theme", cJSON_CreateString(cfg->display_theme));
if (strlen(cfg->display_layout_r) > 0)
cJSON_AddItemToObject(config, "display_layout_r", cJSON_CreateString(cfg->display_layout_r));
if (strlen(cfg->name) > 0)
cJSON_AddItemToObject(config, "name", cJSON_CreateString(cfg->name));

Expand Down Expand Up @@ -644,6 +647,10 @@ int json_to_config(cJSON *config, struct fanpico_config *cfg)
if ((val = cJSON_GetStringValue(ref)))
strncopy(cfg->display_theme, val, sizeof(cfg->display_theme));
}
if ((ref = cJSON_GetObjectItem(config, "display_layout_r"))) {
if ((val = cJSON_GetStringValue(ref)))
strncopy(cfg->display_layout_r, val, sizeof(cfg->display_layout_r));
}
if ((ref = cJSON_GetObjectItem(config, "name"))) {
if ((val = cJSON_GetStringValue(ref)))
strncopy(cfg->name, val, sizeof(cfg->name));
Expand Down
177 changes: 138 additions & 39 deletions src/display_oled.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,90 @@

#ifdef OLED_DISPLAY

enum layout_item_types {
BLANK = 0,
LABEL = 1,
LINE = 2,
MBFAN = 3,
SENSOR = 4,
VSENSOR = 5,
};

struct layout_item {
enum layout_item_types type;
uint8_t idx;
const char *label;
};

#define R_LAYOUT_MAX 10

static SSOLED oled;
static uint8_t ucBuffer[(128*128)/8];
static uint8_t oled_width = 128;
static uint8_t oled_height = 64;
static uint8_t oled_found = 0;
static uint8_t r_lines = 8;
static struct layout_item r_layout[R_LAYOUT_MAX];


/* Default screen layouts for inputs/sensors */
#define R_LAYOUT_128x64 "M1,M2,M3,M4,-,S1,S2,S3"
#define R_LAYOUT_128x128 "LMB Inputs,M1,M2,M3,M4,-,LSensors,S1,S2,S3"


void parse_r_layout(const char *layout)
{
int i;
char *tok, *saveptr, *tmp;

for (i = 0; i < R_LAYOUT_MAX; i++) {
r_layout[i].type = BLANK;
r_layout[i].idx = 0;
r_layout[i].label = NULL;
}

if (!layout)
return;

tmp = strdup(layout);
if (!tmp)
return;

log_msg(LOG_DEBUG, "parse OLED right layout: '%s", tmp);

i = 0;
tok = strtok_r(tmp, ",", &saveptr);
while (tok && i < r_lines) {
switch (tok[0]) {
case '-':
r_layout[i].type = LINE;
break;
case 'L':
r_layout[i].type = LABEL;
r_layout[i].idx = strlen(tok) - 1;
r_layout[i].label = layout + (tok - tmp) + 1;
break;
case 'M':
r_layout[i].type = MBFAN;
r_layout[i].idx = clamp_int(atoi(tok + 1), 1, MBFAN_COUNT) - 1;
break;
case 'S':
r_layout[i].type = SENSOR;
r_layout[i].idx = clamp_int(atoi(tok + 1), 1, SENSOR_COUNT) - 1;
break;
case 'V':
r_layout[i].type = VSENSOR;
r_layout[i].idx = clamp_int(atoi(tok + 1), 1, VSENSOR_COUNT) - 1;
break;

};

tok = strtok_r(NULL, ",", &saveptr);
i++;
}

free(tmp);
}


void oled_display_init()
Expand All @@ -63,6 +142,7 @@ void oled_display_init()
if (!strncmp(tok, "128x128", 7)) {
dtype = OLED_128x128;
oled_height = 128;
r_lines = 10;
}
else if (!strncmp(tok, "invert", 6))
invert = 1;
Expand All @@ -81,6 +161,12 @@ void oled_display_init()
}
}

if (strlen(cfg->display_layout_r) > 1) {
parse_r_layout(cfg->display_layout_r);
} else {
parse_r_layout(r_lines == 8 ? R_LAYOUT_128x64 : R_LAYOUT_128x128);
}

disp_brightness = (brightness / 100.0) * 255;
log_msg(LOG_DEBUG, "Set display brightness: %u%% (0x%x)\n",
brightness, disp_brightness);
Expand Down Expand Up @@ -156,60 +242,73 @@ void oled_display_status(const struct fanpico_state *state,
if (!oled_found || !state)
return;

int h_pos = 70;
int fan_row_offset = (oled_height > 64 ? 1 : 0);

if (!bg_drawn) {
/* Draw "background" only once... */
oled_clear_display();

if (oled_height > 64) {
oledWriteString(&oled, 0, 0, 0, "Fans", FONT_6x8, 0, 1);
oledWriteString(&oled, 0, 74, 0, "MB Inputs", FONT_6x8, 0, 1);
oledWriteString(&oled, 0, 74, 6, "Sensors", FONT_6x8, 0, 1);
oledDrawLine(&oled, 72, 44, oled_width - 1, 44, 1);
oledDrawLine(&oled, 72, 0, 72, 79, 1);
} else {
oledDrawLine(&oled, 70, 35, oled_width - 1, 35, 1);
oledDrawLine(&oled, 70, 0, 70, 63, 1);
}

for (i = 0; i < r_lines; i++) {
struct layout_item *l = &r_layout[i];
char label[16];
int y = i * 8;

if (l->type == LINE) {
oledDrawLine(&oled, h_pos, y + 4, oled_width - 1, y + 4, 1);
}
else if (l->type == LABEL) {
int len = l->idx;
if (len >= sizeof(label))
len = sizeof(label) - 1;
memcpy(label, l->label, len);
label[len] = 0;
oledWriteString(&oled, 0, h_pos + 2, i, label, FONT_6x8, 0, 1);
}
}
oledDrawLine(&oled, h_pos, 0, h_pos, r_lines * 8 - 1, 1);
bg_drawn = 1;
}

if (oled_height <= 64) {
for (i = 0; i < FAN_COUNT; i++) {
rpm = state->fan_freq[i] * 60 / conf->fans[i].rpm_factor;
pwm = state->fan_duty[i];
snprintf(buf, sizeof(buf), "%d:%4.0lf %3.0lf%%", i + 1, rpm, pwm);
oledWriteString(&oled, 0 , 0, i, buf, FONT_6x8, 0, 1);

for (i = 0; i < FAN_COUNT; i++) {
rpm = state->fan_freq[i] * 60 / conf->fans[i].rpm_factor;
pwm = state->fan_duty[i];
snprintf(buf, sizeof(buf), "%d:%4.0lf %3.0lf%%", i + 1, rpm, pwm);
oledWriteString(&oled, 0 , 0, i + fan_row_offset, buf, FONT_6x8, 0, 1);
}
for (i = 0; i < r_lines; i++) {
struct layout_item *l = &r_layout[i];
int write_buf = 0;

if (l->type == MBFAN) {
pwm = state->mbfan_duty[l->idx];
snprintf(buf, sizeof(buf), "%d: %4.0lf%% ", l->idx + 1, pwm);
write_buf = 1;
}
else if (l->type == SENSOR) {
temp = state->temp[l->idx];
snprintf(buf, sizeof(buf), "s%d:%5.1lfC", l->idx + 1, temp);
write_buf = 1;
}
for (i = 0; i < MBFAN_COUNT; i++) {
pwm = state->mbfan_duty[i];
snprintf(buf, sizeof(buf), "%d: %4.0lf%% ", i + 1, pwm);
oledWriteString(&oled, 0 , 74, i + 0, buf, FONT_6x8, 0, 1);
else if (l->type == VSENSOR) {
temp = state->vtemp[l->idx];
snprintf(buf, sizeof(buf), "v%d:%5.1lfC", l->idx + 1, temp);
write_buf = 1;
}
for (i = 0; i < SENSOR_COUNT; i++) {
temp = state->temp[i];
snprintf(buf, sizeof(buf), "%d:%5.1lfC ", i + 1, temp);
if (i == 2) {
if (write_buf) {
if (oled_height <= 64 && i == 0) {
buf[8] = (counter++ % 2 == 0 ? '*' : ' ');
}
oledWriteString(&oled, 0 , 74, i + 5, buf, FONT_6x8, 0, 1);
oledWriteString(&oled, 0 , h_pos + 2, i, buf, FONT_6x8, 0, 1);
}
}
else {
for (i = 0; i < FAN_COUNT; i++) {
rpm = state->fan_freq[i] * 60 / conf->fans[i].rpm_factor;
pwm = state->fan_duty[i];
snprintf(buf, sizeof(buf), "%d:%4.0lf %3.0lf%% ", i + 1, rpm, pwm);
oledWriteString(&oled, 0 , 0, i + 1, buf, FONT_6x8, 0, 1);
}
for (i = 0; i < MBFAN_COUNT; i++) {
pwm = state->mbfan_duty[i];
snprintf(buf, sizeof(buf), "%d: %4.0lf%% ", i + 1, pwm);
oledWriteString(&oled, 0 , 78, i + 1, buf, FONT_6x8, 0, 1);
}
for (i = 0; i < SENSOR_COUNT; i++) {
temp = state->temp[i];
snprintf(buf, sizeof(buf), "%d:%5.1lfC ", i + 1, temp);
oledWriteString(&oled, 0 , 78, i + 7, buf, FONT_6x8, 0, 1);
}

if (oled_height > 64) {
/* IP */
const char *ip = network_ip();
if (ip) {
Expand Down
2 changes: 2 additions & 0 deletions src/fanpico.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ struct fanpico_config {
uint8_t led_mode;
char display_type[64];
char display_theme[16];
char display_layout_r[64];
char name[32];
bool spi_active;
bool serial_active;
Expand Down Expand Up @@ -362,6 +363,7 @@ struct tm *datetime_to_tm(const datetime_t *t, struct tm *tm);
time_t datetime_to_time(const datetime_t *datetime);
void watchdog_disable();
int getstring_timeout_ms(char *str, uint32_t maxlen, uint32_t timeout);
int clamp_int(int val, int min, int max);

/* crc32.c */
unsigned int xcrc32 (const unsigned char *buf, int len, unsigned int init);
Expand Down
13 changes: 13 additions & 0 deletions src/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -436,4 +436,17 @@ int getstring_timeout_ms(char *str, uint32_t maxlen, uint32_t timeout)
return res;
}


int clamp_int(int val, int min, int max)
{
int res = val;

if (res < min)
res = min;
if (res > max)
res = max;

return res;
}

/* eof */

0 comments on commit b29f865

Please sign in to comment.