diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 08330c8f1..92e3f4e4d 100755 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -5,6 +5,7 @@ SRCS "main.c" "nvs_config.c" "display.c" + "display_config.c" "screen.c" "input.c" "system.c" diff --git a/main/default_screens.h b/main/default_screens.h new file mode 100644 index 000000000..4c6948757 --- /dev/null +++ b/main/default_screens.h @@ -0,0 +1,36 @@ +#ifndef DEFAULT_SCREENS_H_ +#define DEFAULT_SCREENS_H_ + +// Default screen configurations for OLED display +// Each screen is a string with \n separating lines +// Variables in curly braces {variable} are replaced at runtime + +#define DEFAULT_SCREENS_COUNT 4 + +static const char *default_screens[] = { + // Screen 1: URLs (from SCR_URLS) + "Stratum Host:\n{pool_url}\nIP Address:\n{ip}", + + // Screen 2: Stats (from SCR_STATS) + "Gh/s: {hashrate}\nJ/Th: {efficiency}\nBest: {session_diff}/{best_diff}\nTemp: {asic_temp}°C", + + // Screen 3: Mining (from SCR_MINING) + "Block: {block_height}\nDifficulty: {network_diff}\nScriptsig:\n{scriptsig}", + + // Screen 4: WiFi (from SCR_WIFI) + "Wi-Fi Signal\nRSSI: {rssi} dBm\nSignal: {signal}\nUptime: {uptime}", + + // // Screen 5: Power Stats + // "Volts: {voltage} V\nAmps: {amps} A\nFan: {fan}%\nRPM: {fan_rpm}", + + // // Screen 6: Mining Stats + // "Shares: {shares_a}/{shares_r}\nJobs Received: {work_received}\nTime: {response_time}ms", + + // // Screen 7: System Info + // "Frequency: {frequency}MHz\nExpected: {expected_hashrate}Gh/s\nPower: {power} W\nCore: {core_voltage}nV", + + // // Screen 8: Temperature Details + // "Chip Temp: {temp}°C\nVR Temp: {vr_temp}°C\nTarget: {temp_target}°C", +}; + +#endif // DEFAULT_SCREENS_H_ diff --git a/main/display_config.c b/main/display_config.c new file mode 100644 index 000000000..a0784054d --- /dev/null +++ b/main/display_config.c @@ -0,0 +1,445 @@ +#include +#include +#include +#include +#include "esp_log.h" +#include "esp_timer.h" +#include "esp_app_desc.h" +#include "display_config.h" +#include "connect.h" +#include "nvs_config.h" + +static inline bool append_string(char **dst, size_t *remaining, const char *str) +{ + if (!str) str = "--"; + size_t len = strlen(str); + if (len > *remaining) return false; + memcpy(*dst, str, len); + *dst += len; + *remaining -= len; + return true; +} + +static inline bool append_formatted(char **dst, size_t *remaining, + char *temp_buffer, size_t temp_size, + const char *format, ...) +{ + va_list args; + va_start(args, format); + int len = vsnprintf(temp_buffer, temp_size, format, args); + va_end(args); + + if (len <= 0 || (size_t)len >= temp_size) return false; + return append_string(dst, remaining, temp_buffer); +} + +static void handle_hashrate(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.2f", GLOBAL_STATE->SYSTEM_MODULE.current_hashrate); +} + +static void handle_hashrate_1m(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.2f", GLOBAL_STATE->SYSTEM_MODULE.hashrate_1m); +} + +static void handle_hashrate_10m(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.2f", GLOBAL_STATE->SYSTEM_MODULE.hashrate_10m); +} + +static void handle_hashrate_1h(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.2f", GLOBAL_STATE->SYSTEM_MODULE.hashrate_1h); +} + +static void handle_hashrate_expected(GlobalState *GLOBAL_STATE, char *temp, size_t ts,char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.1f", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.expected_hashrate); +} + +static void handle_power(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.1f", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.power); +} + +static void handle_asic_temp(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.1f", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.chip_temp_avg); +} + +static void handle_asic2_temp(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.1f", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.chip_temp2_avg); +} + +static void handle_best_diff(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_string(dst, remaining, GLOBAL_STATE->SYSTEM_MODULE.best_diff_string); +} + +static void handle_session_diff(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_string(dst, remaining, GLOBAL_STATE->SYSTEM_MODULE.best_session_diff_string); +} + +static void handle_ip(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_string(dst, remaining, GLOBAL_STATE->SYSTEM_MODULE.ip_addr_str); +} + +static void handle_ipv6(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_string(dst, remaining, GLOBAL_STATE->SYSTEM_MODULE.ipv6_addr_str); +} + +static void handle_shares_a(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%llu", GLOBAL_STATE->SYSTEM_MODULE.shares_accepted); +} + +static void handle_shares_r(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%llu", GLOBAL_STATE->SYSTEM_MODULE.shares_rejected); +} + +static void handle_network_diff(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_string(dst, remaining, GLOBAL_STATE->network_diff_string); +} + +static void handle_scriptsig(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_string(dst, remaining, GLOBAL_STATE->scriptsig); +} + +static void handle_voltage(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.2f", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.voltage); +} + +static void handle_core_voltage(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.2f", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.core_voltage); +} + +static void handle_current(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.2f", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.current); +} + +static void handle_fan_perc(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.1f", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.fan_perc); +} + +static void handle_fan_rpm(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%u", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.fan_rpm); +} + +static void handle_fan2_rpm(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%u", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.fan2_rpm); +} + +static void handle_work_received(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%llu", GLOBAL_STATE->SYSTEM_MODULE.work_received); +} + +static void handle_response_time(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.2f", GLOBAL_STATE->SYSTEM_MODULE.response_time); +} + +static void handle_frequency(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.0f", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.frequency_value); +} + +static void handle_vr_temp(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.1f", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.vr_temp); +} + +static void handle_efficiency(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + float power = GLOBAL_STATE->POWER_MANAGEMENT_MODULE.power; + float hashrate = GLOBAL_STATE->SYSTEM_MODULE.current_hashrate; + float efficiency = (power > 0.0f && hashrate > 0.0f) ? power / (hashrate / 1000.0f) : 0.0f; + append_formatted(dst, remaining, temp, ts, "%.2f", efficiency); +} + +static void handle_pool_url(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + const char *pool = GLOBAL_STATE->SYSTEM_MODULE.is_using_fallback + ? GLOBAL_STATE->SYSTEM_MODULE.fallback_pool_url + : GLOBAL_STATE->SYSTEM_MODULE.pool_url; + append_string(dst, remaining, pool); +} + +static void handle_pool_difficulty(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%u", GLOBAL_STATE->pool_difficulty); +} + +static void handle_rssi(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + int8_t rssi = -128; + if (GLOBAL_STATE->SYSTEM_MODULE.is_connected) { + get_wifi_current_rssi(&rssi); + } + append_formatted(dst, remaining, temp, ts, "%d", (int)rssi); +} + +static void handle_signal(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + int8_t rssi = -128; + if (GLOBAL_STATE->SYSTEM_MODULE.is_connected) { + get_wifi_current_rssi(&rssi); + } + const char *quality = (rssi >= -50) ? "Excellent" + : (rssi >= -60) ? "Good" + : (rssi >= -70) ? "Fair" + : (rssi > -128) ? "Poor" + : "--"; + append_string(dst, remaining, quality); +} + +static void handle_uptime(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + uint64_t uptime_us = esp_timer_get_time() - GLOBAL_STATE->SYSTEM_MODULE.start_time; + uint32_t total_seconds = uptime_us / 1000000; + + uint32_t days = total_seconds / 86400; + total_seconds %= 86400; + uint32_t hours = total_seconds / 3600; + total_seconds %= 3600; + uint32_t minutes = total_seconds / 60; + uint32_t seconds = total_seconds % 60; + + if (days > 0) { + snprintf(temp, ts, "%lud %luh %lum %lus", days, hours, minutes, seconds); + } else if (hours > 0) { + snprintf(temp, ts, "%luh %lum %lus", hours, minutes, seconds); + } else if (minutes > 0) { + snprintf(temp, ts, "%lum %lus", minutes, seconds); + } else { + snprintf(temp, ts, "%lus", seconds); + } + append_string(dst, remaining, temp); +} + +static void handle_target_temp(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + uint16_t target = nvs_config_get_u16(NVS_CONFIG_TEMP_TARGET); + append_formatted(dst, remaining, temp, ts, "%u", target); +} + +static void handle_error_percentage(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%.2f", GLOBAL_STATE->SYSTEM_MODULE.error_percentage); +} + +static void handle_ssid(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_string(dst, remaining, GLOBAL_STATE->SYSTEM_MODULE.ssid); +} + +static void handle_wifi_status(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_string(dst, remaining, GLOBAL_STATE->SYSTEM_MODULE.wifi_status); +} + +static void handle_pool_connection_info(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_string(dst, remaining, GLOBAL_STATE->SYSTEM_MODULE.pool_connection_info); +} + +static void handle_power_fault(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%u", GLOBAL_STATE->SYSTEM_MODULE.power_fault); +} + +static void handle_block_found(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + const char *found = GLOBAL_STATE->SYSTEM_MODULE.block_found ? "Yes" : "No"; + append_string(dst, remaining, found); +} + +static void handle_hostname(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + char *hostname = nvs_config_get_string(NVS_CONFIG_HOSTNAME); + append_string(dst, remaining, hostname); + free(hostname); +} + +static void handle_device_model(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_string(dst, remaining, GLOBAL_STATE->DEVICE_CONFIG.family.name); +} + +static void handle_asic_model(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_string(dst, remaining, GLOBAL_STATE->DEVICE_CONFIG.family.asic.name); +} + +static void handle_board_version(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_string(dst, remaining, GLOBAL_STATE->DEVICE_CONFIG.board_version); +} + +static void handle_free_heap(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%u", esp_get_free_heap_size()); +} + +static void handle_version(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_string(dst, remaining, GLOBAL_STATE->SYSTEM_MODULE.version); +} + +static void handle_axe_os_version(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_string(dst, remaining, GLOBAL_STATE->SYSTEM_MODULE.axeOSVersion); +} + +static void handle_is_using_fallback_stratum(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + const char *str = GLOBAL_STATE->SYSTEM_MODULE.is_using_fallback ? "Yes" : "No"; + append_string(dst, remaining, str); +} + +static void handle_block_height(GlobalState *GLOBAL_STATE, char *temp, size_t ts, char **dst, size_t *remaining) +{ + append_formatted(dst, remaining, temp, ts, "%u", GLOBAL_STATE->block_height); +} + +typedef void (*handler_func_t)(GlobalState *GLOBAL_STATE, char *temp, size_t temp_size, char **dst, size_t *remaining); + +typedef struct { + const char *var_name; + handler_func_t handler; +} var_entry_t; + +static const var_entry_t variables[] = { + { "hashrate", handle_hashrate }, + { "hashrate_1m", handle_hashrate_1m }, + { "hashrate_10m", handle_hashrate_10m }, + { "hashrate_1h", handle_hashrate_1h }, + { "hashrate_expected", handle_hashrate_expected }, + + { "frequency", handle_frequency }, + { "power", handle_power }, + { "efficiency", handle_efficiency }, + { "voltage", handle_voltage }, + { "core_voltage", handle_core_voltage }, + { "current", handle_current }, + { "power_fault", handle_power_fault }, + + { "asic_temp", handle_asic_temp }, + { "asic2_temp", handle_asic2_temp }, + { "vr_temp", handle_vr_temp }, + { "target_temp", handle_target_temp }, + + { "fan_perc", handle_fan_perc }, + { "fan_rpm", handle_fan_rpm }, + { "fan2_rpm", handle_fan2_rpm }, + + { "pool_url", handle_pool_url }, + { "pool_difficulty", handle_pool_difficulty }, + { "response_time", handle_response_time }, + { "pool_connection_info", handle_pool_connection_info }, + { "is_using_fallback_stratum", handle_is_using_fallback_stratum }, + + { "shares_a", handle_shares_a }, + { "shares_r", handle_shares_r }, + { "work_received", handle_work_received }, + { "error_percentage", handle_error_percentage }, + { "session_diff", handle_session_diff }, + { "best_diff", handle_best_diff }, + { "block_found", handle_block_found }, + + { "ssid", handle_ssid }, + { "wifi_status", handle_wifi_status }, + { "ip", handle_ip }, + { "ipv6", handle_ipv6 }, + { "rssi", handle_rssi }, + { "signal", handle_signal }, + { "uptime", handle_uptime }, + + { "network_diff", handle_network_diff }, + { "scriptsig", handle_scriptsig }, + { "block_height", handle_block_height }, + + { "hostname", handle_hostname }, + { "device_model", handle_device_model }, + { "asic_model", handle_asic_model }, + { "board_version", handle_board_version }, + + { "version", handle_version }, + { "axe_os_version", handle_axe_os_version }, + { "free_heap", handle_free_heap }, + { NULL, NULL } +}; + +esp_err_t display_config_format_string(GlobalState *GLOBAL_STATE, + const char *input, + char *output, + size_t max_len) +{ + if (!GLOBAL_STATE || !input || !output || max_len == 0) { + return ESP_ERR_INVALID_ARG; + } + + char temp_buffer[32]; + char var_buffer[32]; + const char *src = input; + char *dst = output; + size_t remaining = max_len - 1; + + while (*src && remaining > 0) { + if (*src == '{') { + const char *brace_end = strchr(src + 1, '}'); + if (brace_end) { + size_t var_len = brace_end - src - 1; + if (var_len > 0 && var_len < sizeof(var_buffer)) { + memcpy(var_buffer, src + 1, var_len); + var_buffer[var_len] = '\0'; + + bool found = false; + for (const var_entry_t *e = variables; e->var_name; ++e) { + if (strcmp(var_buffer, e->var_name) == 0) { + e->handler(GLOBAL_STATE, temp_buffer, sizeof(temp_buffer), + &dst, &remaining); + src = brace_end + 1; + found = true; + break; + } + } + if (found) continue; + } + } + } + *dst++ = *src++; + remaining--; + } + + *dst = '\0'; + return ESP_OK; +} + +esp_err_t display_config_get_variables(const char ***vars, size_t *count) { + if (!vars || !count) return ESP_ERR_INVALID_ARG; + + static const char *var_list[100]; // assume max 100 variables + size_t i = 0; + for (const var_entry_t *e = variables; e->var_name && i < 100; ++e) { + var_list[i++] = e->var_name; + } + + *vars = var_list; + *count = i; + return ESP_OK; +} diff --git a/main/display_config.h b/main/display_config.h new file mode 100644 index 000000000..0c5783b5a --- /dev/null +++ b/main/display_config.h @@ -0,0 +1,33 @@ +/** + * @file display_config.h + * @brief Display configuration and variable substitution header + * @author esp-miner team + * @date 2025-01-07 + */ + +#ifndef DISPLAY_CONFIG_H +#define DISPLAY_CONFIG_H + +#include +#include +#include "esp_err.h" +#include "global_state.h" + +/** + * @brief Format a display string with variable substitution + * @param input Input string with {variable} placeholders + * @param output Buffer for formatted output + * @param max_len Maximum buffer length + * @return ESP_OK on success, error code otherwise + */ +esp_err_t display_config_format_string(GlobalState *GLOBAL_STATE, const char *input, char *output, size_t max_len); + +/** + * @brief Get the list of available display variables + * @param vars Pointer to array of variable names (output) + * @param count Number of variables (output) + * @return ESP_OK on success, error code otherwise + */ +esp_err_t display_config_get_variables(const char ***vars, size_t *count); + +#endif // DISPLAY_CONFIG_H \ No newline at end of file diff --git a/main/http_server/axe-os/src/app/components/edit/edit.component.html b/main/http_server/axe-os/src/app/components/edit/edit.component.html index 528d92831..3eb033052 100644 --- a/main/http_server/axe-os/src/app/components/edit/edit.component.html +++ b/main/http_server/axe-os/src/app/components/edit/edit.component.html @@ -144,6 +144,81 @@

