diff --git a/components/influx/CMakeLists.txt b/components/influx/CMakeLists.txt new file mode 100644 index 000000000..c0ca7571c --- /dev/null +++ b/components/influx/CMakeLists.txt @@ -0,0 +1,12 @@ +idf_component_register( + SRCS + "influx.c" + INCLUDE_DIRS + "include" + REQUIRES + "esp_http_client" + "json" + "esp_timer" + "nvs_flash" + "driver" +) \ No newline at end of file diff --git a/components/influx/include/influx.h b/components/influx/include/influx.h new file mode 100644 index 000000000..4f7ba5452 --- /dev/null +++ b/components/influx/include/influx.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + float asicTemp; + float vrTemp; + float hashingSpeed; + int invalidShares; + int validShares; + int difficulty; + float bestSessionDifficulty; + int poolErrors; + int accepted; + int notAccepted; + int totalUptime; + float totalBestDifficulty; + int uptime; + int blocksFound; + int totalBlocksFound; + int duplicateHashes; + float voltage; + float current; + float power; +} influx_stats_t; + +typedef struct { + char *host; + int port; + char *token; + char *org; + char *bucket; + char *prefix; + char auth_header[128]; + char *big_buffer; + pthread_mutex_t lock; + influx_stats_t stats; +} influx_client_t; + +/** + * @brief Initialize the InfluxDB client + * + * @param client Pointer to the client structure to initialize + * @param host InfluxDB server hostname + * @param port InfluxDB server port + * @param token Authentication token + * @param bucket Bucket name + * @param org Organization name + * @param prefix Metric prefix + * @return true if initialization successful + * @return false if initialization failed + */ +bool influx_init(influx_client_t *client, const char *host, int port, + const char *token, const char *bucket, const char *org, + const char *prefix); + +/** + * @brief Write current stats to InfluxDB + * + * @param client Pointer to the client structure + * @return true if write successful + * @return false if write failed + */ +bool influx_write(influx_client_t *client); + +/** + * @brief Deinitialize and cleanup the InfluxDB client + * + * @param client Pointer to the client structure + */ +void influx_deinit(influx_client_t *client); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/components/influx/influx.c b/components/influx/influx.c new file mode 100644 index 000000000..dbc14ed53 --- /dev/null +++ b/components/influx/influx.c @@ -0,0 +1,173 @@ +#include +#include +#include +#include "esp_log.h" +#include "esp_http_client.h" +#include "esp_timer.h" +#include "cJSON.h" +#include "influx.h" + +static const char *TAG = "influx"; + +#define INFLUX_BUFFER_SIZE 2048 +#define INFLUX_API_VERSION "2.0" +#define INFLUX_WRITE_PATH "/api/v2/write" +#define INFLUX_BUCKETS_PATH "/api/v2/buckets" + +static esp_err_t http_event_handler(esp_http_client_event_t *evt) +{ + // Log the event ID for debugging + ESP_LOGD(TAG, "HTTP Event: %d", evt->event_id); + + // Handle specific events + if (evt->event_id == HTTP_EVENT_ERROR) { + ESP_LOGE(TAG, "HTTP_EVENT_ERROR"); + } else if (evt->event_id == HTTP_EVENT_ON_CONNECTED) { + ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED"); + } else if (evt->event_id == HTTP_EVENT_ON_HEADER) { + ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value); + } else if (evt->event_id == HTTP_EVENT_ON_DATA) { + if (!esp_http_client_is_chunked_response(evt->client)) { + ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len); + } + } else if (evt->event_id == HTTP_EVENT_ON_FINISH) { + ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH"); + } else if (evt->event_id == HTTP_EVENT_DISCONNECTED) { + ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED"); + } else if (evt->event_id == HTTP_EVENT_REDIRECT) { + ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT"); + } + + return ESP_OK; +} + +bool influx_init(influx_client_t *client, const char *host, int port, + const char *token, const char *bucket, const char *org, + const char *prefix) +{ + if (!client || !host || !token || !bucket || !org || !prefix) { + ESP_LOGE(TAG, "Invalid parameters"); + return false; + } + + memset(client, 0, sizeof(influx_client_t)); + + // Allocate and copy strings + client->host = strdup(host); + client->token = strdup(token); + client->bucket = strdup(bucket); + client->org = strdup(org); + client->prefix = strdup(prefix); + client->port = port; + + // Initialize mutex + if (pthread_mutex_init(&client->lock, NULL) != 0) { + ESP_LOGE(TAG, "Mutex initialization failed"); + influx_deinit(client); + return false; + } + + // Allocate buffer for HTTP requests + client->big_buffer = malloc(INFLUX_BUFFER_SIZE); + if (!client->big_buffer) { + ESP_LOGE(TAG, "Failed to allocate buffer"); + influx_deinit(client); + return false; + } + + // Create auth header + snprintf(client->auth_header, sizeof(client->auth_header), "Token %s", token); + + return true; +} + +void influx_deinit(influx_client_t *client) +{ + if (!client) return; + + pthread_mutex_destroy(&client->lock); + free(client->host); + free(client->token); + free(client->bucket); + free(client->org); + free(client->prefix); + free(client->big_buffer); + memset(client, 0, sizeof(influx_client_t)); +} + +bool influx_write(influx_client_t *client) +{ + if (!client) return false; + + pthread_mutex_lock(&client->lock); + + char url[128]; + snprintf(url, sizeof(url), "http://%s:%d%s?bucket=%s&org=%s&precision=s", + client->host, client->port, INFLUX_WRITE_PATH, + client->bucket, client->org); + + int len = snprintf(client->big_buffer, INFLUX_BUFFER_SIZE, + "%s asic_temp=%f,vr_temp=%f," + "hashing_speed=%f,invalid_shares=%d,valid_shares=%d,uptime=%d," + "best_difficulty=%f,total_best_difficulty=%f,pool_errors=%d," + "accepted=%d,not_accepted=%d,total_uptime=%d,blocks_found=%d," + "voltage=%f,current=%f,power=%f," + "total_blocks_found=%d,duplicate_hashes=%d", + client->prefix, + (double)client->stats.asicTemp, + (double)client->stats.vrTemp, + (double)client->stats.hashingSpeed, + client->stats.invalidShares, + client->stats.validShares, + client->stats.uptime, + (double)client->stats.bestSessionDifficulty, + (double)client->stats.totalBestDifficulty, + client->stats.poolErrors, + client->stats.accepted, + client->stats.notAccepted, + client->stats.totalUptime, + client->stats.blocksFound, + (double)client->stats.voltage, + (double)client->stats.current, + (double)client->stats.power, + client->stats.totalBlocksFound, + client->stats.duplicateHashes); + + if (len >= INFLUX_BUFFER_SIZE) { + ESP_LOGE(TAG, "Buffer too small for data"); + pthread_mutex_unlock(&client->lock); + return false; + } + + ESP_LOGI(TAG, "URL: %s", url); + ESP_LOGI(TAG, "POST: %s", client->big_buffer); + + esp_http_client_config_t config = { + .url = url, + .event_handler = http_event_handler, + .method = HTTP_METHOD_POST, + }; + + esp_http_client_handle_t http_client = esp_http_client_init(&config); + if (!http_client) { + ESP_LOGE(TAG, "Failed to initialize HTTP client"); + pthread_mutex_unlock(&client->lock); + return false; + } + + esp_http_client_set_header(http_client, "Authorization", client->auth_header); + esp_http_client_set_header(http_client, "Content-Type", "text/plain; charset=utf-8"); + esp_http_client_set_post_field(http_client, client->big_buffer, len); + + esp_err_t err = esp_http_client_perform(http_client); + esp_http_client_cleanup(http_client); + + pthread_mutex_unlock(&client->lock); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(err)); + return false; + } + + return true; +} \ No newline at end of file diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index f3c8f26b4..617da7155 100755 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -20,6 +20,7 @@ SRCS "./tasks/asic_task.c" "./tasks/asic_result_task.c" "./tasks/power_management_task.c" + "./tasks/influx_task.c" "./thermal/EMC2101.c" "./thermal/EMC2103.c" "./thermal/TMP1075.c" @@ -40,9 +41,13 @@ INCLUDE_DIRS "../components/connect/include" "../components/dns_server/include" "../components/stratum/include" + "../components/influx/include" "thermal" "power" +REQUIRES + "influx" + PRIV_REQUIRES "app_update" "driver" diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 10dd8a1ff..3859442a4 100755 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -115,3 +115,49 @@ menu "Stratum Configuration" A starting difficulty to use with the pool. endmenu + +menu "InfluxDB Configuration" + + config INFLUXDB_ENABLED + int "Enable InfluxDB Integration" + default 0 + help + Enable or disable InfluxDB metrics collection. + + config INFLUXDB_HOST + string "InfluxDB Host" + default "localhost" + help + The hostname or IP address of your InfluxDB server. + + config INFLUXDB_PORT + int "InfluxDB Port" + default 8086 + help + The port number of your InfluxDB server. + + config INFLUXDB_TOKEN + string "InfluxDB Token" + default "" + help + The authentication token for your InfluxDB server. + + config INFLUXDB_ORG + string "InfluxDB Organization" + default "bitaxe" + help + The organization name in InfluxDB. + + config INFLUXDB_BUCKET + string "InfluxDB Bucket" + default "bitaxe" + help + The bucket name in InfluxDB where metrics will be stored. + + config INFLUXDB_MEASUREMENT + string "InfluxDB Measurement" + default "mainnet_stats" + help + The measurement name for the metrics in InfluxDB. + +endmenu diff --git a/main/global_state.h b/main/global_state.h index 4cc597413..0736afe5f 100644 --- a/main/global_state.h +++ b/main/global_state.h @@ -139,6 +139,10 @@ typedef struct bool ASIC_initalized; bool psram_is_available; + + // InfluxDB client pointer (actual type is influx_client_t* from influx.h) + // Using void* here to avoid circular dependencies + void *influx_client; } GlobalState; #endif /* GLOBAL_STATE_H_ */ diff --git a/main/http_server/axe-os/src/app/app-routing.module.ts b/main/http_server/axe-os/src/app/app-routing.module.ts index 81dcee7e6..7ab21c648 100644 --- a/main/http_server/axe-os/src/app/app-routing.module.ts +++ b/main/http_server/axe-os/src/app/app-routing.module.ts @@ -8,6 +8,7 @@ import { NetworkComponent } from './components/network/network.component'; import { SwarmComponent } from './components/swarm/swarm.component'; import { DesignComponent } from './components/design/design.component'; import { PoolComponent } from './components/pool/pool.component'; +import { InfluxdbComponent } from './components/influxdb/influxdb.component'; import { AppLayoutComponent } from './layout/app.layout.component'; import { ApModeGuard } from './guards/ap-mode.guard'; @@ -54,6 +55,10 @@ const routes: Routes = [ { path: 'pool', component: PoolComponent + }, + { + path: 'influxdb', + component: InfluxdbComponent } ] }, diff --git a/main/http_server/axe-os/src/app/app.module.ts b/main/http_server/axe-os/src/app/app.module.ts index 2f8c73961..a75726ab2 100644 --- a/main/http_server/axe-os/src/app/app.module.ts +++ b/main/http_server/axe-os/src/app/app.module.ts @@ -21,6 +21,7 @@ import { SettingsComponent } from './components/settings/settings.component'; import { SwarmComponent } from './components/swarm/swarm.component'; import { ThemeConfigComponent } from './components/design/theme-config.component'; import { DesignComponent } from './components/design/design.component'; +import { InfluxdbComponent } from './components/influxdb/influxdb.component'; import { AppLayoutModule } from './layout/app.layout.module'; import { ANSIPipe } from './pipes/ansi.pipe'; import { DateAgoPipe } from './pipes/date-ago.pipe'; @@ -41,7 +42,8 @@ const components = [ NetworkComponent, SettingsComponent, LogsComponent, - PoolComponent + PoolComponent, + InfluxdbComponent ]; @NgModule({ diff --git a/main/http_server/axe-os/src/app/components/influxdb/influxdb.component.html b/main/http_server/axe-os/src/app/components/influxdb/influxdb.component.html new file mode 100644 index 000000000..bb0da7313 --- /dev/null +++ b/main/http_server/axe-os/src/app/components/influxdb/influxdb.component.html @@ -0,0 +1,101 @@ +
+

