From 0e52e197688f139576b29fe646e2f1da4e8dd089 Mon Sep 17 00:00:00 2001 From: imlenti <280957457+imlenti@users.noreply.github.com> Date: Sun, 14 Jun 2026 12:04:01 +0200 Subject: [PATCH] Add mining pause and resume support Expose explicit mining transition states, stop and restart the BM1370 chain safely, reset hashrate baselines, suspend Stratum work, and keep board telemetry live while paused. --- components/asic/asic.c | 10 +++ components/asic/bm1370.c | 5 ++ components/asic/include/asic.h | 1 + components/asic/include/bm1370.h | 1 + main/global_state.h | 13 +++ main/http_server/http_server.c | 101 ++++++++++++++++++++- main/http_server/openapi.yaml | 61 +++++++++++-- main/main.c | 4 +- main/system.c | 4 + main/tasks/asic_result_task.c | 5 ++ main/tasks/asic_task.c | 6 ++ main/tasks/create_jobs_task.c | 4 +- main/tasks/hashrate_monitor_task.c | 42 +++++++-- main/tasks/hashrate_monitor_task.h | 1 + main/tasks/power_management_task.c | 137 ++++++++++++++++++++++++++++- main/tasks/stratum_task.c | 12 +++ 16 files changed, 390 insertions(+), 17 deletions(-) diff --git a/components/asic/asic.c b/components/asic/asic.c index 202fa95..2eea522 100644 --- a/components/asic/asic.c +++ b/components/asic/asic.c @@ -19,6 +19,16 @@ uint8_t ASIC_init(GlobalState * GLOBAL_STATE) { return ESP_OK; } +void ASIC_hold_reset_low(GlobalState * GLOBAL_STATE) { + switch (GLOBAL_STATE->device_model) { + case BITFORGE_NANO: + BM1370_hold_reset_low(); + break; + default: + break; + } +} + uint8_t ASIC_get_asic_count(GlobalState * GLOBAL_STATE) { switch (GLOBAL_STATE->device_model) { case BITFORGE_NANO: diff --git a/components/asic/bm1370.c b/components/asic/bm1370.c index 0edfe49..6c91322 100644 --- a/components/asic/bm1370.c +++ b/components/asic/bm1370.c @@ -414,6 +414,11 @@ static void _reset(void) vTaskDelay(100 / portTICK_PERIOD_MS); } +void BM1370_hold_reset_low(void) +{ + gpio_set_level(GPIO_ASIC_RESET, 0); +} + // static void _send_read_address(void) // { diff --git a/components/asic/include/asic.h b/components/asic/include/asic.h index e68c48a..e30dfec 100644 --- a/components/asic/include/asic.h +++ b/components/asic/include/asic.h @@ -8,6 +8,7 @@ #define BITFORGE_NANO_ASIC_COUNT 2 uint8_t ASIC_init(GlobalState * GLOBAL_STATE); +void ASIC_hold_reset_low(GlobalState * GLOBAL_STATE); uint8_t ASIC_get_asic_count(GlobalState * GLOBAL_STATE); uint16_t ASIC_get_small_core_count(GlobalState * GLOBAL_STATE); task_result * ASIC_process_work(GlobalState * GLOBAL_STATE); diff --git a/components/asic/include/bm1370.h b/components/asic/include/bm1370.h index fc7b9b0..0b4ecb7 100644 --- a/components/asic/include/bm1370.h +++ b/components/asic/include/bm1370.h @@ -38,6 +38,7 @@ typedef struct __attribute__((__packed__)) } BM1370_job; uint8_t BM1370_init(uint64_t frequency, uint16_t asic_count); +void BM1370_hold_reset_low(void); void BM1370_send_work(void * GLOBAL_STATE, bm_job * next_bm_job); void BM1370_set_job_difficulty_mask(int); void BM1370_set_version_mask(uint32_t version_mask); diff --git a/main/global_state.h b/main/global_state.h index 0a4d0fc..c821351 100644 --- a/main/global_state.h +++ b/main/global_state.h @@ -4,6 +4,7 @@ #include #include #include "freertos/FreeRTOS.h" +#include "freertos/task.h" #include "asic_task.h" #include "bm1370.h" #include "common.h" @@ -32,6 +33,14 @@ typedef enum ASIC_BM1370, } AsicModel; +typedef enum +{ + MINING_STATE_MINING = 0, + MINING_STATE_PAUSING, + MINING_STATE_PAUSED, + MINING_STATE_RESUMING, +} MiningState; + // typedef struct // { // uint8_t (*init_fn)(uint64_t, uint16_t); @@ -86,6 +95,8 @@ typedef struct bool is_using_fallback; char pool_connection_info[64]; uint16_t overheat_mode; + volatile bool mining_paused; + volatile MiningState mining_state; uint16_t power_fault; uint32_t lastClockSync; bool is_screen_active; @@ -144,6 +155,8 @@ typedef struct portMUX_TYPE stratum_mux; bool ASIC_initalized; + bool mining_control_ready; + TaskHandle_t power_management_task_handle; bool psram_is_available; } GlobalState; diff --git a/main/http_server/http_server.c b/main/http_server/http_server.c index 85d3402..4506120 100644 --- a/main/http_server/http_server.c +++ b/main/http_server/http_server.c @@ -718,6 +718,86 @@ static esp_err_t POST_restart(httpd_req_t * req) return ESP_OK; } +static const char * mining_state_name(MiningState state) +{ + switch (state) { + case MINING_STATE_PAUSING: + return "pausing"; + case MINING_STATE_PAUSED: + return "paused"; + case MINING_STATE_RESUMING: + return "resuming"; + case MINING_STATE_MINING: + default: + return "mining"; + } +} + +static esp_err_t set_mining_paused(httpd_req_t * req, bool paused) +{ + if (is_network_allowed(req) != ESP_OK) { + return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized"); + } + + if (!GLOBAL_STATE->mining_control_ready) { + httpd_resp_set_status(req, "503 Service Unavailable"); + httpd_resp_set_type(req, "application/json"); + return httpd_resp_sendstr(req, "{\"message\":\"Mining subsystem is not ready\"}"); + } + + if (set_cors_headers(req) != ESP_OK) { + httpd_resp_send_500(req); + return ESP_OK; + } + + SystemModule * system = &GLOBAL_STATE->SYSTEM_MODULE; + system->mining_paused = paused; + if (paused) { + if (system->mining_state != MINING_STATE_PAUSED) { + system->mining_state = MINING_STATE_PAUSING; + } + } else if (system->mining_state != MINING_STATE_MINING) { + system->mining_state = MINING_STATE_RESUMING; + } + + if (GLOBAL_STATE->power_management_task_handle != NULL) { + xTaskNotifyGive(GLOBAL_STATE->power_management_task_handle); + } + + ESP_LOGI(TAG, "Mining %s by API request", paused ? "pause requested" : "resume requested"); + + cJSON * root = cJSON_CreateObject(); + if (root == NULL) { + return httpd_resp_send_500(req); + } + + cJSON_AddStringToObject(root, "message", + paused ? "Mining pause requested" : "Mining resume requested"); + cJSON_AddBoolToObject(root, "miningPaused", paused); + cJSON_AddStringToObject(root, "miningState", mining_state_name(system->mining_state)); + + char * response = cJSON_PrintUnformatted(root); + cJSON_Delete(root); + if (response == NULL) { + return httpd_resp_send_500(req); + } + + httpd_resp_set_type(req, "application/json"); + esp_err_t result = httpd_resp_sendstr(req, response); + free(response); + return result; +} + +static esp_err_t POST_mining_pause(httpd_req_t * req) +{ + return set_mining_paused(req, true); +} + +static esp_err_t POST_mining_resume(httpd_req_t * req) +{ + return set_mining_paused(req, false); +} + /* Simple handler for getting system handler */ static esp_err_t GET_system_info(httpd_req_t * req) { @@ -755,7 +835,7 @@ static esp_err_t GET_system_info(httpd_req_t * req) cJSON * root = cJSON_CreateObject(); cJSON_AddNumberToObject(root, "power", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.power); cJSON_AddNumberToObject(root, "voltage", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.voltage); - cJSON_AddNumberToObject(root, "current", Power_get_current(GLOBAL_STATE)); + cJSON_AddNumberToObject(root, "current", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.current); cJSON_AddNumberToObject(root, "temp", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.chip_temp_avg); cJSON_AddNumberToObject(root, "vrTemp", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.vr_temp); cJSON_AddNumberToObject(root, "maxPower", Power_get_max_settings(GLOBAL_STATE)); @@ -821,6 +901,9 @@ static esp_err_t GET_system_info(httpd_req_t * req) cJSON_AddStringToObject(root, "runningPartition", esp_ota_get_running_partition()->label); cJSON_AddNumberToObject(root, "overheat_mode", nvs_config_get_u16(NVS_CONFIG_OVERHEAT_MODE, 0)); + cJSON_AddBoolToObject(root, "miningPaused", GLOBAL_STATE->SYSTEM_MODULE.mining_paused); + cJSON_AddStringToObject(root, "miningState", + mining_state_name(GLOBAL_STATE->SYSTEM_MODULE.mining_state)); cJSON_AddNumberToObject(root, "overclockEnabled", nvs_config_get_u16(NVS_CONFIG_OVERCLOCK_ENABLED, 0)); cJSON_AddNumberToObject(root, "autofanspeed", nvs_config_get_u16(NVS_CONFIG_AUTO_FAN_SPEED, 1)); @@ -1279,6 +1362,22 @@ esp_err_t start_rest_server(void * pvParameters) .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 system_mining_pause_uri = { + .uri = "/api/system/pause", .method = HTTP_POST, .handler = POST_mining_pause, .user_ctx = rest_context}; + httpd_register_uri_handler(server, &system_mining_pause_uri); + + httpd_uri_t system_mining_pause_options_uri = { + .uri = "/api/system/pause", .method = HTTP_OPTIONS, .handler = handle_options_request, .user_ctx = NULL}; + httpd_register_uri_handler(server, &system_mining_pause_options_uri); + + httpd_uri_t system_mining_resume_uri = { + .uri = "/api/system/resume", .method = HTTP_POST, .handler = POST_mining_resume, .user_ctx = rest_context}; + httpd_register_uri_handler(server, &system_mining_resume_uri); + + httpd_uri_t system_mining_resume_options_uri = { + .uri = "/api/system/resume", .method = HTTP_OPTIONS, .handler = handle_options_request, .user_ctx = NULL}; + httpd_register_uri_handler(server, &system_mining_resume_options_uri); + httpd_uri_t update_system_settings_uri = { .uri = "/api/system", .method = HTTP_PATCH, .handler = PATCH_update_settings, .user_ctx = rest_context}; httpd_register_uri_handler(server, &update_system_settings_uri); diff --git a/main/http_server/openapi.yaml b/main/http_server/openapi.yaml index 1ecb254..c92f521 100644 --- a/main/http_server/openapi.yaml +++ b/main/http_server/openapi.yaml @@ -99,6 +99,8 @@ components: - isPSRAMAvailable - isUsingFallbackStratum - macAddr + - miningPaused + - miningState - overheat_mode - power - runningPartition @@ -146,7 +148,7 @@ components: description: Actual measured ASIC core voltage current: type: number - description: Current draw in milliamps + description: Live board input current in milliamps, including while mining is paused fallbackStratumPort: type: number description: Fallback stratum server port @@ -158,7 +160,7 @@ components: description: Fallback stratum username fanrpm: type: number - description: Current fan speed in RPM + description: Current fan speed in RPM, including while mining is paused fanSpeed: type: number description: Current fan speed percentage @@ -195,12 +197,19 @@ components: macAddr: type: string description: Device MAC address + miningPaused: + type: boolean + description: Whether mining pause has been requested + miningState: + type: string + description: Current mining hardware transition state + enum: [mining, pausing, paused, resuming] overheat_mode: type: number description: Overheat protection mode power: type: number - description: Power consumption in watts + description: Live board input power in watts, including controller and fans while mining is paused runningPartition: type: string description: Currently active OTA partition @@ -230,7 +239,7 @@ components: description: Primary stratum username temp: type: number - description: Average chip temperature + description: Average ASIC temperature, or 0 while ASIC thermal diodes are unavailable during pause uptimeSeconds: type: number description: System uptime in seconds @@ -239,7 +248,7 @@ components: description: Firmware version voltage: type: number - description: Input voltage + description: Live board input voltage in millivolts, including while mining is paused vrTemp: type: number description: Voltage regulator temperature @@ -376,6 +385,48 @@ components: description: Internal server error paths: + /api/system/pause: + post: + summary: Pause mining while keeping the controller online + responses: + "200": + description: Pause request accepted + content: + application/json: + schema: + type: object + required: [message, miningPaused, miningState] + properties: + message: + type: string + miningPaused: + type: boolean + miningState: + type: string + enum: [mining, pausing, paused, resuming] + "503": + description: Mining subsystem is not ready + /api/system/resume: + post: + summary: Resume mining and reinitialize the ASIC chain + responses: + "200": + description: Resume request accepted + content: + application/json: + schema: + type: object + required: [message, miningPaused, miningState] + properties: + message: + type: string + miningPaused: + type: boolean + miningState: + type: string + enum: [mining, pausing, paused, resuming] + "503": + description: Mining subsystem is not ready /api/system/wifi/scan: get: summary: Scan for available WiFi networks diff --git a/main/main.c b/main/main.c index 7c8c99f..4fe2bdd 100644 --- a/main/main.c +++ b/main/main.c @@ -119,7 +119,8 @@ void app_main(void) SYSTEM_init_peripherals(&GLOBAL_STATE); - xTaskCreate(POWER_MANAGEMENT_task, "power management", 8192, (void *) &GLOBAL_STATE, 10, NULL); + xTaskCreate(POWER_MANAGEMENT_task, "power management", 8192, (void *) &GLOBAL_STATE, 10, + &GLOBAL_STATE.power_management_task_handle); //start the API for AxeOS start_rest_server((void *) &GLOBAL_STATE); @@ -175,6 +176,7 @@ void app_main(void) SERIAL_clear_buffer(); GLOBAL_STATE.ASIC_initalized = true; + GLOBAL_STATE.mining_control_ready = true; xTaskCreate(stratum_task, "stratum admin", 8192, (void *) &GLOBAL_STATE, 5, NULL); xTaskCreate(create_jobs_task, "stratum miner", 8192, (void *) &GLOBAL_STATE, 10, NULL); diff --git a/main/system.c b/main/system.c index 88945ef..dfcd165 100644 --- a/main/system.c +++ b/main/system.c @@ -96,6 +96,10 @@ void SYSTEM_init_system(GlobalState * GLOBAL_STATE) module->overheat_mode = nvs_config_get_u16(NVS_CONFIG_OVERHEAT_MODE, 0); ESP_LOGI(TAG, "Initial overheat_mode value: %d", module->overheat_mode); + // Pause is intentionally not persisted: every clean boot starts mining. + module->mining_paused = false; + module->mining_state = MINING_STATE_MINING; + //Initialize power_fault fault mode module->power_fault = 0; diff --git a/main/tasks/asic_result_task.c b/main/tasks/asic_result_task.c index c33944f..bf15227 100644 --- a/main/tasks/asic_result_task.c +++ b/main/tasks/asic_result_task.c @@ -19,6 +19,11 @@ void ASIC_result_task(void *pvParameters) while (1) { + if (!GLOBAL_STATE->ASIC_initalized) { + vTaskDelay(100 / portTICK_PERIOD_MS); + continue; + } + //task_result *asic_result = (*GLOBAL_STATE->ASIC_functions.receive_result_fn)(GLOBAL_STATE); task_result *asic_result = ASIC_process_work(GLOBAL_STATE); diff --git a/main/tasks/asic_task.c b/main/tasks/asic_task.c index 5def6cf..b201ca8 100644 --- a/main/tasks/asic_task.c +++ b/main/tasks/asic_task.c @@ -37,6 +37,12 @@ void ASIC_task(void *pvParameters) bm_job *next_bm_job = (bm_job *)queue_dequeue(&GLOBAL_STATE->ASIC_jobs_queue); + if (!GLOBAL_STATE->ASIC_initalized) { + free_bm_job(next_bm_job); + vTaskDelay(100 / portTICK_PERIOD_MS); + continue; + } + if (next_bm_job->pool_diff != GLOBAL_STATE->stratum_difficulty) { ESP_LOGI(TAG, "New pool difficulty %.2f", next_bm_job->pool_diff); diff --git a/main/tasks/create_jobs_task.c b/main/tasks/create_jobs_task.c index 432c4a7..d907e78 100644 --- a/main/tasks/create_jobs_task.c +++ b/main/tasks/create_jobs_task.c @@ -49,7 +49,9 @@ void create_jobs_task(void *pvParameters) } uint64_t extranonce_2 = 0; - while (queue_count(&GLOBAL_STATE->stratum_queue) < 1 && GLOBAL_STATE->abandon_work == 0) + while (queue_count(&GLOBAL_STATE->stratum_queue) < 1 && + GLOBAL_STATE->abandon_work == 0 && + !GLOBAL_STATE->SYSTEM_MODULE.mining_paused) { if (should_generate_more_work(GLOBAL_STATE)) { diff --git a/main/tasks/hashrate_monitor_task.c b/main/tasks/hashrate_monitor_task.c index 794e48d..55a5f58 100644 --- a/main/tasks/hashrate_monitor_task.c +++ b/main/tasks/hashrate_monitor_task.c @@ -54,6 +54,28 @@ static void clear_measurements(HashrateMonitorModule * HASHRATE_MONITOR_MODULE, memset(HASHRATE_MONITOR_MODULE->error_measurement, 0, asic_count * sizeof(measurement_t)); } +void hashrate_monitor_reset(void *pvParameters) +{ + GlobalState * GLOBAL_STATE = (GlobalState *)pvParameters; + HashrateMonitorModule * HASHRATE_MONITOR_MODULE = &GLOBAL_STATE->HASHRATE_MONITOR_MODULE; + + if (!HASHRATE_MONITOR_MODULE->is_initialized) { + HASHRATE_MONITOR_MODULE->hashrate = 0.0f; + HASHRATE_MONITOR_MODULE->error_count = 0; + return; + } + + int asic_count = ASIC_get_asic_count(GLOBAL_STATE); + + pthread_mutex_lock(&HASHRATE_MONITOR_MODULE->measurement_lock); + clear_measurements(HASHRATE_MONITOR_MODULE, asic_count, BM1370_HASH_DOMAINS); + HASHRATE_MONITOR_MODULE->hashrate = 0.0f; + HASHRATE_MONITOR_MODULE->error_count = 0; + pthread_mutex_unlock(&HASHRATE_MONITOR_MODULE->measurement_lock); + + ESP_LOGI(TAG, "Hashrate estimator reset"); +} + float hash_counter_to_ghs(uint32_t duration_ms, uint32_t counter) { if (duration_ms == 0) return 0.0f; @@ -158,6 +180,12 @@ void hashrate_monitor_task(void *pvParameters) TickType_t taskWakeTime = xTaskGetTickCount(); while (1) { + if (!GLOBAL_STATE->ASIC_initalized) { + taskWakeTime = xTaskGetTickCount(); + vTaskDelay(100 / portTICK_PERIOD_MS); + continue; + } + BM1370_read_registers(); ESP_LOGD(TAG, "Sent register read commands"); @@ -166,9 +194,6 @@ void hashrate_monitor_task(void *pvParameters) pthread_mutex_lock(&HASHRATE_MONITOR_MODULE->measurement_lock); float hashrate = sum_hashrates(HASHRATE_MONITOR_MODULE->total_measurement, asic_count); uint32_t error_count = sum_values(HASHRATE_MONITOR_MODULE->error_measurement, asic_count); - pthread_mutex_unlock(&HASHRATE_MONITOR_MODULE->measurement_lock); - - ESP_LOGI(TAG, "Calculated hashrate: %.2f GH/s", hashrate); if (hashrate == 0.0) { HASHRATE_MONITOR_MODULE->hashrate = 0.0; @@ -176,14 +201,17 @@ void hashrate_monitor_task(void *pvParameters) if (HASHRATE_MONITOR_MODULE->hashrate == 0.0f) { // Initialize with current hashrate HASHRATE_MONITOR_MODULE->hashrate = hashrate; - ESP_LOGI(TAG, "Initial hashrate set to %.2f GH/s", hashrate); } else { HASHRATE_MONITOR_MODULE->hashrate = ((HASHRATE_MONITOR_MODULE->hashrate * (EMA_ALPHA - 1)) + hashrate) / EMA_ALPHA; - ESP_LOGI(TAG, "EMA hashrate: %.2f GH/s", HASHRATE_MONITOR_MODULE->hashrate); } } HASHRATE_MONITOR_MODULE->error_count = error_count; + float filtered_hashrate = HASHRATE_MONITOR_MODULE->hashrate; + pthread_mutex_unlock(&HASHRATE_MONITOR_MODULE->measurement_lock); + + ESP_LOGI(TAG, "Calculated hashrate: %.2f GH/s", hashrate); + ESP_LOGI(TAG, "Reported hashrate: %.2f GH/s", filtered_hashrate); vTaskDelayUntil(&taskWakeTime, POLL_RATE / portTICK_PERIOD_MS); } @@ -200,6 +228,10 @@ void hashrate_monitor_register_read(void *pvParameters, register_type_t register int asic_count = ASIC_get_asic_count(GLOBAL_STATE); int hash_domains = BM1370_HASH_DOMAINS; + if (!GLOBAL_STATE->ASIC_initalized || !HASHRATE_MONITOR_MODULE->is_initialized) { + return; + } + ESP_LOGD(TAG, "Register read: type=%d, asic=%d, value=0x%08"PRIX32, register_type, asic_nr, value); if (asic_nr >= asic_count) { diff --git a/main/tasks/hashrate_monitor_task.h b/main/tasks/hashrate_monitor_task.h index 9ded101..2ecf694 100644 --- a/main/tasks/hashrate_monitor_task.h +++ b/main/tasks/hashrate_monitor_task.h @@ -23,6 +23,7 @@ typedef struct { void hashrate_monitor_task(void *pvParameters); void hashrate_monitor_register_read(void *pvParameters, register_type_t register_type, uint8_t asic_nr, uint32_t value); +void hashrate_monitor_reset(void *pvParameters); float hash_counter_to_ghs(uint32_t duration_ms, uint32_t counter); #endif /* HASHRATE_MONITOR_TASK_H_ */ diff --git a/main/tasks/power_management_task.c b/main/tasks/power_management_task.c index bc4ba47..adb3b8f 100644 --- a/main/tasks/power_management_task.c +++ b/main/tasks/power_management_task.c @@ -15,6 +15,7 @@ #include "power.h" #include "asic.h" #include "adc.h" +#include "hashrate_monitor_task.h" #define POLL_RATE 2000 #define MAX_TEMP 90.0 @@ -27,11 +28,107 @@ #define TPS546_THROTTLE_TEMP 105.0 #define TPS546_MAX_TEMP 145.0 +#define PAUSED_FAN_SPEED 30.0 static const char * TAG = "power_management"; static bool even = false; +static void update_input_telemetry(GlobalState * GLOBAL_STATE) +{ + PowerManagementModule * power_management = &GLOBAL_STATE->POWER_MANAGEMENT_MODULE; + + power_management->voltage = Power_get_input_voltage(GLOBAL_STATE); + power_management->current = Power_get_current(GLOBAL_STATE); + power_management->power = Power_get_power(GLOBAL_STATE); +} + +static void update_paused_telemetry(GlobalState * GLOBAL_STATE) +{ + PowerManagementModule * power_management = &GLOBAL_STATE->POWER_MANAGEMENT_MODULE; + + update_input_telemetry(GLOBAL_STATE); + + PAC9544_selectChannel(even + 2U); + vTaskDelay(pdMS_TO_TICKS(10)); + power_management->vr_temp = Power_get_vreg_temp(GLOBAL_STATE); + power_management->fan_rpm[even] = Thermal_getFanSpeed(); + + // ASIC thermal diodes are not valid while VCORE is disabled. + power_management->chip_temp[0] = 0.0f; + power_management->chip_temp[1] = 0.0f; + power_management->chip_temp_avg = 0.0f; + + even = !even; +} + +static void mining_stop(GlobalState * GLOBAL_STATE) +{ + ESP_LOGI(TAG, "Stopping mining"); + + // Stop the worker tasks before changing the ASIC clock and power state. + GLOBAL_STATE->ASIC_initalized = false; + hashrate_monitor_reset(GLOBAL_STATE); + + // Match AxeOS: wind the chain down before switching off the TPS546 output. + if (!ASIC_set_frequency(GLOBAL_STATE, 50.0f)) { + ESP_LOGW(TAG, "Unable to reduce ASIC frequency before pause"); + } + + if (VCORE_set_voltage(0.0f, GLOBAL_STATE) != ESP_OK) { + ESP_LOGE(TAG, "Unable to turn off ASIC core voltage"); + } + ASIC_hold_reset_low(GLOBAL_STATE); + + // Let in-flight UART work finish, then discard stale bytes. + vTaskDelay(500 / portTICK_PERIOD_MS); + SERIAL_clear_buffer(); + vTaskDelay(100 / portTICK_PERIOD_MS); + + hashrate_monitor_reset(GLOBAL_STATE); + GLOBAL_STATE->POWER_MANAGEMENT_MODULE.fan_perc = PAUSED_FAN_SPEED; + Thermal_setFanSpeedPercent(PAUSED_FAN_SPEED / 100.0f); + + ESP_LOGI(TAG, "Mining stopped"); +} + +static bool mining_start(GlobalState * GLOBAL_STATE) +{ + ESP_LOGI(TAG, "Starting mining"); + + uint16_t voltage = nvs_config_get_u16(NVS_CONFIG_ASIC_VOLTAGE, CONFIG_ASIC_VOLTAGE); + if (VCORE_set_voltage((double) voltage / 1000.0, GLOBAL_STATE) != ESP_OK) { + ESP_LOGE(TAG, "Unable to restore ASIC core voltage"); + return false; + } + + vTaskDelay(500 / portTICK_PERIOD_MS); + + // A freshly reset BM1370 chain starts at the default UART baud. + SERIAL_set_baud(115200); + SERIAL_clear_buffer(); + vTaskDelay(100 / portTICK_PERIOD_MS); + + uint8_t chip_count = ASIC_init(GLOBAL_STATE); + if (chip_count == 0) { + ESP_LOGE(TAG, "Mining resume failed: ASIC chain not detected"); + ASIC_hold_reset_low(GLOBAL_STATE); + VCORE_set_voltage(0.0f, GLOBAL_STATE); + return false; + } + + SERIAL_set_baud(ASIC_set_max_baud(GLOBAL_STATE)); + SERIAL_clear_buffer(); + + // ASIC counters restart with the chain. Discard every pre-pause baseline + // before register polling is allowed to resume. + hashrate_monitor_reset(GLOBAL_STATE); + GLOBAL_STATE->ASIC_initalized = true; + + ESP_LOGI(TAG, "Mining started successfully (%u chip(s))", chip_count); + return true; +} + // Set the fan speed between 35% min and 100% max based on ASIC and VR temperatures. // Uses the higher of the two temperature-based fan speed requirements. // ASIC: 45°C (35%) → 75°C (100%) @@ -102,13 +199,45 @@ void POWER_MANAGEMENT_task(void * pvParameters) vTaskDelay(500 / portTICK_PERIOD_MS); uint16_t last_core_voltage = 0.0; uint16_t last_asic_frequency = power_management->frequency_value; + bool is_user_paused = false; while (1) { + if (sys_module->mining_paused && !is_user_paused) { + sys_module->mining_state = MINING_STATE_PAUSING; + mining_stop(GLOBAL_STATE); + is_user_paused = true; + if (!sys_module->mining_paused) { + sys_module->mining_state = MINING_STATE_RESUMING; + continue; + } + sys_module->mining_state = MINING_STATE_PAUSED; + } else if (!sys_module->mining_paused && is_user_paused) { + sys_module->mining_state = MINING_STATE_RESUMING; + if (mining_start(GLOBAL_STATE)) { + is_user_paused = false; + if (sys_module->mining_paused) { + sys_module->mining_state = MINING_STATE_PAUSING; + continue; + } + sys_module->mining_state = MINING_STATE_MINING; + } else { + // Keep the externally visible state honest if recovery fails. + sys_module->mining_paused = true; + sys_module->mining_state = MINING_STATE_PAUSED; + } + } + + if (is_user_paused) { + // Keep board-level telemetry live while the ASIC rail is off. + update_paused_telemetry(GLOBAL_STATE); + ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(POLL_RATE)); + continue; + } + PAC9544_selectChannel(even + 2U); vTaskDelay(pdMS_TO_TICKS(10)); // Allow PAC9544 channel switch to settle - power_management->voltage = Power_get_input_voltage(GLOBAL_STATE); - power_management->power = Power_get_power(GLOBAL_STATE); + update_input_telemetry(GLOBAL_STATE); #ifdef POWER_DEBUG ESP_LOGI(TAG, "POWER: %f", power_management->power); #endif @@ -221,7 +350,7 @@ void POWER_MANAGEMENT_task(void * pvParameters) VCORE_check_fault(GLOBAL_STATE); even = !even; - // looper: - vTaskDelay(POLL_RATE / portTICK_PERIOD_MS); + // API pause/resume requests wake this task without shortening sensor polling. + ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(POLL_RATE)); } } diff --git a/main/tasks/stratum_task.c b/main/tasks/stratum_task.c index f31a328..16016e4 100644 --- a/main/tasks/stratum_task.c +++ b/main/tasks/stratum_task.c @@ -367,6 +367,11 @@ void stratum_task(void * pvParameters) ESP_LOGI(TAG, "Opening connection to pool: %s:%d", stratum_url, port); while (1) { + if (GLOBAL_STATE->SYSTEM_MODULE.mining_paused) { + vTaskDelay(500 / portTICK_PERIOD_MS); + continue; + } + if (!is_wifi_connected()) { ESP_LOGI(TAG, "WiFi disconnected, attempting to reconnect..."); vTaskDelay(10000 / portTICK_PERIOD_MS); @@ -475,6 +480,13 @@ void stratum_task(void * pvParameters) GLOBAL_STATE->abandon_work = 0; while (1) { + if (GLOBAL_STATE->SYSTEM_MODULE.mining_paused) { + ESP_LOGI(TAG, "Mining paused, disconnecting from pool"); + retry_attempts = 0; + stratum_close_connection(GLOBAL_STATE); + break; + } + char * line = STRATUM_V1_receive_jsonrpc_line(GLOBAL_STATE->transport); if (!line) { ESP_LOGE(TAG, "Failed to receive JSON-RPC line, reconnecting...");