Display

+ +
+ + + + + + +
+ +
+
+ + + +
+
+ + +
+ + + +
+
+
+
+ +
+ + + +
+
+ +
+
+
Available Variables:
+ +
+
    +
  • + {{ '{' + item + '}' }} +
  • +
+
+
+
+
+
+

Statistics

diff --git a/main/http_server/axe-os/src/app/components/edit/edit.component.ts b/main/http_server/axe-os/src/app/components/edit/edit.component.ts index 95dee8fc3..9e7cdd214 100644 --- a/main/http_server/axe-os/src/app/components/edit/edit.component.ts +++ b/main/http_server/axe-os/src/app/components/edit/edit.component.ts @@ -47,6 +47,10 @@ export class EditComponent implements OnInit, OnDestroy, OnChanges { public displayTimeoutControl: FormControl; public statsFrequencyControl: FormControl; + public showCustomizeScreens = false; + public displayScreens: string[] = []; + public availableVariables: string[] = []; + constructor( private fb: FormBuilder, private systemService: SystemApiService, @@ -114,6 +118,8 @@ export class EditComponent implements OnInit, OnDestroy, OnChanges { ngOnInit(): void { this.loadDeviceSettings(); + this.loadDisplayScreens(); + this.loadDisplayVariables(); } ngOnChanges(changes: SimpleChanges): void { @@ -123,6 +129,10 @@ export class EditComponent implements OnInit, OnDestroy, OnChanges { } } + trackByIndex(index: number, item: any): number { + return index; + } + private loadDeviceSettings(): void { const deviceUri = this.uri || ''; @@ -213,6 +223,78 @@ export class EditComponent implements OnInit, OnDestroy, OnChanges { }); } + private loadDisplayScreens(): void { + const deviceUri = this.uri || ''; + this.systemService.getDisplayScreens(deviceUri) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: (screens) => { + this.displayScreens = screens.screens; + }, + error: (err) => { + console.error('Failed to load display screens:', err); + this.displayScreens = ['']; + } + }); + } + + private loadDisplayVariables(): void { + const deviceUri = this.uri || ''; + this.systemService.getDisplayVariables(deviceUri) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: (variables) => { + this.availableVariables = variables; + }, + error: (err) => { + console.error('Failed to load display variables:', err); + this.availableVariables = []; + } + }); + } + + public saveDisplayScreens(): void { + const deviceUri = this.uri || ''; + + this.systemService.updateDisplayScreens(deviceUri, this.displayScreens) + .pipe(this.loadingService.lockUIUntilComplete()) + .subscribe({ + next: () => { + this.toastr.success('Display screens saved successfully'); + }, + error: (err: HttpErrorResponse) => { + this.toastr.error(`Failed to save display screens: ${err.message}`); + } + }); + } + + public resetDisplayScreens(): void { + const deviceUri = this.uri || ''; + this.systemService.resetDisplayScreens(deviceUri) + .pipe(this.loadingService.lockUIUntilComplete()) + .subscribe({ + next: () => { + this.toastr.success('Display screens reset to defaults'); + this.loadDisplayScreens(); // Reload screens + }, + error: (err: HttpErrorResponse) => { + this.toastr.error(`Failed to reset display screens: ${err.message}`); + } + }); + } + + public addScreen(): void { + if (this.displayScreens.length < 8) { + this.displayScreens.push(''); + } + } + + public removeScreen(index: number): void { + if (this.displayScreens.length > 1) { + this.displayScreens.splice(index, 1); + } + } + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); diff --git a/main/http_server/axe-os/src/app/services/system.service.ts b/main/http_server/axe-os/src/app/services/system.service.ts index c4f8349de..479e70336 100644 --- a/main/http_server/axe-os/src/app/services/system.service.ts +++ b/main/http_server/axe-os/src/app/services/system.service.ts @@ -308,5 +308,96 @@ export class SystemApiService { }).pipe(delay(1000)); } + public getDisplayScreens(uri: string = ''): Observable<{ screens: string[] }> { + if (environment.production) { + return this.httpClient.get<{ screens: string[] }>(`${uri}/api/display/screens`).pipe(timeout(5000)); + } + + // Mock data for development + return of({ + screens: [ + "Gh/s: {hashrate}", + "J/Th: {efficiency}", + "Temp: {temp}°C", + "Best: {best_diff}" + ] + }).pipe(delay(1000)); + } + + public updateDisplayScreens(uri: string = '', screens: string[]): Observable { + if (environment.production) { + return this.httpClient.post(`${uri}/api/display/screens`, { screens }); + } + + return of({ success: true }).pipe(delay(1000)); + } + + public resetDisplayScreens(uri: string = ''): Observable { + if (environment.production) { + return this.httpClient.post(`${uri}/api/display/screens/reset`, {}); + } + + return of({ success: true }).pipe(delay(1000)); + } + + public getDisplayVariables(uri: string = ''): Observable { + if (environment.production) { + return this.httpClient.get(`${uri}/api/display/variables`).pipe(timeout(5000)); + } + // Mock data for development + return of([ + "hashrate", + "hashrate_1m", + "hashrate_10m", + "hashrate_1h", + "hashrate_expected", + "frequency", + "power", + "efficiency", + "voltage", + "core_voltage", + "current", + "power_fault", + "asic_temp", + "asic2_temp", + "vr_temp", + "target_temp", + "fan_perc", + "fan_rpm", + "fan2_rpm", + "pool_url", + "pool_difficulty", + "stratum_url", + "stratum_port", + "stratum_user", + "response_time", + "pool_connection_info", + "is_using_fallback_stratum", + "shares_a", + "shares_r", + "work_received", + "error_percentage", + "session_diff", + "best_diff", + "block_found", + "ssid", + "wifi_status", + "ip", + "ipv6", + "rssi", + "signal", + "uptime", + "network_diff", + "scriptsig", + "block_height", + "hostname", + "device_model", + "asic_model", + "board_version", + "version", + "axe_os_version", + "free_heap" + ]).pipe(delay(1000)); + } } diff --git a/main/http_server/http_server.c b/main/http_server/http_server.c index d0ef19b25..3acdf43e7 100644 --- a/main/http_server/http_server.c +++ b/main/http_server/http_server.c @@ -46,6 +46,8 @@ #include "http_server.h" #include "system.h" #include "websocket.h" +#include "screen.h" +#include "display_config.h" static const char * TAG = "http_server"; static const char * CORS_TAG = "CORS"; @@ -1071,6 +1073,169 @@ static esp_err_t GET_system_statistics(httpd_req_t * req) return res; } +/* Handler for getting display screens */ +static esp_err_t GET_display_screens(httpd_req_t * req) +{ + if (is_network_allowed(req) != ESP_OK) { + return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized"); + } + + httpd_resp_set_type(req, "application/json"); + + // Set CORS headers + if (set_cors_headers(req) != ESP_OK) { + httpd_resp_send_500(req); + return ESP_OK; + } + + cJSON *root = cJSON_CreateObject(); + cJSON *screens_array = cJSON_CreateArray(); + + // Get all screens from NVS + for (int i = 0; i < MAX_CAROUSEL_SCREENS; i++) { + char *screen_content = nvs_config_get_string_indexed(NVS_CONFIG_SCREENS, i); + if (screen_content && screen_content[0] != '\0') { + cJSON_AddItemToArray(screens_array, cJSON_CreateString(screen_content)); + } + free(screen_content); + } + + cJSON_AddItemToObject(root, "screens", screens_array); + cJSON_AddBoolToObject(root, "carouselEnabled", true); + + esp_err_t res = HTTP_send_json(req, root, &api_common_prebuffer_len); + + cJSON_Delete(root); + + return res; +} + +/* Handler for updating display screens */ +static esp_err_t POST_display_screens(httpd_req_t * req) +{ + if (is_network_allowed(req) != ESP_OK) { + return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized"); + } + + // Set CORS headers + if (set_cors_headers(req) != ESP_OK) { + httpd_resp_send_500(req); + return ESP_OK; + } + + int total_len = req->content_len; + int cur_len = 0; + char * buf = ((rest_server_context_t *) (req->user_ctx))->scratch; + int received = 0; + if (total_len >= SCRATCH_BUFSIZE) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "content too long"); + return ESP_OK; + } + while (cur_len < total_len) { + received = httpd_req_recv(req, buf + cur_len, total_len); + if (received <= 0) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to post screen data"); + return ESP_OK; + } + cur_len += received; + } + buf[total_len] = '\0'; + + cJSON * root = cJSON_Parse(buf); + if (root == NULL) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); + return ESP_OK; + } + + cJSON *screens_array = cJSON_GetObjectItem(root, "screens"); + if (!cJSON_IsArray(screens_array)) { + cJSON_Delete(root); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing screens array"); + return ESP_OK; + } + + // Update screens in NVS + int screen_count = cJSON_GetArraySize(screens_array); + if (screen_count > MAX_CAROUSEL_SCREENS) { + cJSON_Delete(root); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Too many screens"); + return ESP_OK; + } + + for (int i = 0; i < MAX_CAROUSEL_SCREENS; i++) { + char * value = ""; + if (i < screen_count) { + cJSON *content_item = cJSON_GetArrayItem(screens_array, i); + if (cJSON_IsString(content_item)) { + value = content_item->valuestring; + } + } + nvs_config_set_string_indexed(NVS_CONFIG_SCREENS, i, value); + } + + cJSON_Delete(root); + httpd_resp_send_chunk(req, NULL, 0); + return ESP_OK; +} + +/* Handler for resetting display screens to defaults */ +static esp_err_t POST_display_reset(httpd_req_t * req) +{ + if (is_network_allowed(req) != ESP_OK) { + return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized"); + } + + // Set CORS headers + if (set_cors_headers(req) != ESP_OK) { + httpd_resp_send_500(req); + return ESP_OK; + } + + // Reset all screens to defaults (empty strings in NVS) + for (int i = 0; i < MAX_CAROUSEL_SCREENS; i++) { + nvs_config_set_string_indexed(NVS_CONFIG_SCREENS, i, ""); + } + + httpd_resp_set_type(req, "text/plain"); + httpd_resp_send(req, "Display screens reset to defaults", HTTPD_RESP_USE_STRLEN); + return ESP_OK; +} + +/* Handler for getting display variables */ +static esp_err_t GET_display_variables(httpd_req_t * req) +{ + if (is_network_allowed(req) != ESP_OK) { + return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized"); + } + + httpd_resp_set_type(req, "application/json"); + + // Set CORS headers + if (set_cors_headers(req) != ESP_OK) { + httpd_resp_send_500(req); + return ESP_OK; + } + + const char **vars; + size_t count; + if (display_config_get_variables(&vars, &count) != ESP_OK) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to get variables"); + return ESP_OK; + } + + cJSON *root = cJSON_CreateArray(); + + for (size_t i = 0; i < count; i++) { + cJSON_AddItemToArray(root, cJSON_CreateString(vars[i])); + } + + esp_err_t res = HTTP_send_json(req, root, &api_common_prebuffer_len); + + cJSON_Delete(root); + + return res; +} + esp_err_t POST_WWW_update(httpd_req_t * req) { if (is_network_allowed(req) != ESP_OK) { @@ -1257,6 +1422,7 @@ esp_err_t start_rest_server(void * pvParameters) config.max_uri_handlers = 20; config.close_fn = websocket_close_fn; config.lru_purge_enable = true; + config.max_uri_handlers = 21; ESP_LOGI(TAG, "Starting HTTP Server"); REST_CHECK(httpd_start(&server, &config) == ESP_OK, "Start server failed", err_start); @@ -1269,6 +1435,14 @@ esp_err_t start_rest_server(void * pvParameters) }; httpd_register_uri_handler(server, &recovery_explicit_get_uri); + httpd_uri_t options_uri = { + .uri = "/api/*", + .method = HTTP_OPTIONS, + .handler = handle_options_request, + .user_ctx = NULL + }; + httpd_register_uri_handler(server, &options_uri); + // Register theme API endpoints ESP_ERROR_CHECK(register_theme_api_endpoints(server, rest_context)); @@ -1315,14 +1489,6 @@ esp_err_t start_rest_server(void * pvParameters) }; httpd_register_uri_handler(server, &system_identify_uri); - httpd_uri_t system_identify_options_uri = { - .uri = "/api/system/identify", - .method = HTTP_OPTIONS, - .handler = handle_options_request, - .user_ctx = NULL - }; - httpd_register_uri_handler(server, &system_identify_options_uri); - httpd_uri_t system_restart_uri = { .uri = "/api/system/restart", .method = HTTP_POST, .handler = POST_restart, @@ -1330,14 +1496,6 @@ esp_err_t start_rest_server(void * pvParameters) }; httpd_register_uri_handler(server, &system_restart_uri); - httpd_uri_t system_restart_options_uri = { - .uri = "/api/system/restart", - .method = HTTP_OPTIONS, - .handler = handle_options_request, - .user_ctx = NULL - }; - httpd_register_uri_handler(server, &system_restart_options_uri); - httpd_uri_t update_system_settings_uri = { .uri = "/api/system", .method = HTTP_PATCH, @@ -1346,13 +1504,40 @@ esp_err_t start_rest_server(void * pvParameters) }; httpd_register_uri_handler(server, &update_system_settings_uri); - httpd_uri_t system_options_uri = { - .uri = "/api/system", - .method = HTTP_OPTIONS, - .handler = handle_options_request, - .user_ctx = NULL, + /* URI handler for getting display screens */ + httpd_uri_t display_screens_get_uri = { + .uri = "/api/display/screens", + .method = HTTP_GET, + .handler = GET_display_screens, + .user_ctx = rest_context + }; + httpd_register_uri_handler(server, &display_screens_get_uri); + + /* URI handler for updating display screens */ + httpd_uri_t display_screens_post_uri = { + .uri = "/api/display/screens", + .method = HTTP_POST, + .handler = POST_display_screens, + .user_ctx = rest_context + }; + httpd_register_uri_handler(server, &display_screens_post_uri); + + /* URI handler for resetting display screens */ + httpd_uri_t display_reset_post_uri = { + .uri = "/api/display/screens/reset", + .method = HTTP_POST, + .handler = POST_display_reset, + .user_ctx = rest_context + }; + httpd_register_uri_handler(server, &display_reset_post_uri); + + httpd_uri_t display_variables_get_uri = { + .uri = "/api/display/variables", + .method = HTTP_GET, + .handler = GET_display_variables, + .user_ctx = rest_context }; - httpd_register_uri_handler(server, &system_options_uri); + httpd_register_uri_handler(server, &display_variables_get_uri); httpd_uri_t update_post_ota_firmware = { .uri = "/api/system/OTA", diff --git a/main/http_server/openapi.yaml b/main/http_server/openapi.yaml index c7bd3a27f..cb7cbd473 100644 --- a/main/http_server/openapi.yaml +++ b/main/http_server/openapi.yaml @@ -893,3 +893,78 @@ paths: description: Unauthorized - Client not in allowed network range '500': description: Internal server error + + /api/display/screens: + get: + summary: Get display screens configuration + description: Returns the current display screens configuration + operationId: getDisplayScreens + tags: + - display + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - screens + - carouselEnabled + properties: + screens: + type: array + items: + type: string + description: Screen content with variables + carouselEnabled: + type: boolean + description: Whether carousel mode is enabled + '401': + description: Unauthorized - Client not in allowed network range + '500': + description: Internal server error + post: + summary: Update display screens configuration + description: Updates the display screens configuration + operationId: updateDisplayScreens + tags: + - display + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - screens + properties: + screens: + type: array + items: + type: string + description: Screen content with variables + responses: + '200': + description: Screens updated successfully + '400': + description: Invalid configuration provided + '401': + description: Unauthorized - Client not in allowed network range + '500': + description: Internal server error + + /api/display/reset: + post: + summary: Reset display screens to defaults + description: Resets all display screens to their default configurations + operationId: resetDisplayScreens + tags: + - display + responses: + '200': + description: Screens reset to defaults + '401': + description: Unauthorized - Client not in allowed network range + '500': + description: Internal server error diff --git a/main/http_server/theme_api.c b/main/http_server/theme_api.c index 0756d127e..6f1428cff 100644 --- a/main/http_server/theme_api.c +++ b/main/http_server/theme_api.c @@ -16,14 +16,6 @@ static esp_err_t set_cors_headers(httpd_req_t *req) return ESP_OK; } -// Handle OPTIONS requests for CORS -static esp_err_t theme_options_handler(httpd_req_t *req) -{ - set_cors_headers(req); - httpd_resp_send(req, NULL, 0); - return ESP_OK; -} - // GET /api/theme handler static esp_err_t theme_get_handler(httpd_req_t *req) { @@ -104,16 +96,8 @@ esp_err_t register_theme_api_endpoints(httpd_handle_t server, void* ctx) .user_ctx = ctx }; - httpd_uri_t theme_options = { - .uri = "/api/theme", - .method = HTTP_OPTIONS, - .handler = theme_options_handler, - .user_ctx = ctx - }; - ESP_ERROR_CHECK(httpd_register_uri_handler(server, &theme_get)); ESP_ERROR_CHECK(httpd_register_uri_handler(server, &theme_post)); - ESP_ERROR_CHECK(httpd_register_uri_handler(server, &theme_options)); return ESP_OK; } diff --git a/main/nvs_config.c b/main/nvs_config.c index 5d5fdeea3..0286df6d4 100644 --- a/main/nvs_config.c +++ b/main/nvs_config.c @@ -13,6 +13,8 @@ #include #include "display.h" #include "theme_api.h" +#include "screen.h" +#include "default_screens.h" #define NVS_CONFIG_NAMESPACE "main" #define NVS_STR_LIMIT (4000 - 1) // See nvs_set_str @@ -36,6 +38,7 @@ typedef struct { NvsConfigKey key; ConfigType type; ConfigValue value; + int index; } ConfigUpdate; static const char * TAG = "nvs_config"; @@ -75,6 +78,7 @@ static Settings settings[NVS_CONFIG_COUNT] = { [NVS_CONFIG_INVERT_SCREEN] = {.nvs_key_name = "invertscreen", .type = TYPE_BOOL, .rest_name = "invertscreen", .min = 0, .max = 1}, [NVS_CONFIG_DISPLAY_OFFSET] = {.nvs_key_name = "displayOffset", .type = TYPE_U16, .default_value = {.u16 = LCD_SH1107_PARAM_DEFAULT_DISP_OFFSET }, .rest_name = "displayOffset", .min = 0, .max = UINT8_MAX}, [NVS_CONFIG_DISPLAY_TIMEOUT] = {.nvs_key_name = "displayTimeout", .type = TYPE_I32, .default_value = {.i32 = -1}, .rest_name = "displayTimeout", .min = -1, .max = UINT16_MAX}, + [NVS_CONFIG_SCREENS] = {.nvs_key_name = "screens", .type = TYPE_STR, .array_size = MAX_CAROUSEL_SCREENS, .rest_name = "displayScreens", .min = 0, .max = NVS_STR_LIMIT}, [NVS_CONFIG_AUTO_FAN_SPEED] = {.nvs_key_name = "autofanspeed", .type = TYPE_BOOL, .default_value = {.b = true}, .rest_name = "autofanspeed", .min = 0, .max = 1}, [NVS_CONFIG_MANUAL_FAN_SPEED] = {.nvs_key_name = "manualfanspeed", .type = TYPE_U16, .default_value = {.u16 = 100}, .rest_name = "manualFanSpeed", .min = 0, .max = 100}, @@ -86,7 +90,7 @@ static Settings settings[NVS_CONFIG_COUNT] = { [NVS_CONFIG_BEST_DIFF] = {.nvs_key_name = "bestdiff", .type = TYPE_U64}, [NVS_CONFIG_SELF_TEST] = {.nvs_key_name = "selftest", .type = TYPE_BOOL}, - [NVS_CONFIG_SWARM] = {.nvs_key_name = "swarmconfig", .type = TYPE_STR, .default_value = {.str = ""}}, + [NVS_CONFIG_SWARM] = {.nvs_key_name = "swarmconfig", .type = TYPE_STR}, [NVS_CONFIG_THEME_SCHEME] = {.nvs_key_name = "themescheme", .type = TYPE_STR, .default_value = {.str = DEFAULT_THEME}}, [NVS_CONFIG_THEME_COLORS] = {.nvs_key_name = "themecolors", .type = TYPE_STR, .default_value = {.str = DEFAULT_COLORS}}, @@ -118,6 +122,22 @@ Settings *nvs_config_get_settings(NvsConfigKey key) return &settings[key]; } +static int get_array_size(const Settings * setting) +{ + return (setting->array_size > 0) ? setting->array_size : 1; +} + +static void get_nvs_key_name(const Settings * setting, const int index, char dest[static NVS_KEY_NAME_MAX_SIZE]) +{ + if (setting->array_size > 0) { + int width = 1; + for (int t = setting->array_size - 1; t >= 10 && width < 5; t /= 10) width++; + snprintf(dest, NVS_KEY_NAME_MAX_SIZE, "%s_%0*d", setting->nvs_key_name, width, index + 1); + } else { + strncpy(dest, setting->nvs_key_name, NVS_KEY_NAME_MAX_SIZE); + } +} + static void nvs_config_init_fallback(NvsConfigKey key, Settings * setting) { esp_err_t ret; @@ -148,10 +168,10 @@ static void nvs_config_init_fallback(NvsConfigKey key, Settings * setting) static void nvs_config_apply_fallback(NvsConfigKey key, Settings * setting) { if (key == NVS_CONFIG_ASIC_FREQUENCY) { - nvs_set_u16(handle, FALLBACK_KEY_ASICFREQUENCY, (uint16_t) setting->value.f); + nvs_set_u16(handle, FALLBACK_KEY_ASICFREQUENCY, (uint16_t) setting->value[0].f); } if (key == NVS_CONFIG_MANUAL_FAN_SPEED) { - nvs_set_u16(handle, FALLBACK_KEY_FANSPEED, setting->value.u16); + nvs_set_u16(handle, FALLBACK_KEY_FANSPEED, setting->value[0].u16); } } @@ -163,34 +183,38 @@ static void nvs_task(void *pvParameters) Settings *setting = nvs_config_get_settings(update.key); if (setting && setting->type == update.type) { esp_err_t ret = ESP_OK; + + char key[NVS_KEY_NAME_MAX_SIZE]; + get_nvs_key_name(setting, update.index, key); + char *old_str = NULL; switch (update.type) { case TYPE_STR: - old_str = setting->value.str; - setting->value.str = update.value.str; - ret = nvs_set_str(handle, setting->nvs_key_name, setting->value.str); + old_str = setting->value[update.index].str; + setting->value[update.index].str = update.value.str; + ret = nvs_set_str(handle, key, setting->value[update.index].str); break; case TYPE_U16: - setting->value.u16 = update.value.u16; - ret = nvs_set_u16(handle, setting->nvs_key_name, setting->value.u16); + setting->value[update.index].u16 = update.value.u16; + ret = nvs_set_u16(handle, key, setting->value[update.index].u16); break; case TYPE_I32: - setting->value.i32 = update.value.i32; - ret = nvs_set_i32(handle, setting->nvs_key_name, setting->value.i32); + setting->value[update.index].i32 = update.value.i32; + ret = nvs_set_i32(handle, key, setting->value[update.index].i32); break; case TYPE_U64: - setting->value.u64 = update.value.u64; - ret = nvs_set_u64(handle, setting->nvs_key_name, setting->value.u64); + setting->value[update.index].u64 = update.value.u64; + ret = nvs_set_u64(handle, key, setting->value[update.index].u64); break; case TYPE_FLOAT: - setting->value.f = update.value.f; + setting->value[update.index].f = update.value.f; char buf[32]; - snprintf(buf, sizeof(buf), "%f", setting->value.f); - ret = nvs_set_str(handle, setting->nvs_key_name, buf); + snprintf(buf, sizeof(buf), "%f", setting->value[update.index].f); + ret = nvs_set_str(handle, key, buf); break; case TYPE_BOOL: - setting->value.b = update.value.b; - ret = nvs_set_u16(handle, setting->nvs_key_name, setting->value.b ? 1 : 0); + setting->value[update.index].b = update.value.b; + ret = nvs_set_u16(handle, key, setting->value[update.index].b ? 1 : 0); break; } @@ -224,6 +248,17 @@ esp_err_t nvs_config_init(void) ESP_LOGW(TAG, "Could not open nvs"); return err; } + + nvs_stats_t stats; + err = nvs_get_stats(NULL, &stats); + if (err == ESP_OK) { + ESP_LOGI(TAG, "Used entries: %lu", stats.used_entries); + ESP_LOGI(TAG, "Free entries: %lu", stats.free_entries); + ESP_LOGI(TAG, "Available entries: %lu", stats.available_entries); + ESP_LOGI(TAG, "Total entries: %lu", stats.total_entries); + } else { + ESP_LOGE(TAG, "Error getting NVS stats: %s\n", esp_err_to_name(err)); + } // Load all for (NvsConfigKey key = 0; key < NVS_CONFIG_COUNT; key++) { @@ -232,50 +267,72 @@ esp_err_t nvs_config_init(void) nvs_config_init_fallback(key, setting); esp_err_t ret; - switch (setting->type) { - case TYPE_STR: { - size_t len = 0; - nvs_get_str(handle, setting->nvs_key_name, NULL, &len); - char *buf = len > 0 ? malloc(len) : NULL; - if (buf) { - ret = nvs_get_str(handle, setting->nvs_key_name, buf, &len); - setting->value.str = (ret == ESP_OK) ? buf : strdup(setting->default_value.str); - if (ret != ESP_OK) free(buf); - } else { - setting->value.str = strdup(setting->default_value.str); + + int count = get_array_size(setting); + setting->value = calloc(count, sizeof(ConfigValue)); + + for (int idx = 0; idx < count; idx++) { + char nvs_key[NVS_KEY_NAME_MAX_SIZE]; + get_nvs_key_name(setting, idx, nvs_key); + + switch (setting->type) { + case TYPE_STR: { + size_t len = 0; + esp_err_t ret = nvs_get_str(handle, nvs_key, NULL, &len); + if (ret == ESP_OK && len > 1) { + char *buf = malloc(len); + if (buf) { + ret = nvs_get_str(handle, nvs_key, buf, &len); + if (ret == ESP_OK) { + setting->value[idx].str = buf; + ESP_LOGI(TAG, "%s: %s", nvs_key, buf); + break; + } + free(buf); + } + } + + const char *def = setting->default_value.str ? setting->default_value.str : ""; + setting->value[idx].str = strdup(def); + + // For display screens, if default is empty, use default_screens + if (key == NVS_CONFIG_SCREENS && setting->value[idx].str[0] == '\0') { + free(setting->value[idx].str); + setting->value[idx].str = strdup(default_screens[idx]); + } + break; + } + case TYPE_U16: { + uint16_t val; + ret = nvs_get_u16(handle, nvs_key, &val); + setting->value[idx].u16 = (ret == ESP_OK) ? val : setting->default_value.u16; + break; + } + case TYPE_I32: { + int32_t val; + ret = nvs_get_i32(handle, nvs_key, &val); + setting->value[idx].i32 = (ret == ESP_OK) ? val : setting->default_value.i32; + break; + } + case TYPE_U64: { + uint64_t val; + ret = nvs_get_u64(handle, nvs_key, &val); + setting->value[idx].u64 = (ret == ESP_OK) ? val : setting->default_value.u64; + break; + } + case TYPE_FLOAT: { + char buf[32]; + size_t len = sizeof(buf); + ret = nvs_get_str(handle, nvs_key, buf, &len); + setting->value[idx].f = (ret == ESP_OK) ? atof(buf) : setting->default_value.f; + break; + } + case TYPE_BOOL: { + uint16_t val; + ret = nvs_get_u16(handle, nvs_key, &val); + setting->value[idx].b = (ret == ESP_OK) ? (val != 0) : setting->default_value.b; + break; } - break; - } - case TYPE_U16: { - uint16_t val; - ret = nvs_get_u16(handle, setting->nvs_key_name, &val); - setting->value.u16 = (ret == ESP_OK) ? val : setting->default_value.u16; - break; - } - case TYPE_I32: { - int32_t val; - ret = nvs_get_i32(handle, setting->nvs_key_name, &val); - setting->value.i32 = (ret == ESP_OK) ? val : setting->default_value.i32; - break; - } - case TYPE_U64: { - uint64_t val; - ret = nvs_get_u64(handle, setting->nvs_key_name, &val); - setting->value.u64 = (ret == ESP_OK) ? val : setting->default_value.u64; - break; - } - case TYPE_FLOAT: { - char buf[32]; - size_t len = sizeof(buf); - ret = nvs_get_str(handle, setting->nvs_key_name, buf, &len); - setting->value.f = (ret == ESP_OK) ? atof(buf) : setting->default_value.f; - break; - } - case TYPE_BOOL: { - uint16_t val; - ret = nvs_get_u16(handle, setting->nvs_key_name, &val); - setting->value.b = (ret == ESP_OK) ? (val != 0) : setting->default_value.b; - break; } } } @@ -297,24 +354,43 @@ esp_err_t nvs_config_init(void) char *nvs_config_get_string(NvsConfigKey key) { Settings *setting = nvs_config_get_settings(key); - if (!setting || setting->type != TYPE_STR) { + if (!setting || setting->type != TYPE_STR || setting->array_size > 1) { ESP_LOGE(TAG, "Wrong type for %s (str)", setting->nvs_key_name); return NULL; } - return strdup(setting->value.str); + return strdup(setting->value[0].str); +} + +char *nvs_config_get_string_indexed(NvsConfigKey key, int index) +{ + Settings *setting = nvs_config_get_settings(key); + if (!setting || setting->type != TYPE_STR || setting->array_size < 1 || index < 0 || index >= setting->array_size) { + return NULL; + } + return strdup(setting->value[index].str); } void nvs_config_set_string(NvsConfigKey key, const char *value) { Settings *setting = nvs_config_get_settings(key); // Skip if invalid, wrong type, or value unchanged - if (!setting || setting->type != TYPE_STR || (setting->value.str && strcmp(setting->value.str, value) == 0)) return; + if (!setting || setting->type != TYPE_STR || (setting->value[0].str && strcmp(setting->value[0].str, value) == 0)) return; ConfigUpdate update = { .key = key, .type = TYPE_STR, .value.str = strdup(value) }; if (!update.value.str) return; xQueueSend(nvs_save_queue, &update, portMAX_DELAY); } +void nvs_config_set_string_indexed(NvsConfigKey key, int index, const char *value) +{ + Settings *setting = nvs_config_get_settings(key); + if (!setting || setting->type != TYPE_STR || (setting->value[index].str && strcmp(setting->value[index].str, value) == 0)) return; + + ConfigUpdate update = { .key = key, .type = TYPE_STR, .value.str = strdup(value), .index = index }; + if (!update.value.str) return; + xQueueSend(nvs_save_queue, &update, portMAX_DELAY); +} + uint16_t nvs_config_get_u16(NvsConfigKey key) { Settings *setting = nvs_config_get_settings(key); @@ -322,13 +398,13 @@ uint16_t nvs_config_get_u16(NvsConfigKey key) ESP_LOGE(TAG, "Wrong type for %s (u16)", setting->nvs_key_name); return 0; } - return setting->value.u16; + return setting->value[0].u16; } void nvs_config_set_u16(NvsConfigKey key, uint16_t value) { Settings *setting = nvs_config_get_settings(key); - if (!setting || setting->type != TYPE_U16 || setting->value.u16 == value) return; + if (!setting || setting->type != TYPE_U16 || setting->value[0].u16 == value) return; ConfigUpdate update = { .key = key, .type = TYPE_U16, .value.u16 = value }; xQueueSend(nvs_save_queue, &update, portMAX_DELAY); @@ -341,13 +417,13 @@ int32_t nvs_config_get_i32(NvsConfigKey key) ESP_LOGE(TAG, "Wrong type for %s (i32)", setting->nvs_key_name); return 0; } - return setting->value.i32; + return setting->value[0].i32; } void nvs_config_set_i32(NvsConfigKey key, int32_t value) { Settings *setting = nvs_config_get_settings(key); - if (!setting || setting->type != TYPE_I32 || setting->value.i32 == value) return; + if (!setting || setting->type != TYPE_I32 || setting->value[0].i32 == value) return; ConfigUpdate update = { .key = key, .type = TYPE_I32, .value.i32 = value }; xQueueSend(nvs_save_queue, &update, portMAX_DELAY); @@ -360,13 +436,13 @@ uint64_t nvs_config_get_u64(NvsConfigKey key) ESP_LOGE(TAG, "Wrong type for %s (u64)", setting->nvs_key_name); return 0; } - return setting->value.u64; + return setting->value[0].u64; } void nvs_config_set_u64(NvsConfigKey key, uint64_t value) { Settings *setting = nvs_config_get_settings(key); - if (!setting || setting->type != TYPE_U64 || setting->value.u64 == value) return; + if (!setting || setting->type != TYPE_U64 || setting->value[0].u64 == value) return; ConfigUpdate update = { .key = key, .type = TYPE_U64, .value.u64 = value }; xQueueSend(nvs_save_queue, &update, portMAX_DELAY); @@ -379,20 +455,19 @@ float nvs_config_get_float(NvsConfigKey key) ESP_LOGE(TAG, "Wrong type for %s (float)", setting->nvs_key_name); return 0; } - return setting->value.f; + return setting->value[0].f; } void nvs_config_set_float(NvsConfigKey key, float value) { Settings *setting = nvs_config_get_settings(key); // Skip if invalid, wrong type, or value unchanged (use epsilon for float comparison) - if (!setting || setting->type != TYPE_FLOAT || fabsf(setting->value.f - value) < 0.001f) return; + if (!setting || setting->type != TYPE_FLOAT || fabsf(setting->value[0].f - value) < 0.001f) return; ConfigUpdate update = { .key = key, .type = TYPE_FLOAT, .value.f = value }; xQueueSend(nvs_save_queue, &update, portMAX_DELAY); } - bool nvs_config_get_bool(NvsConfigKey key) { Settings *setting = nvs_config_get_settings(key); @@ -400,13 +475,13 @@ bool nvs_config_get_bool(NvsConfigKey key) ESP_LOGE(TAG, "Wrong type for %s (bool)", setting->nvs_key_name); return false; } - return setting->value.b; + return setting->value[0].b; } void nvs_config_set_bool(NvsConfigKey key, bool value) { Settings *setting = nvs_config_get_settings(key); - if (!setting || setting->type != TYPE_BOOL || setting->value.b == value) return; + if (!setting || setting->type != TYPE_BOOL || setting->value[0].b == value) return; ConfigUpdate update = { .key = key, .type = TYPE_BOOL, .value.b = value }; xQueueSend(nvs_save_queue, &update, portMAX_DELAY); diff --git a/main/nvs_config.h b/main/nvs_config.h index 14b600a2d..8cd9442ac 100644 --- a/main/nvs_config.h +++ b/main/nvs_config.h @@ -37,7 +37,8 @@ typedef enum { NVS_CONFIG_INVERT_SCREEN, NVS_CONFIG_DISPLAY_TIMEOUT, NVS_CONFIG_DISPLAY_OFFSET, - + NVS_CONFIG_SCREENS, + NVS_CONFIG_AUTO_FAN_SPEED, NVS_CONFIG_MANUAL_FAN_SPEED, NVS_CONFIG_MIN_FAN_SPEED, @@ -94,7 +95,8 @@ typedef union { typedef struct { const char *nvs_key_name; ConfigType type; - ConfigValue value; + ConfigValue *value; + int array_size; // Numbered entries ConfigValue default_value; const char *rest_name; int min; @@ -104,7 +106,9 @@ typedef struct { esp_err_t nvs_config_init(void); char * nvs_config_get_string(NvsConfigKey key); +char *nvs_config_get_string_indexed(NvsConfigKey key, int index); void nvs_config_set_string(NvsConfigKey key, const char * value); +void nvs_config_set_string_indexed(NvsConfigKey key, int index, const char *value); uint16_t nvs_config_get_u16(NvsConfigKey key); void nvs_config_set_u16(NvsConfigKey key, uint16_t value); int32_t nvs_config_get_i32(NvsConfigKey key); diff --git a/main/screen.c b/main/screen.c index 9e102f0a7..7dc0675aa 100644 --- a/main/screen.c +++ b/main/screen.c @@ -1,5 +1,4 @@ #include -#include "esp_log.h" #include "esp_err.h" #include "esp_check.h" #include "lvgl.h" @@ -10,6 +9,8 @@ #include "display.h" #include "connect.h" #include "esp_timer.h" +#include "default_screens.h" +#include "display_config.h" typedef enum { SCR_SELF_TEST, @@ -20,26 +21,24 @@ typedef enum { SCR_CONNECTION, SCR_BITAXE_LOGO, SCR_OSMU_LOGO, - SCR_URLS, - SCR_STATS, - SCR_MINING, - SCR_WIFI, + SCR_CAROUSEL, MAX_SCREENS, } screen_t; #define SCREEN_UPDATE_MS 500 -#define SCR_CAROUSEL_START SCR_URLS - extern const lv_img_dsc_t bitaxe_logo; extern const lv_img_dsc_t osmu_logo; extern const lv_img_dsc_t identify_text; static lv_obj_t * screens[MAX_SCREENS]; -static int delays_ms[MAX_SCREENS] = {0, 0, 0, 0, 0, 1000, 3000, 3000, 10000, 10000, 10000, 10000}; +static screen_t current_screen; + +static int delays_ms[MAX_SCREENS] = {0, 0, 0, 0, 0, 1000, 3000, 3000, 10000}; static int current_screen_time_ms; static int current_screen_delay_ms; +static int current_carousel_index = 0; // static int screen_chars; static int screen_lines; @@ -54,34 +53,15 @@ static lv_obj_t *overheat_ip_addr_label; static lv_obj_t *asic_status_label; -static lv_obj_t *mining_block_height_label; -static lv_obj_t *mining_network_difficulty_label; -static lv_obj_t *mining_scriptsig_label; - static lv_obj_t *firmware_update_scr_filename_label; static lv_obj_t *firmware_update_scr_status_label; static lv_obj_t *connection_wifi_status_label; -static lv_obj_t *urls_ip_addr_label; -static lv_obj_t *urls_mining_url_label; - -static lv_obj_t *stats_hashrate_label; -static lv_obj_t *stats_efficiency_label; -static lv_obj_t *stats_difficulty_label; -static lv_obj_t *stats_temp_label; - -static lv_obj_t *wifi_rssi_value_label; -static lv_obj_t *wifi_signal_strength_label; -static lv_obj_t *wifi_uptime_label; - static lv_obj_t *notification_label; static lv_obj_t *identify_image; -static float current_hashrate; -static float current_power; -static uint64_t current_difficulty; -static float current_chip_temp; +static lv_obj_t *carousel_labels[MAX_CAROUSEL_LABELS] = {0}; #define NOTIFICATION_SHARE_ACCEPTED (1 << 0) #define NOTIFICATION_SHARE_REJECTED (1 << 1) @@ -101,19 +81,9 @@ static const char *notifications[] = { static uint64_t current_shares_accepted; static uint64_t current_shares_rejected; static uint64_t current_work_received; -static int8_t current_rssi_value; -static int current_block_height; static bool self_test_finished; -static screen_t get_current_screen() { - lv_obj_t * active_screen = lv_screen_active(); - for (screen_t scr = 0; scr < MAX_SCREENS; scr++) { - if (screens[scr] == active_screen) return scr; - } - return -1; -} - static lv_obj_t * create_flex_screen(int expected_lines) { lv_obj_t * scr = lv_obj_create(NULL); @@ -286,77 +256,97 @@ static lv_obj_t * create_scr_osmu_logo() { return scr; } -static lv_obj_t * create_scr_urls() { - lv_obj_t * scr = create_flex_screen(4); - - lv_obj_t *label1 = lv_label_create(scr); - lv_label_set_text(label1, "Stratum Host:"); - - urls_mining_url_label = lv_label_create(scr); - lv_obj_set_width(urls_mining_url_label, LV_HOR_RES); - lv_label_set_long_mode(urls_mining_url_label, LV_LABEL_LONG_SCROLL_CIRCULAR); - - lv_obj_t *label3 = lv_label_create(scr); - lv_label_set_text(label3, "IP Address:"); - - urls_ip_addr_label = lv_label_create(scr); - - return scr; -} - -static lv_obj_t * create_scr_stats() { - lv_obj_t * scr = create_flex_screen(4); - - stats_hashrate_label = lv_label_create(scr); - lv_label_set_text(stats_hashrate_label, "Gh/s: --"); +static void update_carousel_screen_content(int screen_index, lv_obj_t *labels[MAX_CAROUSEL_LABELS]) +{ + // Get custom screen content from NVS + char *screen_content = nvs_config_get_string_indexed(NVS_CONFIG_SCREENS, screen_index); + const char *content = screen_content; + + // If empty or not found, use default + if (!content || content[0] == '\0') { + free(screen_content); + screen_content = NULL; + if (screen_index < DEFAULT_SCREENS_COUNT) { + content = default_screens[screen_index]; + } else { + content = ""; + } + } - stats_efficiency_label = lv_label_create(scr); - lv_label_set_text(stats_efficiency_label, "J/Th: --"); + char *content_copy = strdup(content); + if (content_copy) { + char *line = strtok(content_copy, "\n"); + int line_count = 0; + + while (line && line_count < MAX_CAROUSEL_LABELS && labels[line_count]) { + char formatted_line[128]; + if (display_config_format_string(GLOBAL_STATE, line, formatted_line, sizeof(formatted_line)) == ESP_OK) { + lv_label_set_text(labels[line_count], formatted_line); + } else { + lv_label_set_text(labels[line_count], line); + } + line = strtok(NULL, "\n"); + line_count++; + } - stats_difficulty_label = lv_label_create(scr); - lv_label_set_text(stats_difficulty_label, "Best: --"); + // Clear any remaining labels + for (int i = line_count; i < MAX_CAROUSEL_LABELS; i++) { + if (labels[i]) { + lv_label_set_text(labels[i], ""); + } + } - stats_temp_label = lv_label_create(scr); - lv_label_set_text(stats_temp_label, "Temp: --"); + free(content_copy); + } - return scr; + free(screen_content); } -static lv_obj_t * create_scr_mining() { - lv_obj_t * scr = create_flex_screen(4); - - mining_block_height_label = lv_label_create(scr); - lv_label_set_text(mining_block_height_label, "Block: --"); - - mining_network_difficulty_label = lv_label_create(scr); - lv_label_set_text(mining_network_difficulty_label, "Difficulty: --"); - - lv_obj_t *label3 = lv_label_create(scr); - lv_label_set_text(label3, "Scriptsig:"); +static lv_obj_t * create_scr_carousel(int screen_index) +{ + char *screen_content = nvs_config_get_string_indexed(NVS_CONFIG_SCREENS, screen_index); + const char *content = screen_content; + + if (!content || content[0] == '\0') { + free(screen_content); + screen_content = NULL; + if (screen_index < DEFAULT_SCREENS_COUNT) { + content = default_screens[screen_index]; + } else { + content = ""; + } + } - mining_scriptsig_label = lv_label_create(scr); - lv_label_set_text(mining_scriptsig_label, "--"); - lv_obj_set_width(mining_scriptsig_label, LV_HOR_RES); - lv_label_set_long_mode(mining_scriptsig_label, LV_LABEL_LONG_SCROLL_CIRCULAR); + // Truly empty → caller will skip this page + if (!content || content[0] == '\0') { + free(screen_content); + return NULL; + } - return scr; -} + // Count lines including blank ones + int total_lines = 1; + for (const char *p = content; *p; p++) { + if (*p == '\n') total_lines++; + } -static lv_obj_t * create_scr_wifi() { - lv_obj_t * scr = create_flex_screen(4); + int lines_to_show = total_lines; + if (lines_to_show > screen_lines) lines_to_show = screen_lines; + if (lines_to_show > MAX_CAROUSEL_LABELS) lines_to_show = MAX_CAROUSEL_LABELS; - lv_obj_t *title_label = lv_label_create(scr); - lv_label_set_text(title_label, "Wi-Fi Signal"); + lv_obj_t *scr = create_flex_screen(lines_to_show); - wifi_rssi_value_label = lv_label_create(scr); - lv_label_set_text(wifi_rssi_value_label, "RSSI: -- dBm"); + memset(carousel_labels, 0, sizeof(carousel_labels)); - wifi_signal_strength_label = lv_label_create(scr); - lv_label_set_text(wifi_signal_strength_label, "Signal: --%%"); + for (int i = 0; i < lines_to_show; i++) { + lv_obj_t *label = lv_label_create(scr); + lv_obj_set_width(label, LV_HOR_RES); + lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR); + carousel_labels[i] = label; + } - wifi_uptime_label = lv_label_create(scr); - lv_label_set_text(wifi_uptime_label, "Uptime: --"); + update_carousel_screen_content(screen_index, carousel_labels); + free(screen_content); return scr; } @@ -374,19 +364,27 @@ static void scr_create_overlay() static bool screen_show(screen_t screen) { - if (SCR_CAROUSEL_START > screen) { + if (screen < SCR_CAROUSEL) { lv_display_trigger_activity(NULL); } bool is_valid = true; - screen_t current_screen = get_current_screen(); - if (current_screen != screen) { - lv_obj_t * scr = screens[screen]; + if (current_screen != screen || screen == SCR_CAROUSEL) { + lv_obj_t *scr = NULL; + if (screen == SCR_CAROUSEL) { + scr = create_scr_carousel(current_carousel_index); + if (!scr) { + return false; + } + } else { + scr = screens[screen]; + } is_valid = lv_obj_is_valid(scr); if (is_valid && lvgl_port_lock(0)) { - bool auto_del = current_screen == SCR_BITAXE_LOGO || current_screen == SCR_OSMU_LOGO; + bool auto_del = (current_screen == SCR_BITAXE_LOGO || current_screen == SCR_OSMU_LOGO || current_screen == SCR_CAROUSEL); lv_screen_load_anim(scr, LV_SCR_LOAD_ANIM_MOVE_LEFT, LV_DEF_REFR_PERIOD * 128 / 8, 0, auto_del); + current_screen = screen; lvgl_port_unlock(); } @@ -398,15 +396,27 @@ static bool screen_show(screen_t screen) void screen_next() { - screen_t next_scr = get_current_screen(); + if (current_screen < SCR_CAROUSEL) { + screen_t next = current_screen + 1; + while (next < SCR_CAROUSEL) { + if (screen_show(next)) return; + next++; + } + current_carousel_index = 0; + screen_show(SCR_CAROUSEL); + return; + } + + int start_index = current_carousel_index; + do { - next_scr++; + current_carousel_index = (current_carousel_index + 1) % MAX_CAROUSEL_SCREENS; - if (next_scr == MAX_SCREENS) { - next_scr = SCR_CAROUSEL_START; + if (screen_show(SCR_CAROUSEL) || current_carousel_index == start_index) { + current_screen_time_ms = 0; + return; } - - } while (!screen_show(next_scr)); + } while (true); } static void screen_update_cb(lv_timer_t * timer) @@ -423,7 +433,7 @@ static void screen_update_cb(lv_timer_t * timer) // display timeout const uint32_t display_timeout = display_timeout_config * 60 * 1000; - if ((lv_display_get_inactive_time(NULL) > display_timeout) && (SCR_CAROUSEL_START <= get_current_screen()) && + if ((lv_display_get_inactive_time(NULL) > display_timeout) && (current_screen == SCR_CAROUSEL) && lv_obj_has_flag(identify_image, LV_OBJ_FLAG_HIDDEN)) { display_on(false); } else { @@ -505,90 +515,12 @@ static void screen_update_cb(lv_timer_t * timer) } // Carousel - current_screen_time_ms += SCREEN_UPDATE_MS; - PowerManagementModule * power_management = &GLOBAL_STATE->POWER_MANAGEMENT_MODULE; - - char *pool_url = module->is_using_fallback ? module->fallback_pool_url : module->pool_url; - if (strcmp(lv_label_get_text(urls_mining_url_label), pool_url) != 0) { - lv_label_set_text(urls_mining_url_label, pool_url); - } - - if (strcmp(lv_label_get_text(urls_ip_addr_label), module->ip_addr_str) != 0) { - lv_label_set_text(urls_ip_addr_label, module->ip_addr_str); - } - - if (current_hashrate != module->current_hashrate) { - lv_label_set_text_fmt(stats_hashrate_label, "Gh/s: %.2f", module->current_hashrate); - } - - if (current_power != power_management->power || current_hashrate != module->current_hashrate) { - if (power_management->power > 0 && module->current_hashrate > 0) { - float efficiency = power_management->power / (module->current_hashrate / 1000.0); - lv_label_set_text_fmt(stats_efficiency_label, "J/Th: %.2f", efficiency); - } - current_power = power_management->power; - } - current_hashrate = module->current_hashrate; - - if (current_difficulty != module->best_session_nonce_diff) { - if (module->block_found) { - lv_obj_set_width(stats_difficulty_label, LV_HOR_RES); - lv_label_set_long_mode(stats_difficulty_label, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_label_set_text_fmt(stats_difficulty_label, "Best: %s !!! BLOCK FOUND !!!", module->best_session_diff_string); - } else { - lv_label_set_text_fmt(stats_difficulty_label, "Best: %s/%s", module->best_session_diff_string, module->best_diff_string); - } - current_difficulty = module->best_session_nonce_diff; - } - - if (current_chip_temp != power_management->chip_temp_avg) { - if (power_management->chip_temp_avg > 0) { - lv_label_set_text_fmt(stats_temp_label, "Temp: %.1f°C", power_management->chip_temp_avg); - } - current_chip_temp = power_management->chip_temp_avg; - } - - if (current_block_height != GLOBAL_STATE->block_height) { - lv_label_set_text_fmt(mining_block_height_label, "Block: %d", GLOBAL_STATE->block_height); - current_block_height = GLOBAL_STATE->block_height; - } - - if (strcmp(&lv_label_get_text(mining_network_difficulty_label)[9], GLOBAL_STATE->network_diff_string) != 0) { - lv_label_set_text_fmt(mining_network_difficulty_label, "Difficulty: %s", GLOBAL_STATE->network_diff_string); - } - - if (GLOBAL_STATE->scriptsig != NULL && strcmp(lv_label_get_text(mining_scriptsig_label), GLOBAL_STATE->scriptsig) != 0) { - lv_label_set_text(mining_scriptsig_label, GLOBAL_STATE->scriptsig); - } - - // Update WiFi RSSI periodically - int8_t rssi_value = -128; - if (module->is_connected) { - get_wifi_current_rssi(&rssi_value); - } - - if (rssi_value != current_rssi_value) { - if (rssi_value > -50) { - lv_label_set_text(wifi_signal_strength_label, "Signal: Excellent"); - } else if (rssi_value > -60) { - lv_label_set_text(wifi_signal_strength_label, "Signal: Good"); - } else if (rssi_value > -70) { - lv_label_set_text(wifi_signal_strength_label, "Signal: Fair"); - } else if (rssi_value > -128){ - lv_label_set_text(wifi_signal_strength_label, "Signal: Weak"); - } else { - lv_label_set_text(wifi_signal_strength_label, "Signal: --"); - } - - if (rssi_value > -128) { - lv_label_set_text_fmt(wifi_rssi_value_label, "RSSI: %d dBm", rssi_value); - } else { - lv_label_set_text(wifi_rssi_value_label, "RSSI: -- dBm"); - } - current_rssi_value = rssi_value; - } + // Always update content in case variables changed + if (current_screen == SCR_CAROUSEL) { + update_carousel_screen_content(current_carousel_index, carousel_labels); + } uint32_t shares_accepted = module->shares_accepted; uint32_t shares_rejected = module->shares_rejected; @@ -613,8 +545,9 @@ static void screen_update_cb(lv_timer_t * timer) } if (module->block_found) { - if (get_current_screen() != SCR_STATS) { - screen_show(SCR_STATS); + if (current_screen != SCR_CAROUSEL || current_carousel_index != 1) { + current_carousel_index = 1; // Stats screen + screen_show(SCR_CAROUSEL); } lv_display_trigger_activity(NULL); @@ -637,35 +570,6 @@ void screen_button_press() } } -static void uptime_update_cb(lv_timer_t * timer) -{ - if (wifi_uptime_label) { - char uptime[50]; - uint32_t uptime_seconds = (esp_timer_get_time() - GLOBAL_STATE->SYSTEM_MODULE.start_time) / 1000000; - - uint32_t days = uptime_seconds / (24 * 3600); - uptime_seconds %= (24 * 3600); - uint32_t hours = uptime_seconds / 3600; - uptime_seconds %= 3600; - uint32_t minutes = uptime_seconds / 60; - uptime_seconds %= 60; - - if (days > 0) { - snprintf(uptime, sizeof(uptime), "Uptime: %ldd %ldh %ldm %lds", days, hours, minutes, uptime_seconds); - } else if (hours > 0) { - snprintf(uptime, sizeof(uptime), "Uptime: %ldh %ldm %lds", hours, minutes, uptime_seconds); - } else if (minutes > 0) { - snprintf(uptime, sizeof(uptime), "Uptime: %ldm %lds", minutes, uptime_seconds); - } else { - snprintf(uptime, sizeof(uptime), "Uptime: %lds", uptime_seconds); - } - - if (strcmp(lv_label_get_text(wifi_uptime_label), uptime) != 0) { - lv_label_set_text(wifi_uptime_label, uptime); - } - } -} - esp_err_t screen_start(void * pvParameters) { if (lvgl_port_lock(0)) { @@ -685,17 +589,11 @@ esp_err_t screen_start(void * pvParameters) screens[SCR_CONNECTION] = create_scr_connection(SYSTEM_MODULE->ssid, SYSTEM_MODULE->ap_ssid); screens[SCR_BITAXE_LOGO] = create_scr_bitaxe_logo(GLOBAL_STATE->DEVICE_CONFIG.family.name, GLOBAL_STATE->DEVICE_CONFIG.board_version); screens[SCR_OSMU_LOGO] = create_scr_osmu_logo(); - screens[SCR_URLS] = create_scr_urls(); - screens[SCR_STATS] = create_scr_stats(); - screens[SCR_MINING] = create_scr_mining(); - screens[SCR_WIFI] = create_scr_wifi(); + screens[SCR_CAROUSEL] = NULL; // Created dynamically scr_create_overlay(); lv_timer_create(screen_update_cb, SCREEN_UPDATE_MS, NULL); - - // Create uptime update timer (runs every 1 second) - lv_timer_create(uptime_update_cb, 1000, NULL); } lvgl_port_unlock(); } diff --git a/main/screen.h b/main/screen.h index 3d2cb36d1..aa8cdf7ab 100644 --- a/main/screen.h +++ b/main/screen.h @@ -1,6 +1,9 @@ #ifndef SCREEN_H_ #define SCREEN_H_ +#define MAX_CAROUSEL_SCREENS 8 +#define MAX_CAROUSEL_LABELS 16 + esp_err_t screen_start(void * pvParameters); void screen_button_press(void); diff --git a/readme.md b/readme.md index 219d545b8..57b10049a 100755 --- a/readme.md +++ b/readme.md @@ -60,6 +60,8 @@ Available API endpoints: * `/api/system/statistics` Get system statistics (data logging should be activated) * `/api/system/statistics/dashboard` Get system statistics for dashboard * `/api/system/wifi/scan` Scan for available Wi-Fi networks +* `/api/display/screens` Get display screens configuration +* `/api/display/variables` Get display variables **POST** @@ -67,6 +69,8 @@ Available API endpoints: * `/api/system/identify` Identify the device * `/api/system/OTA` Update system firmware * `/api/system/OTAWWW` Update AxeOS +* `/api/display/screens` Update display screens configuration +* `/api/display/screens/reset` Reset display screens to defaults **PATCH**