InfluxDB Configuration

+ +
+
+
+
+
+ + +
+ +
+ + + Please enter a valid IP address +
+ +
+ + + Please enter a valid port number +
+ +
+ + + Token is required when InfluxDB is enabled +
+ +
+ + +
+ +
+ + +
+ +
+ + You must restart this device after saving for changes to take effect. +
+ +
+ +
+
+
+
+
+
diff --git a/main/http_server/axe-os/src/app/components/influxdb/influxdb.component.scss b/main/http_server/axe-os/src/app/components/influxdb/influxdb.component.scss new file mode 100644 index 000000000..faf53f448 --- /dev/null +++ b/main/http_server/axe-os/src/app/components/influxdb/influxdb.component.scss @@ -0,0 +1,11 @@ +:host ::ng-deep { + .p-checkbox { + width: 20px; + height: 20px; + } + + .p-checkbox .p-checkbox-box { + width: 20px; + height: 20px; + } +} \ No newline at end of file diff --git a/main/http_server/axe-os/src/app/components/influxdb/influxdb.component.ts b/main/http_server/axe-os/src/app/components/influxdb/influxdb.component.ts new file mode 100644 index 000000000..a31fe89dd --- /dev/null +++ b/main/http_server/axe-os/src/app/components/influxdb/influxdb.component.ts @@ -0,0 +1,88 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { ToastrService } from 'ngx-toastr'; +import { shareReplay } from 'rxjs'; +import { LoadingService } from 'src/app/services/loading.service'; +import { SystemService } from 'src/app/services/system.service'; +import { ISystemInfo } from 'src/models/ISystemInfo'; + +@Component({ + selector: 'app-influxdb', + templateUrl: './influxdb.component.html', + styleUrls: ['./influxdb.component.scss'] +}) +export class InfluxdbComponent implements OnInit { + public form!: FormGroup; + public info$: any; + public savedChanges = false; + + constructor( + private fb: FormBuilder, + private systemService: SystemService, + private toastr: ToastrService, + private loadingService: LoadingService + ) { + this.info$ = this.systemService.getInfo().pipe(shareReplay({refCount: true, bufferSize: 1})); + } + + ngOnInit() { + this.info$.pipe(this.loadingService.lockUIUntilComplete()) + .subscribe((info: ISystemInfo) => { + // Initialize form with existing values or defaults + this.form = this.fb.group({ + influxEnabled: [info.influxEnabled ? true : false], // Convert to boolean explicitly + influxHost: [info.influxHost || '', [Validators.required, Validators.pattern(/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/)]], + influxPort: [info.influxPort || '8086', [Validators.required, Validators.pattern(/^\d+$/), Validators.min(1), Validators.max(65535)]], + influxToken: ['*****', [Validators.required]], + influxOrg: [info.influxOrg || ''], + influxBucket: [info.influxBucket || ''] + }); + + // Mark form as pristine after initialization + this.form.markAsPristine(); + }); + } + + public updateSystem() { + const formValue = this.form.getRawValue(); + + const update = { + influxEnabled: formValue.influxEnabled ? 1 : 0, + influxHost: formValue.influxHost, + influxPort: formValue.influxPort, + // Only send token if it's been changed from the default asterisks + ...(formValue.influxToken !== '*****' && { influxToken: formValue.influxToken }), + influxOrg: formValue.influxOrg, + influxBucket: formValue.influxBucket + }; + + this.systemService.updateSystem(undefined, update) + .pipe(this.loadingService.lockUIUntilComplete()) + .subscribe({ + next: () => { + this.savedChanges = true; + this.form.markAsPristine(); + // Reset token field to asterisks after successful save + this.form.patchValue({ influxToken: '*****' }); + this.toastr.success('Success!', 'InfluxDB settings saved.'); + }, + error: (err: HttpErrorResponse) => { + this.toastr.error('Error.', `Could not save InfluxDB settings. ${err.message}`); + } + }); + } + + public restart() { + this.systemService.restart() + .pipe(this.loadingService.lockUIUntilComplete()) + .subscribe({ + next: () => { + this.toastr.success('Success!', 'System is restarting...'); + }, + error: (err: HttpErrorResponse) => { + this.toastr.error('Error.', `Could not restart system. ${err.message}`); + } + }); + } +} \ No newline at end of file diff --git a/main/http_server/axe-os/src/app/layout/app.menu.component.ts b/main/http_server/axe-os/src/app/layout/app.menu.component.ts index f4a5bbcb5..af6d46f3b 100644 --- a/main/http_server/axe-os/src/app/layout/app.menu.component.ts +++ b/main/http_server/axe-os/src/app/layout/app.menu.component.ts @@ -26,6 +26,7 @@ export class AppMenuComponent implements OnInit { { label: 'Swarm', icon: 'pi pi-fw pi-share-alt', routerLink: ['swarm'] }, { label: 'Network', icon: 'pi pi-fw pi-wifi', routerLink: ['network'] }, { label: 'Pool Settings', icon: 'pi pi-fw pi-server', routerLink: ['pool'] }, + { label: 'InfluxDB', icon: 'pi pi-fw pi-database', routerLink: ['influxdb'] }, { label: 'Customization', icon: 'pi pi-fw pi-palette', routerLink: ['design'] }, { label: 'Settings', icon: 'pi pi-fw pi-cog', routerLink: ['settings'] }, { label: 'Logs', icon: 'pi pi-fw pi-list', routerLink: ['logs'] }, diff --git a/main/http_server/axe-os/src/models/ISystemInfo.ts b/main/http_server/axe-os/src/models/ISystemInfo.ts index 7bdc0f096..8911d981e 100644 --- a/main/http_server/axe-os/src/models/ISystemInfo.ts +++ b/main/http_server/axe-os/src/models/ISystemInfo.ts @@ -56,4 +56,9 @@ export interface ISystemInfo { overheat_mode: number, power_fault?: string overclockEnabled?: number + influxEnabled?: number + influxHost?: string + influxPort?: string + influxOrg?: string + influxBucket?: string } diff --git a/main/http_server/http_server.c b/main/http_server/http_server.c index 9085ae579..57c7e4d27 100644 --- a/main/http_server/http_server.c +++ b/main/http_server/http_server.c @@ -37,6 +37,8 @@ #include "TPS546.h" #include "theme_api.h" // Add theme API include #include "http_server.h" +#include "influx_task.h" +#include "influx.h" static const char * TAG = "http_server"; static const char * CORS_TAG = "CORS"; @@ -45,10 +47,10 @@ static const char * CORS_TAG = "CORS"; static esp_err_t GET_wifi_scan(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); - + // Give some time for the connected flag to take effect vTaskDelay(100 / portTICK_PERIOD_MS); - + wifi_ap_record_simple_t ap_records[20]; uint16_t ap_count = 0; @@ -482,6 +484,29 @@ static esp_err_t PATCH_update_settings(httpd_req_t * req) nvs_config_set_u16(NVS_CONFIG_OVERCLOCK_ENABLED, item->valueint); } + // Handle InfluxDB configuration + if ((item = cJSON_GetObjectItem(root, "influxEnabled")) != NULL) { + nvs_config_set_u16(NVS_CONFIG_INFLUX_ENABLED, item->valueint); + } + if (cJSON_IsString(item = cJSON_GetObjectItem(root, "influxHost"))) { + nvs_config_set_string(NVS_CONFIG_INFLUX_HOST, item->valuestring); + } + if ((item = cJSON_GetObjectItem(root, "influxPort")) != NULL) { + nvs_config_set_u16(NVS_CONFIG_INFLUX_PORT, item->valueint); + } + if (cJSON_IsString(item = cJSON_GetObjectItem(root, "influxToken"))) { + nvs_config_set_string(NVS_CONFIG_INFLUX_TOKEN, item->valuestring); + } + if (cJSON_IsString(item = cJSON_GetObjectItem(root, "influxBucket"))) { + nvs_config_set_string(NVS_CONFIG_INFLUX_BUCKET, item->valuestring); + } + if (cJSON_IsString(item = cJSON_GetObjectItem(root, "influxOrg"))) { + nvs_config_set_string(NVS_CONFIG_INFLUX_ORG, item->valuestring); + } + if (cJSON_IsString(item = cJSON_GetObjectItem(root, "influxMeasurement"))) { + nvs_config_set_string(NVS_CONFIG_INFLUX_MEASUREMENT, item->valuestring); + } + cJSON_Delete(root); httpd_resp_send_chunk(req, NULL, 0); return ESP_OK; @@ -579,7 +604,7 @@ static esp_err_t GET_system_info(httpd_req_t * req) cJSON *error_array = cJSON_CreateArray(); cJSON_AddItemToObject(root, "sharesRejectedReasons", error_array); - + for (int i = 0; i < GLOBAL_STATE->SYSTEM_MODULE.rejected_reason_stats_count; i++) { cJSON *error_obj = cJSON_CreateObject(); cJSON_AddStringToObject(error_obj, "message", GLOBAL_STATE->SYSTEM_MODULE.rejected_reason_stats[i].message); @@ -614,7 +639,27 @@ static esp_err_t GET_system_info(httpd_req_t * req) cJSON_AddNumberToObject(root, "fanspeed", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.fan_perc); cJSON_AddNumberToObject(root, "temptarget", nvs_config_get_u16(NVS_CONFIG_TEMP_TARGET, 60)); cJSON_AddNumberToObject(root, "fanrpm", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.fan_rpm); - + + // Add InfluxDB configuration + char *influx_host = nvs_config_get_string(NVS_CONFIG_INFLUX_HOST, CONFIG_INFLUXDB_HOST); + char *influx_token = nvs_config_get_string(NVS_CONFIG_INFLUX_TOKEN, CONFIG_INFLUXDB_TOKEN); + char *influx_bucket = nvs_config_get_string(NVS_CONFIG_INFLUX_BUCKET, CONFIG_INFLUXDB_BUCKET); + char *influx_org = nvs_config_get_string(NVS_CONFIG_INFLUX_ORG, CONFIG_INFLUXDB_ORG); + char *influx_measurement = nvs_config_get_string(NVS_CONFIG_INFLUX_MEASUREMENT, CONFIG_INFLUXDB_MEASUREMENT); + + cJSON_AddBoolToObject(root, "influxEnabled", nvs_config_get_u16(NVS_CONFIG_INFLUX_ENABLED, CONFIG_INFLUXDB_ENABLED)); + cJSON_AddStringToObject(root, "influxHost", influx_host); + cJSON_AddNumberToObject(root, "influxPort", nvs_config_get_u16(NVS_CONFIG_INFLUX_PORT, CONFIG_INFLUXDB_PORT)); + cJSON_AddStringToObject(root, "influxBucket", influx_bucket); + cJSON_AddStringToObject(root, "influxOrg", influx_org); + cJSON_AddStringToObject(root, "influxMeasurement", influx_measurement); + + free(influx_host); + free(influx_token); + free(influx_bucket); + free(influx_org); + free(influx_measurement); + if (GLOBAL_STATE->SYSTEM_MODULE.power_fault > 0) { cJSON_AddStringToObject(root, "power_fault", VCORE_get_fault_string(GLOBAL_STATE)); } @@ -720,7 +765,7 @@ esp_err_t POST_OTA_update(httpd_req_t * req) httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Not allowed in AP mode"); return ESP_OK; } - + GLOBAL_STATE->SYSTEM_MODULE.is_firmware_update = true; snprintf(GLOBAL_STATE->SYSTEM_MODULE.firmware_update_filename, 20, "esp-miner.bin"); snprintf(GLOBAL_STATE->SYSTEM_MODULE.firmware_update_status, 20, "Starting..."); diff --git a/main/main.c b/main/main.c index 2a747920a..383106202 100644 --- a/main/main.c +++ b/main/main.c @@ -4,7 +4,7 @@ #include "nvs_flash.h" #include "main.h" - +#include "influx_task.h" #include "asic_result_task.h" #include "asic_task.h" #include "create_jobs_task.h" @@ -21,11 +21,12 @@ #include "driver/gpio.h" static GlobalState GLOBAL_STATE = { - .extranonce_str = NULL, - .extranonce_2_len = 0, - .abandon_work = 0, + .extranonce_str = NULL, + .extranonce_2_len = 0, + .abandon_work = 0, .version_mask = 0, - .ASIC_initalized = false + .ASIC_initalized = false, + .influx_client = NULL }; static const char * TAG = "bitaxe"; @@ -128,4 +129,26 @@ void app_main(void) xTaskCreate(create_jobs_task, "stratum miner", 8192, (void *) &GLOBAL_STATE, 10, NULL); xTaskCreate(ASIC_task, "asic", 8192, (void *) &GLOBAL_STATE, 10, NULL); xTaskCreate(ASIC_result_task, "asic result", 8192, (void *) &GLOBAL_STATE, 15, NULL); + + // Initialize InfluxDB client if enabled and schedule the stats reporting task + bool influx_enabled = nvs_config_get_u16(NVS_CONFIG_INFLUX_ENABLED, CONFIG_INFLUXDB_ENABLED); + + if (influx_enabled) { + bool influx_success = influx_init_and_start( + &GLOBAL_STATE, + nvs_config_get_string(NVS_CONFIG_INFLUX_HOST, CONFIG_INFLUXDB_HOST), + nvs_config_get_u16(NVS_CONFIG_INFLUX_PORT, CONFIG_INFLUXDB_PORT), + nvs_config_get_string(NVS_CONFIG_INFLUX_TOKEN, CONFIG_INFLUXDB_TOKEN), + nvs_config_get_string(NVS_CONFIG_INFLUX_BUCKET, CONFIG_INFLUXDB_BUCKET), + nvs_config_get_string(NVS_CONFIG_INFLUX_ORG, CONFIG_INFLUXDB_ORG), + nvs_config_get_string(NVS_CONFIG_INFLUX_MEASUREMENT, CONFIG_INFLUXDB_MEASUREMENT) + ); + + if (!influx_success) { + ESP_LOGE(TAG, "Failed to initialize InfluxDB client"); + } else { + // Create the InfluxDB stats reporting task only if initialization was successful + xTaskCreate(influx_task, "influx task", 4096, (void *) &GLOBAL_STATE, 5, NULL); + } + } } diff --git a/main/nvs_config.h b/main/nvs_config.h index 0b2b05687..ae2edc486 100644 --- a/main/nvs_config.h +++ b/main/nvs_config.h @@ -38,6 +38,15 @@ #define NVS_CONFIG_THEME_NAME "themename" #define NVS_CONFIG_THEME_COLORS "themecolors" +// InfluxDB configuration +#define NVS_CONFIG_INFLUX_ENABLED "influxenabled" +#define NVS_CONFIG_INFLUX_HOST "influxhost" +#define NVS_CONFIG_INFLUX_PORT "influxport" +#define NVS_CONFIG_INFLUX_TOKEN "influxtoken" +#define NVS_CONFIG_INFLUX_BUCKET "influxbucket" +#define NVS_CONFIG_INFLUX_ORG "influxorg" +#define NVS_CONFIG_INFLUX_MEASUREMENT "influxmeasure" + char * nvs_config_get_string(const char * key, const char * default_value); void nvs_config_set_string(const char * key, const char * default_value); uint16_t nvs_config_get_u16(const char * key, const uint16_t default_value); diff --git a/main/tasks/influx_task.c b/main/tasks/influx_task.c new file mode 100644 index 000000000..c265ace4a --- /dev/null +++ b/main/tasks/influx_task.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include "influx.h" +#include "global_state.h" +#include "tasks/influx_task.h" +#include "thermal/thermal.h" + +static const char *TAG = "influx_task"; + +void influx_task(void *pvParameters) { + GlobalState *state = (GlobalState *)pvParameters; + influx_client_t *client = (influx_client_t *)state->influx_client; + TickType_t last_wake_time = xTaskGetTickCount(); + const TickType_t frequency = pdMS_TO_TICKS(30000); // 30 seconds + + ESP_LOGI(TAG, "InfluxDB stats task started"); + + while (1) { + if (client != NULL) { + // Update stats in the client structure + client->stats.hashingSpeed = state->SYSTEM_MODULE.current_hashrate; + + // System module stats + client->stats.invalidShares = state->SYSTEM_MODULE.shares_rejected; + client->stats.validShares = state->SYSTEM_MODULE.shares_accepted; + client->stats.difficulty = state->stratum_difficulty; + client->stats.bestSessionDifficulty = state->SYSTEM_MODULE.best_session_nonce_diff; + client->stats.accepted = state->SYSTEM_MODULE.shares_accepted; + client->stats.notAccepted = state->SYSTEM_MODULE.shares_rejected; + client->stats.totalUptime = (esp_timer_get_time() - state->SYSTEM_MODULE.start_time) / 1000000; + client->stats.totalBestDifficulty = state->SYSTEM_MODULE.best_nonce_diff; + client->stats.uptime = state->SYSTEM_MODULE.duration_start; + client->stats.blocksFound = state->SYSTEM_MODULE.FOUND_BLOCK ? 1 : 0; + + // Power management stats + client->stats.asicTemp = state->POWER_MANAGEMENT_MODULE.chip_temp_avg; + client->stats.vrTemp = state->POWER_MANAGEMENT_MODULE.vr_temp; + client->stats.voltage = state->POWER_MANAGEMENT_MODULE.voltage; + client->stats.current = state->POWER_MANAGEMENT_MODULE.current; + client->stats.power = state->POWER_MANAGEMENT_MODULE.power; + + if (!influx_write(client)) { + ESP_LOGW(TAG, "Failed to write stats to InfluxDB"); + } + } + + // Wait for the next cycle using vTaskDelayUntil for precise timing + vTaskDelayUntil(&last_wake_time, frequency); + } +} + +bool influx_init_and_start(GlobalState *state, const char *host, int port, const char *token, const char *bucket, const char *org, const char *prefix) { + if (state == NULL) { + ESP_LOGE(TAG, "Global state is NULL"); + return false; + } + + ESP_LOGI(TAG, "Initializing InfluxDB client"); + + // Initialize InfluxDB client + influx_client_t *influx_client = malloc(sizeof(influx_client_t)); + if (influx_client == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for InfluxDB client"); + return false; + } + + bool influx_success = influx_init(influx_client, host, port, token, bucket, org, prefix); + if (!influx_success) { + ESP_LOGE(TAG, "Failed to initialize InfluxDB client"); + free(influx_client); + state->influx_client = NULL; + return false; + } + + ESP_LOGI(TAG, "InfluxDB client initialized successfully"); + state->influx_client = influx_client; + return true; +} \ No newline at end of file diff --git a/main/tasks/influx_task.h b/main/tasks/influx_task.h new file mode 100644 index 000000000..e8832ccce --- /dev/null +++ b/main/tasks/influx_task.h @@ -0,0 +1,44 @@ +#ifndef INFLUX_TASK_H_ +#define INFLUX_TASK_H_ + +#include "global_state.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Task that periodically sends mining stats to InfluxDB + * + * @param pvParameters Pointer to GlobalState structure + */ +void influx_task(void *pvParameters); + +/** + * @brief Initialize InfluxDB client + * + * This function will: + * 1. Initialize the InfluxDB client with the provided configuration + * 2. Test the connection to the InfluxDB server + * + * Note: Changes to InfluxDB configuration require a device restart to take effect. + * + * @param state Pointer to GlobalState structure + * @param host InfluxDB server hostname + * @param port InfluxDB server port + * @param token Authentication token + * @param bucket Bucket name + * @param org Organization name + * @param prefix Metric prefix + * @return true if initialization successful + * @return false if initialization failed + */ +bool influx_init_and_start(GlobalState *state, const char *host, int port, + const char *token, const char *bucket, const char *org, + const char *prefix); + +#ifdef __cplusplus +} +#endif + +#endif /* INFLUX_TASK_H_ */ \ No newline at end of file diff --git a/main/tasks/power_management_task.c b/main/tasks/power_management_task.c index 9a669fd92..e8395bb63 100644 --- a/main/tasks/power_management_task.c +++ b/main/tasks/power_management_task.c @@ -64,7 +64,7 @@ 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; - + while (1) { // Refresh PID setpoint from NVS in case it was changed via API @@ -72,7 +72,7 @@ void POWER_MANAGEMENT_task(void * pvParameters) power_management->voltage = Power_get_input_voltage(GLOBAL_STATE); power_management->power = Power_get_power(GLOBAL_STATE); - + power_management->current = Power_get_current(GLOBAL_STATE); power_management->fan_rpm = Thermal_get_fan_speed(GLOBAL_STATE->device_model); power_management->chip_temp_avg = Thermal_get_chip_temp(GLOBAL_STATE);