From b29f8654259bba87384e1a5b25bae116d53c3298 Mon Sep 17 00:00:00 2001 From: Timo Kokkonen Date: Wed, 16 Aug 2023 19:47:43 -0700 Subject: [PATCH] Support for custom layout on OLED screens. (#50) * 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? --- CMakeLists.txt | 3 +- commands.md | 48 +++++++++++- src/command.c | 11 +++ src/config.c | 7 ++ src/display_oled.c | 177 +++++++++++++++++++++++++++++++++++---------- src/fanpico.h | 2 + src/util.c | 13 ++++ 7 files changed, 220 insertions(+), 41 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ab1719..9299e15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/commands.md b/commands.md index 9d02215..e5dfe58 100644 --- a/commands.md +++ b/commands.md @@ -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) @@ -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: ,,... + +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: + +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. diff --git a/src/command.c b/src/command.c index 64d54b7..b731809 100644 --- a/src/command.c +++ b/src/command.c @@ -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[] = { @@ -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[] = { diff --git a/src/config.c b/src/config.c index 2c96415..aaee27a 100644 --- a/src/config.c +++ b/src/config.c @@ -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; @@ -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)); @@ -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)); diff --git a/src/display_oled.c b/src/display_oled.c index 3551366..86d98fd 100644 --- a/src/display_oled.c +++ b/src/display_oled.c @@ -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() @@ -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; @@ -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); @@ -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) { diff --git a/src/fanpico.h b/src/fanpico.h index 807855e..46e2330 100644 --- a/src/fanpico.h +++ b/src/fanpico.h @@ -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; @@ -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); diff --git a/src/util.c b/src/util.c index 9b83774..ca01d6a 100644 --- a/src/util.c +++ b/src/util.c @@ -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 */