diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 67f5a5086..a9ac9f39d 100755 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -34,6 +34,7 @@ SRCS "./thermal/TMP1075.c" "./thermal/thermal.c" "./thermal/PID.c" + "./thermal/auto_tune.c" "./power/TPS546.c" "./power/DS4432U.c" "./power/INA260.c" 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 8989ffa08..084393bf9 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 @@ -12,6 +12,7 @@ import { DesignComponent } from './components/design/design.component'; import { PoolComponent } from './components/pool/pool.component'; import { AppLayoutComponent } from './layout/app.layout.component'; import { ApModeGuard } from './guards/ap-mode.guard'; +import { AutotuneComponent } from './components/autotune/autotune.component'; const TITLE_PREFIX = 'AxeOS'; @@ -76,6 +77,11 @@ const routes: Routes = [ path: 'pool', component: PoolComponent, title: `${TITLE_PREFIX} Pool`, + }, + { + path: 'autotune', + component: AutotuneComponent, + title: `${TITLE_PREFIX} Autotune`, } ] }, @@ -87,3 +93,4 @@ const routes: Routes = [ exports: [RouterModule] }) export class AppRoutingModule { } + 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 c1e11f9b1..7384f1781 100644 --- a/main/http_server/axe-os/src/app/app.module.ts +++ b/main/http_server/axe-os/src/app/app.module.ts @@ -25,6 +25,7 @@ import { SystemComponent } from './components/system/system.component'; import { UpdateComponent } from './components/update/update.component'; import { NetworkComponent } from './components/network/network.component'; import { SettingsComponent } from './components/settings/settings.component'; +import { AutotuneComponent } from './components/autotune/autotune.component'; import { SwarmComponent } from './components/swarm/swarm.component'; import { ThemeConfigComponent } from './components/design/theme-config.component'; import { DesignComponent } from './components/design/design.component'; @@ -43,6 +44,7 @@ import { DialogService, DialogListComponent } from './services/dialog.service'; const components = [ AppComponent, EditComponent, + AutotuneComponent, NetworkEditComponent, HomeComponent, ModalComponent, diff --git a/main/http_server/axe-os/src/app/components/autotune/autotune.component.html b/main/http_server/axe-os/src/app/components/autotune/autotune.component.html new file mode 100644 index 000000000..c98c81882 --- /dev/null +++ b/main/http_server/axe-os/src/app/components/autotune/autotune.component.html @@ -0,0 +1,50 @@ +
+
+

Autotune Settings

+ + + +
+ +
+
+ + +
+ +
+ + + +
+ +
+ + + +
+
+
+ +
+ +
+ +
+ +
+
\ No newline at end of file diff --git a/main/http_server/axe-os/src/app/components/autotune/autotune.component.spec.ts b/main/http_server/axe-os/src/app/components/autotune/autotune.component.spec.ts new file mode 100644 index 000000000..614d080ae --- /dev/null +++ b/main/http_server/axe-os/src/app/components/autotune/autotune.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AutotuneComponent } from './autotune.component'; + +describe('SettingsComponent', () => { + let component: AutotuneComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [AutotuneComponent] + }); + fixture = TestBed.createComponent(AutotuneComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/main/http_server/axe-os/src/app/components/autotune/autotune.component.ts b/main/http_server/axe-os/src/app/components/autotune/autotune.component.ts new file mode 100644 index 000000000..29032c9e8 --- /dev/null +++ b/main/http_server/axe-os/src/app/components/autotune/autotune.component.ts @@ -0,0 +1,199 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { HttpErrorResponse } from '@angular/common/http'; + +import { LoadingService } from 'src/app/services/loading.service'; + +import { ToastrService } from 'ngx-toastr'; +import { forkJoin } from 'rxjs'; +import { SystemInfo, SystemService } from 'src/app/generated'; +import { AutotuneService } from 'src/app/generated/api/autotune.service'; + +interface SliderConfig { + formControlName: string; + label: string; + min: number; + max: number; + step: number; + unit: string; + tooltip: string; +} + +@Component({ + selector: 'autotune', + templateUrl: './autotune.component.html', +}) +export class AutotuneComponent implements OnInit { + public autotuneForm!: FormGroup; + + public sliderConfigs: SliderConfig[] = [ + { + formControlName: 'power_limit', + label: 'Power Limit', + min: 10, + max: 40, + step: 1, + unit: 'W', + tooltip: 'The maximum power limit that the miner is allowed to use, based on your power supply output. Default:25W' + }, + { + formControlName: 'fan_limit', + label: 'Fan Limit', + min: 0, + max: 100, + step: 1, + unit: '%', + tooltip: 'Sets the maximum fan speed limit in percent. This ensures that fans do not exceed this speed, helping to control noise levels and reduce wear on the fans. Default:75%' + }, + { + formControlName: 'osh_pow_limit', + label: 'Overshoot Power Limit', + min: 0, + max: 2.2, + step: 0.1, + unit: 'W', + tooltip: 'Maximum allowed power overshoot in watts. This provides a buffer for temporary spikes above the power limit, allowing for brief surges without triggering safety mechanisms. Default:0.2W' + }, + { + formControlName: 'osh_fan_limit', + label: 'Overshoot Fanspeed', + min: 0, + max: 25, + step: 1, + unit: '%', + tooltip: 'Maximum allowed fan speed overshoot in percent. This provides a buffer for temporary spikes above the fan limit, allowing for brief increases without triggering safety mechanisms. Default:5%' + }, + { + formControlName: 'max_volt_asic', + label: 'Max Voltage ASIC', + min: 1000, + max: 1400, + step: 1, + unit: 'mV', + tooltip: 'Maximum voltage for the ASIC in millivolts. This prevents over-voltage conditions that could damage hardware, ensuring safe operation within specified limits. Default:1400mV' + }, + { + formControlName: 'max_freq_asic', + label: 'Max Frequency ASIC', + min: 400, + max: 1000, + step: 1, + unit: 'MHz', + tooltip: 'Maximum frequency for the ASIC in megahertz. This prevents overclocking beyond safe limits, ensuring stable and reliable performance. Default:1000MHz' + }, + { + formControlName: 'max_temp_asic', + label: 'Max Temperature ASIC', + min: 20, + max: 70, + step: 1, + unit: '°C', + tooltip: 'Maximum temperature allowed for the ASIC in degrees Celsius. This ensures safe operation and prevents overheating that could damage hardware or affect performance. Default:65°C' + }, + { + formControlName: 'max_temp_vr', + label: 'Max Temperature VR', + min: 20, + max: 90, + step: 1, + unit: '°C', + tooltip: 'Maximum temperature for the VoltageRegulator in degrees Celsius. This ensures thermal safety, preventing hardware damage due to overheating. Default:85°C' + }, + ]; + + constructor( + private fb: FormBuilder, + private loadingService: LoadingService, + private systemService: SystemService, + private toastr: ToastrService, + private autotuneService: AutotuneService + ) { } + + ngOnInit(): void { + this.autotuneForm = this.fb.group({ + power_limit: [null, [Validators.required, Validators.min(1)]], + fan_limit: [null, [Validators.required, Validators.min(0)]], + osh_pow_limit: [null], + osh_fan_limit: [null], + max_volt_asic: [null, [Validators.required, Validators.min(1)]], + max_freq_asic: [null, [Validators.required, Validators.min(1)]], + max_temp_asic: [null, [Validators.required, Validators.min(1)]], + max_temp_vr: [null], + auto_tune: [false] + }); + + this.loadAutotuneSettings(); + } + + private loadAutotuneSettings(): void { + forkJoin({ + info: this.systemService.getSystemInfo(), + asic: this.systemService.getAsicSettings(), + autotune: this.autotuneService.getAutotuneSettings() + }).pipe( + this.loadingService.lockUIUntilComplete() + ).subscribe({ + next: ({ info, asic, autotune }) => { + this.updateSliderMinForPid(info); + + const maxVoltage = asic.defaultVoltage * 1.25; + const maxFrequency = asic.defaultFrequency * 2; + + this.sliderConfigs = this.sliderConfigs.map(config => { + if (config.formControlName === 'max_volt_asic') { + return { ...config, max: maxVoltage }; + } + if (config.formControlName === 'max_freq_asic') { + return { ...config, max: maxFrequency }; + } + return config; + }); + + this.autotuneForm.patchValue({ + power_limit: autotune.power_limit, + fan_limit: autotune.fan_limit, + osh_pow_limit: autotune.osh_pow_limit, + osh_fan_limit: autotune.osh_fan_limit, + max_volt_asic: autotune.max_volt_asic, + max_freq_asic: autotune.max_freq_asic, + max_temp_asic: autotune.max_temp_asic, + max_temp_vr: autotune.max_temp_vr, + auto_tune: autotune.auto_tune ?? false + }); + }, + error: () => { + this.toastr.error('Failed to load autotune settings'); + } + }); + } + + public updateAutotune(): void { + this.autotuneService.updateAutotuneSettings(this.autotuneForm.value).subscribe({ + next: () => this.toastr.success('Autotune settings saved'), + error: (err: HttpErrorResponse) => this.toastr.error(`Could not save autotune settings. ${err.message}`) + }); + } + + private updateSliderMinForPid(info: SystemInfo): void { + // Check if PID is active (autofanspeed = 1) + const isPidActive = info.autofanspeed === 1; + + // If PID is active, set the minimum value to (temptarget + 1) + // Otherwise keep the default minimum of 20 + const minTemp = isPidActive ? (info.temptarget + 1) : 20; + const minFanspeed = isPidActive ? (info.minFanSpeed + 1) : 20; + const maxPower = info.maxPower; + // Update the slider configuration in our component + this.sliderConfigs.forEach(config => { + if (config.formControlName === 'max_temp_asic') { + config.min = minTemp; + } + if (config.formControlName === 'fan_limit') { + config.min = minFanspeed; + } + if (config.formControlName === 'power_limit') { + config.max = maxPower; + } + }); + } +} 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..ae69831d6 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 @@ -162,7 +162,7 @@ export class EditComponent implements OnInit, OnDestroy, OnChanges { Validators.max(this.displayTimeoutMaxValue) ]], coreVoltage: [info.coreVoltage, [Validators.required]], - frequency: [info.frequency, [Validators.required]], + frequency: [info.frequencySet, [Validators.required]], autofanspeed: [info.autofanspeed == 1, [Validators.required]], minfanspeed: [info.minFanSpeed, [Validators.required]], manualFanSpeed: [info.manualFanSpeed, [Validators.required]], diff --git a/main/http_server/axe-os/src/app/components/home/home.component.ts b/main/http_server/axe-os/src/app/components/home/home.component.ts index 5a9250be7..d29f5047e 100644 --- a/main/http_server/axe-os/src/app/components/home/home.component.ts +++ b/main/http_server/axe-os/src/app/components/home/home.component.ts @@ -332,6 +332,7 @@ export class HomeComponent implements OnInit, OnDestroy { stats.statistics.forEach((element: number[]) => { switch (chartLabelValue(chartY1DataLabel)) { case eChartLabel.asicVoltage: + case eChartLabel.asicVoltageSet: case eChartLabel.voltage: case eChartLabel.current: element[idxChartY1Data] = element[idxChartY1Data] / 1000; @@ -341,6 +342,7 @@ export class HomeComponent implements OnInit, OnDestroy { } switch (chartLabelValue(chartY2DataLabel)) { case eChartLabel.asicVoltage: + case eChartLabel.asicVoltageSet: case eChartLabel.voltage: case eChartLabel.current: element[idxChartY2Data] = element[idxChartY2Data] / 1000; @@ -412,6 +414,7 @@ export class HomeComponent implements OnInit, OnDestroy { info.current = info.current / 1000; info.coreVoltageActual = info.coreVoltageActual / 1000; info.coreVoltage = info.coreVoltage / 1000; + info.coreVoltageSet = info.coreVoltageSet / 1000; return info; }), tap(info => { @@ -471,9 +474,11 @@ export class HomeComponent implements OnInit, OnDestroy { info.current = parseFloat(info.current.toFixed(1)); info.coreVoltageActual = parseFloat(info.coreVoltageActual.toFixed(2)); info.coreVoltage = parseFloat(info.coreVoltage.toFixed(2)); + info.coreVoltageSet = parseFloat(info.coreVoltageSet.toFixed(2)); info.temp = parseFloat(info.temp.toFixed(1)); info.temp2 = parseFloat(info.temp2.toFixed(1)); info.responseTime = parseFloat(info.responseTime.toFixed(1)); + info.frequency = parseFloat(info.frequency.toFixed(2)); return info; }), @@ -741,6 +746,7 @@ export class HomeComponent implements OnInit, OnDestroy { case eChartLabel.asicTemp: return this.maxTemp; case eChartLabel.vrTemp: return this.maxTemp + 25; case eChartLabel.asicVoltage: return info.coreVoltage; + case eChartLabel.asicVoltageSet: return info.coreVoltageSet; case eChartLabel.voltage: return info.nominalVoltage + .5; case eChartLabel.power: return this.maxPower; case eChartLabel.current: return this.maxPower / info.coreVoltage; @@ -748,6 +754,7 @@ export class HomeComponent implements OnInit, OnDestroy { case eChartLabel.fanRpm: return 7000; case eChartLabel.fan2Rpm: return 7000; case eChartLabel.responseTime: return 50; + case eChartLabel.frequency: return 0; default: return 0; } } @@ -762,6 +769,7 @@ export class HomeComponent implements OnInit, OnDestroy { case eChartLabel.asicTemp: return info.temp; case eChartLabel.vrTemp: return info.vrTemp; case eChartLabel.asicVoltage: return info.coreVoltageActual; + case eChartLabel.asicVoltageSet: return info.coreVoltageSet; case eChartLabel.voltage: return info.voltage; case eChartLabel.power: return info.power; case eChartLabel.current: return info.current; @@ -771,6 +779,7 @@ export class HomeComponent implements OnInit, OnDestroy { case eChartLabel.wifiRssi: return info.wifiRSSI; case eChartLabel.freeHeap: return info.freeHeap; case eChartLabel.responseTime: return info.responseTime; + case eChartLabel.frequency: return info.frequency; default: return 0.0; } } @@ -781,7 +790,8 @@ export class HomeComponent implements OnInit, OnDestroy { case eChartLabel.asicTemp: case eChartLabel.vrTemp: return {suffix: ' °C', precision: 1}; case eChartLabel.asicVoltage: - case eChartLabel.voltage: return {suffix: ' V', precision: 1}; + case eChartLabel.voltage: + case eChartLabel.asicVoltageSet: return {suffix: ' V', precision: 3}; case eChartLabel.power: return {suffix: ' W', precision: 1}; case eChartLabel.current: return {suffix: ' A', precision: 1}; case eChartLabel.fanSpeed: return {suffix: ' %', precision: 1}; @@ -789,6 +799,7 @@ export class HomeComponent implements OnInit, OnDestroy { case eChartLabel.fan2Rpm: return {suffix: ' rpm', precision: 0}; case eChartLabel.wifiRssi: return {suffix: ' dBm', precision: 0}; case eChartLabel.responseTime: return {suffix: ' ms', precision: 1}; + case eChartLabel.frequency: return {suffix: ' MHz', precision: 1}; default: return {suffix: '', precision: 0}; } } 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 1bab04a8a..b9ee476e3 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 @@ -34,6 +34,7 @@ export class AppMenuComponent implements OnInit { { label: 'Network', icon: 'pi pi-fw pi-wifi', routerLink: ['network'] }, { label: 'Theme', icon: 'pi pi-fw pi-palette', routerLink: ['design'] }, { label: 'Settings', icon: 'pi pi-fw pi-cog', routerLink: ['settings'] }, + { label: 'Autotune', icon: 'pi pi-fw pi-sliders-h', routerLink: ['autotune'] }, { label: 'Update', icon: 'pi pi-fw pi-sync', routerLink: ['update'] }, { separator: true }, @@ -43,3 +44,4 @@ export class AppMenuComponent implements OnInit { ]; } } + 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..379a6221b 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 @@ -4,9 +4,11 @@ import { delay, Observable, of, timeout } from 'rxjs'; import { eChartLabel } from 'src/models/enum/eChartLabel'; import { chartLabelKey } from 'src/models/enum/eChartLabel'; import { chartLabelValue } from 'src/models/enum/eChartLabel'; +import { IAutotuneSettings } from 'src/models/IAutotuneSettings'; import { SystemInfo as ISystemInfo, SystemStatistics as ISystemStatistics, + SystemASIC as ISystemASIC, SystemASICASICModelEnum, SystemService as GeneratedSystemService, @@ -59,6 +61,7 @@ export class SystemApiService { freeHeapSpiram: 200504, coreVoltage: 1200, coreVoltageActual: 1200, + coreVoltageSet: 1200, hostname: "Bitaxe", macAddr: "2C:54:91:88:C9:E3", ssid: "default", @@ -96,6 +99,7 @@ export class SystemApiService { isUsingFallbackStratum: 0, poolConnectionInfo: "IPv4 (TLS)", frequency: 485, + frequencySet: 450, version: "v2.12.0", axeOSVersion: "v2.12.0", idfVersion: "v5.5.1", @@ -151,43 +155,43 @@ export class SystemApiService { return this.generatedSystemService.getSystemStatistics(columnList).pipe(timeout(API_TIMEOUT)); } - const hashrateData = [0,413.4903744405481,410.7764830376959,440.100549473198,430.5816012914026,452.5464981767163,414.9564271189586,498.7294609150379,411.1671601439723,491.327834852684]; - const powerData = [14.45068359375,14.86083984375,15.03173828125,15.1171875,15.1171875,15.1513671875,15.185546875,15.27099609375,15.30517578125,15.33935546875]; - const asicTempData = [-1,58.5,59.625,60.125,60.75,61.5,61.875,62.125,62.5,63]; - const vrTempData = [45,45,45,44,45,44,44,45,45,45]; - const asicVoltageData = [1221,1223,1219,1223,1217,1222,1221,1219,1221,1221]; - const voltageData = [5196.875,5204.6875,5196.875,5196.875,5196.875,5196.875,5196.875,5196.875,5196.875,5204.6875]; - const currentData = [2284.375,2284.375,2253.125,2284.375,2253.125,2231.25,2284.375,2253.125,2253.125,2284.375]; - const fanSpeedData = [48,52,50,52,53,54,50,50,48,48]; - const fanRpmData = [4032,3545,3904,3691,3564,3554,3691,3573,3701,4044]; - const fan2RpmData = [3545,3904,3691,3564,3554,3691,3573,3701,4044, 4032]; - const wifiRssiData = [-35,-34,-33,-34,-34,-34,-33,-35,-33,-34]; - const freeHeapData = [214504,212504,213504,210504,207504,209504,203504,202504,201504,200504]; - const responseTimeData = [15.1,14.5,14.3,15.1,13.1,16.1,28.6,18.4,17.7,17.6,18.0,15.5]; - const timestampData = [13131,18126,23125,28125,33125,38125,43125,48125,53125,58125]; + const hashrateData = [0, 413.4903744405481, 410.7764830376959, 440.100549473198, 430.5816012914026, 452.5464981767163, 414.9564271189586, 498.7294609150379, 411.1671601439723, 491.327834852684]; + const powerData = [14.45068359375, 14.86083984375, 15.03173828125, 15.1171875, 15.1171875, 15.1513671875, 15.185546875, 15.27099609375, 15.30517578125, 15.33935546875]; + const asicTempData = [-1, 58.5, 59.625, 60.125, 60.75, 61.5, 61.875, 62.125, 62.5, 63]; + const vrTempData = [45, 45, 45, 44, 45, 44, 44, 45, 45, 45]; + const asicVoltageData = [1221, 1223, 1219, 1223, 1217, 1222, 1221, 1219, 1221, 1221]; + const voltageData = [5196.875, 5204.6875, 5196.875, 5196.875, 5196.875, 5196.875, 5196.875, 5196.875, 5196.875, 5204.6875]; + const currentData = [2284.375, 2284.375, 2253.125, 2284.375, 2253.125, 2231.25, 2284.375, 2253.125, 2253.125, 2284.375]; + const fanSpeedData = [48, 52, 50, 52, 53, 54, 50, 50, 48, 48]; + const fanRpmData = [4032, 3545, 3904, 3691, 3564, 3554, 3691, 3573, 3701, 4044]; + const fan2RpmData = [3545, 3904, 3691, 3564, 3554, 3691, 3573, 3701, 4044, 4032]; + const wifiRssiData = [-35, -34, -33, -34, -34, -34, -33, -35, -33, -34]; + const freeHeapData = [214504, 212504, 213504, 210504, 207504, 209504, 203504, 202504, 201504, 200504]; + const responseTimeData = [15.1, 14.5, 14.3, 15.1, 13.1, 16.1, 28.6, 18.4, 17.7, 17.6, 18.0, 15.5]; + const timestampData = [13131, 18126, 23125, 28125, 33125, 38125, 43125, 48125, 53125, 58125]; columnList.push("timestamp"); let statisticsList: number[][] = []; - for(let i: number = 0; i < 10; i++) { + for (let i: number = 0; i < 10; i++) { statisticsList[i] = []; - for(let j: number = 0; j < columnList.length; j++) { + for (let j: number = 0; j < columnList.length; j++) { switch (chartLabelValue(columnList[j])) { - case eChartLabel.hashrate: statisticsList[i][j] = hashrateData[i]; break; - case eChartLabel.hashrate_1m: statisticsList[i][j] = hashrateData[i]; break; - case eChartLabel.hashrate_10m: statisticsList[i][j] = hashrateData[i]; break; - case eChartLabel.hashrate_1h: statisticsList[i][j] = hashrateData[i]; break; - case eChartLabel.power: statisticsList[i][j] = powerData[i]; break; - case eChartLabel.asicTemp: statisticsList[i][j] = asicTempData[i]; break; - case eChartLabel.vrTemp: statisticsList[i][j] = vrTempData[i]; break; - case eChartLabel.asicVoltage: statisticsList[i][j] = asicVoltageData[i]; break; - case eChartLabel.voltage: statisticsList[i][j] = voltageData[i]; break; - case eChartLabel.current: statisticsList[i][j] = currentData[i]; break; - case eChartLabel.fanSpeed: statisticsList[i][j] = fanSpeedData[i]; break; - case eChartLabel.fanRpm: statisticsList[i][j] = fanRpmData[i]; break; - case eChartLabel.fan2Rpm: statisticsList[i][j] = fan2RpmData[i]; break; - case eChartLabel.wifiRssi: statisticsList[i][j] = wifiRssiData[i]; break; - case eChartLabel.freeHeap: statisticsList[i][j] = freeHeapData[i]; break; + case eChartLabel.hashrate: statisticsList[i][j] = hashrateData[i]; break; + case eChartLabel.hashrate_1m: statisticsList[i][j] = hashrateData[i]; break; + case eChartLabel.hashrate_10m: statisticsList[i][j] = hashrateData[i]; break; + case eChartLabel.hashrate_1h: statisticsList[i][j] = hashrateData[i]; break; + case eChartLabel.power: statisticsList[i][j] = powerData[i]; break; + case eChartLabel.asicTemp: statisticsList[i][j] = asicTempData[i]; break; + case eChartLabel.vrTemp: statisticsList[i][j] = vrTempData[i]; break; + case eChartLabel.asicVoltage: statisticsList[i][j] = asicVoltageData[i]; break; + case eChartLabel.voltage: statisticsList[i][j] = voltageData[i]; break; + case eChartLabel.current: statisticsList[i][j] = currentData[i]; break; + case eChartLabel.fanSpeed: statisticsList[i][j] = fanSpeedData[i]; break; + case eChartLabel.fanRpm: statisticsList[i][j] = fanRpmData[i]; break; + case eChartLabel.fan2Rpm: statisticsList[i][j] = fan2RpmData[i]; break; + case eChartLabel.wifiRssi: statisticsList[i][j] = wifiRssiData[i]; break; + case eChartLabel.freeHeap: statisticsList[i][j] = freeHeapData[i]; break; case eChartLabel.responseTime: statisticsList[i][j] = responseTimeData[i]; break; default: if (columnList[j] === "timestamp") { @@ -309,4 +313,28 @@ export class SystemApiService { } + + + public getAutotune() { + if (environment.production) { + return this.httpClient.get('/api/system/autotune').pipe(timeout(5000)); + } + + // Mock data for development + return of({ + power_limit: 20, + fan_limit: 75, + max_volt_asic: 1400, + max_freq_asic: 1000, + max_temp_asic: 65, + max_temp_vr: 85, + auto_tune: false, + osh_pow_limit: 0.2, + osh_fan_limit: 5, + }).pipe(delay(1000)); + } + + public updateAutotune(data: any) { + return this.httpClient.post('/api/system/autotune', data); + } } diff --git a/main/http_server/axe-os/src/models/IAutotuneSettings.ts b/main/http_server/axe-os/src/models/IAutotuneSettings.ts new file mode 100644 index 000000000..667e746c2 --- /dev/null +++ b/main/http_server/axe-os/src/models/IAutotuneSettings.ts @@ -0,0 +1,11 @@ +export interface IAutotuneSettings { + power_limit: number; + fan_limit: number; + max_volt_asic: number; + max_freq_asic: number; + max_temp_asic: number; + max_temp_vr: number; + auto_tune: boolean; + osh_pow_limit: number; + osh_fan_limit: number; +} \ No newline at end of file diff --git a/main/http_server/axe-os/src/models/enum/eChartLabel.ts b/main/http_server/axe-os/src/models/enum/eChartLabel.ts index 6443df997..bea406074 100644 --- a/main/http_server/axe-os/src/models/enum/eChartLabel.ts +++ b/main/http_server/axe-os/src/models/enum/eChartLabel.ts @@ -7,6 +7,7 @@ export enum eChartLabel { errorPercentage = 'Error %', vrTemp = 'VR Temp', asicVoltage = 'ASIC Voltage', + asicVoltageSet = 'ASIC Voltage Set', voltage = 'Voltage', power = 'Power', current = 'Current', @@ -16,6 +17,7 @@ export enum eChartLabel { wifiRssi = 'Wi-Fi RSSI', freeHeap = 'Free Heap', responseTime = 'Response Time', + frequency = 'Frequency', none = 'None' } diff --git a/main/http_server/http_server.c b/main/http_server/http_server.c index d0ef19b25..ce0351eb6 100644 --- a/main/http_server/http_server.c +++ b/main/http_server/http_server.c @@ -40,6 +40,7 @@ #include "asic.h" #include "TPS546.h" #include "statistics_task.h" +#include "auto_tune.h" #include "theme_api.h" // Add theme API include #include "axe-os/api/system/asic_settings.h" #include "display.h" @@ -58,6 +59,7 @@ static const char * STATS_LABEL_ERROR_PERCENTAGE = "errorPercentage"; static const char * STATS_LABEL_ASIC_TEMP = "asicTemp"; static const char * STATS_LABEL_VR_TEMP = "vrTemp"; static const char * STATS_LABEL_ASIC_VOLTAGE = "asicVoltage"; +static const char * STATS_LABEL_ASIC_VOLTAGE_SET = "asicVoltageSet"; static const char * STATS_LABEL_VOLTAGE = "voltage"; static const char * STATS_LABEL_POWER = "power"; static const char * STATS_LABEL_CURRENT = "current"; @@ -67,6 +69,7 @@ static const char * STATS_LABEL_FAN2_RPM = "fan2Rpm"; static const char * STATS_LABEL_WIFI_RSSI = "wifiRssi"; static const char * STATS_LABEL_FREE_HEAP = "freeHeap"; static const char * STATS_LABEL_RESPONSE_TIME = "responseTime"; +static const char * STATS_LABEL_FREQUENCY = "frequency"; static const char * STATS_LABEL_TIMESTAMP = "timestamp"; @@ -85,6 +88,7 @@ typedef enum SRC_ASIC_TEMP, SRC_VR_TEMP, SRC_ASIC_VOLTAGE, + SRC_ASIC_VOLTAGE_SET, SRC_VOLTAGE, SRC_POWER, SRC_CURRENT, @@ -94,6 +98,7 @@ typedef enum SRC_WIFI_RSSI, SRC_FREE_HEAP, SRC_RESPONSE_TIME, + SRC_FREQUENCY, SRC_NONE // last } DataSource; @@ -111,12 +116,14 @@ DataSource strToDataSource(const char * sourceStr) if (strcmp(sourceStr, STATS_LABEL_ASIC_TEMP) == 0) return SRC_ASIC_TEMP; if (strcmp(sourceStr, STATS_LABEL_VR_TEMP) == 0) return SRC_VR_TEMP; if (strcmp(sourceStr, STATS_LABEL_ASIC_VOLTAGE) == 0) return SRC_ASIC_VOLTAGE; + if (strcmp(sourceStr, STATS_LABEL_ASIC_VOLTAGE_SET) == 0) return SRC_ASIC_VOLTAGE_SET; if (strcmp(sourceStr, STATS_LABEL_FAN_SPEED) == 0) return SRC_FAN_SPEED; if (strcmp(sourceStr, STATS_LABEL_FAN_RPM) == 0) return SRC_FAN_RPM; if (strcmp(sourceStr, STATS_LABEL_FAN2_RPM) == 0) return SRC_FAN2_RPM; if (strcmp(sourceStr, STATS_LABEL_WIFI_RSSI) == 0) return SRC_WIFI_RSSI; if (strcmp(sourceStr, STATS_LABEL_FREE_HEAP) == 0) return SRC_FREE_HEAP; if (strcmp(sourceStr, STATS_LABEL_RESPONSE_TIME) == 0) return SRC_RESPONSE_TIME; + if (strcmp(sourceStr, STATS_LABEL_FREQUENCY) == 0) return SRC_FREQUENCY; } return SRC_NONE; } @@ -805,7 +812,7 @@ static esp_err_t GET_system_info(httpd_req_t * req) char * stratumCert = nvs_config_get_string(NVS_CONFIG_STRATUM_CERT); char * fallbackStratumCert = nvs_config_get_string(NVS_CONFIG_FALLBACK_STRATUM_CERT); char * display = nvs_config_get_string(NVS_CONFIG_DISPLAY); - float frequency = nvs_config_get_float(NVS_CONFIG_ASIC_FREQUENCY); + float frequency = GLOBAL_STATE->POWER_MANAGEMENT_MODULE.frequency_value; uint8_t mac[6]; esp_wifi_get_mac(WIFI_IF_STA, mac); @@ -846,7 +853,9 @@ static esp_err_t GET_system_info(httpd_req_t * req) cJSON_AddNumberToObject(root, "coreVoltage", nvs_config_get_u16(NVS_CONFIG_ASIC_VOLTAGE)); cJSON_AddNumberToObject(root, "coreVoltageActual", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.core_voltage); + cJSON_AddNumberToObject(root, "coreVoltageSet", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.core_voltage); cJSON_AddNumberToObject(root, "frequency", frequency); + cJSON_AddNumberToObject(root, "frequencySet", nvs_config_get_float(NVS_CONFIG_ASIC_FREQUENCY)); cJSON_AddStringToObject(root, "ssid", ssid); cJSON_AddStringToObject(root, "macAddr", formattedMac); cJSON_AddStringToObject(root, "hostname", hostname); @@ -1024,6 +1033,7 @@ static esp_err_t GET_system_statistics(httpd_req_t * req) if (dataSelection[SRC_VR_TEMP]) { cJSON_AddItemToArray(labelArray, cJSON_CreateString(STATS_LABEL_VR_TEMP)); } if (dataSelection[SRC_ASIC_VOLTAGE]) { cJSON_AddItemToArray(labelArray, cJSON_CreateString(STATS_LABEL_ASIC_VOLTAGE)); } if (dataSelection[SRC_VOLTAGE]) { cJSON_AddItemToArray(labelArray, cJSON_CreateString(STATS_LABEL_VOLTAGE)); } + if (dataSelection[SRC_ASIC_VOLTAGE_SET]) { cJSON_AddItemToArray(labelArray, cJSON_CreateString(STATS_LABEL_ASIC_VOLTAGE_SET)); } if (dataSelection[SRC_POWER]) { cJSON_AddItemToArray(labelArray, cJSON_CreateString(STATS_LABEL_POWER)); } if (dataSelection[SRC_CURRENT]) { cJSON_AddItemToArray(labelArray, cJSON_CreateString(STATS_LABEL_CURRENT)); } if (dataSelection[SRC_FAN_SPEED]) { cJSON_AddItemToArray(labelArray, cJSON_CreateString(STATS_LABEL_FAN_SPEED)); } @@ -1032,6 +1042,7 @@ static esp_err_t GET_system_statistics(httpd_req_t * req) if (dataSelection[SRC_WIFI_RSSI]) { cJSON_AddItemToArray(labelArray, cJSON_CreateString(STATS_LABEL_WIFI_RSSI)); } if (dataSelection[SRC_FREE_HEAP]) { cJSON_AddItemToArray(labelArray, cJSON_CreateString(STATS_LABEL_FREE_HEAP)); } if (dataSelection[SRC_RESPONSE_TIME]) { cJSON_AddItemToArray(labelArray, cJSON_CreateString(STATS_LABEL_RESPONSE_TIME)); } + if (dataSelection[SRC_FREQUENCY]) { cJSON_AddItemToArray(labelArray, cJSON_CreateString(STATS_LABEL_FREQUENCY)); } cJSON_AddItemToArray(labelArray, cJSON_CreateString(STATS_LABEL_TIMESTAMP)); cJSON_AddItemToObject(root, "labels", labelArray); @@ -1050,6 +1061,7 @@ static esp_err_t GET_system_statistics(httpd_req_t * req) if (dataSelection[SRC_ASIC_TEMP]) { cJSON_AddItemToArray(valueArray, cJSON_CreateFloat(statsData.chipTemperature)); } if (dataSelection[SRC_VR_TEMP]) { cJSON_AddItemToArray(valueArray, cJSON_CreateFloat(statsData.vrTemperature)); } if (dataSelection[SRC_ASIC_VOLTAGE]) { cJSON_AddItemToArray(valueArray, cJSON_CreateNumber(statsData.coreVoltageActual)); } + if (dataSelection[SRC_ASIC_VOLTAGE_SET]) { cJSON_AddItemToArray(valueArray, cJSON_CreateNumber(statsData.core_voltage)); } if (dataSelection[SRC_VOLTAGE]) { cJSON_AddItemToArray(valueArray, cJSON_CreateFloat(statsData.voltage)); } if (dataSelection[SRC_POWER]) { cJSON_AddItemToArray(valueArray, cJSON_CreateFloat(statsData.power)); } if (dataSelection[SRC_CURRENT]) { cJSON_AddItemToArray(valueArray, cJSON_CreateFloat(statsData.current)); } @@ -1059,6 +1071,7 @@ static esp_err_t GET_system_statistics(httpd_req_t * req) if (dataSelection[SRC_WIFI_RSSI]) { cJSON_AddItemToArray(valueArray, cJSON_CreateNumber(statsData.wifiRSSI)); } if (dataSelection[SRC_FREE_HEAP]) { cJSON_AddItemToArray(valueArray, cJSON_CreateNumber(statsData.freeHeap)); } if (dataSelection[SRC_RESPONSE_TIME]) { cJSON_AddItemToArray(valueArray, cJSON_CreateFloat(statsData.responseTime)); } + if (dataSelection[SRC_FREQUENCY]) { cJSON_AddItemToArray(valueArray, cJSON_CreateNumber(statsData.frequency)); } cJSON_AddItemToArray(valueArray, cJSON_CreateNumber(statsData.timestamp)); cJSON_AddItemToArray(statsArray, valueArray); @@ -1230,6 +1243,110 @@ esp_err_t http_404_error_handler(httpd_req_t * req, httpd_err_code_t err) return ESP_OK; } +esp_err_t GET_autotune_info(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"); + if (set_cors_headers(req) != ESP_OK) { + httpd_resp_send_500(req); + return ESP_OK; + } + + cJSON *root = cJSON_CreateObject(); + cJSON_AddNumberToObject(root, nvs_config_get_settings(NVS_CONFIG_KEY_POWER_LIMIT)->nvs_key_name, AUTO_TUNE.power_limit); + cJSON_AddNumberToObject(root, nvs_config_get_settings(NVS_CONFIG_KEY_FAN_LIMIT)->nvs_key_name, AUTO_TUNE.fan_limit); + cJSON_AddNumberToObject(root, nvs_config_get_settings(NVS_CONFIG_KEY_MAX_VOLTAGE_ASIC)->nvs_key_name, AUTO_TUNE.max_voltage_asic); + cJSON_AddNumberToObject(root, nvs_config_get_settings(NVS_CONFIG_KEY_MAX_FREQUENCY_ASIC)->nvs_key_name, AUTO_TUNE.max_frequency_asic); + cJSON_AddNumberToObject(root, nvs_config_get_settings(NVS_CONFIG_KEY_MAX_TEMP_ASIC)->nvs_key_name, AUTO_TUNE.max_temp_asic); + cJSON_AddBoolToObject(root, nvs_config_get_settings(NVS_CONFIG_KEY_AUTO_TUNE_ENABLE)->nvs_key_name, AUTO_TUNE.auto_tune_hashrate); + cJSON_AddNumberToObject(root, nvs_config_get_settings(NVS_CONFIG_KEY_OVERSHOT_POWER_LIMIT)->nvs_key_name, AUTO_TUNE.overshot_power_limit); + cJSON_AddNumberToObject(root, nvs_config_get_settings(NVS_CONFIG_KEY_OVERSHOT_FAN_LIMIT)->nvs_key_name, AUTO_TUNE.overshot_fanspeed); + cJSON_AddNumberToObject(root, nvs_config_get_settings(NVS_CONFIG_KEY_MAX_TEMP_VR)->nvs_key_name, AUTO_TUNE.max_temp_vr); + + const char *response = cJSON_Print(root); + httpd_resp_sendstr(req, response); + free((void *)response); + cJSON_Delete(root); + return ESP_OK; +} + +esp_err_t POST_autotune_update(httpd_req_t * req) +{ + if (is_network_allowed(req) != ESP_OK) { + return httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized"); + } + if (set_cors_headers(req) != ESP_OK) { + httpd_resp_send_500(req); + return ESP_OK; + } + + int total_len = req->content_len; + char buf[512]; + int received = 0, cur_len = 0; + if (total_len >= sizeof(buf)) { + 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 - cur_len); + if (received <= 0) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive data"); + return ESP_OK; + } + cur_len += received; + } + buf[total_len] = '\0'; + + cJSON *root = cJSON_Parse(buf); + if (!root) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); + return ESP_OK; + } + + cJSON *item; + if ((item = cJSON_GetObjectItem(root, nvs_config_get_settings(NVS_CONFIG_KEY_POWER_LIMIT)->nvs_key_name)) && cJSON_IsNumber(item)) { + AUTO_TUNE.power_limit = item->valuedouble; + nvs_config_set_u16(NVS_CONFIG_KEY_POWER_LIMIT, (uint16_t)AUTO_TUNE.power_limit); + } + if ((item = cJSON_GetObjectItem(root, nvs_config_get_settings(NVS_CONFIG_KEY_FAN_LIMIT)->nvs_key_name)) && cJSON_IsNumber(item)) { + AUTO_TUNE.fan_limit = item->valuedouble; + nvs_config_set_u16(NVS_CONFIG_KEY_FAN_LIMIT, (uint16_t)AUTO_TUNE.fan_limit); + } + if ((item = cJSON_GetObjectItem(root, nvs_config_get_settings(NVS_CONFIG_KEY_MAX_VOLTAGE_ASIC)->nvs_key_name)) && cJSON_IsNumber(item)) { + AUTO_TUNE.max_voltage_asic = item->valuedouble; + nvs_config_set_u16(NVS_CONFIG_KEY_MAX_VOLTAGE_ASIC, (uint16_t)AUTO_TUNE.max_voltage_asic); + } + if ((item = cJSON_GetObjectItem(root, nvs_config_get_settings(NVS_CONFIG_KEY_MAX_FREQUENCY_ASIC)->nvs_key_name)) && cJSON_IsNumber(item)) { + AUTO_TUNE.max_frequency_asic = item->valuedouble; + nvs_config_set_u16(NVS_CONFIG_KEY_MAX_FREQUENCY_ASIC, (uint16_t)AUTO_TUNE.max_frequency_asic); + } + if ((item = cJSON_GetObjectItem(root, nvs_config_get_settings(NVS_CONFIG_KEY_MAX_TEMP_ASIC)->nvs_key_name)) && cJSON_IsNumber(item)) { + AUTO_TUNE.max_temp_asic = item->valuedouble; + nvs_config_set_u16(NVS_CONFIG_KEY_MAX_TEMP_ASIC, (uint16_t)AUTO_TUNE.max_temp_asic); + } + if ((item = cJSON_GetObjectItem(root, nvs_config_get_settings(NVS_CONFIG_KEY_AUTO_TUNE_ENABLE)->nvs_key_name)) && cJSON_IsBool(item)) { + AUTO_TUNE.auto_tune_hashrate = item->valueint; + nvs_config_set_bool(NVS_CONFIG_KEY_AUTO_TUNE_ENABLE, (bool)AUTO_TUNE.auto_tune_hashrate); + } + if ((item = cJSON_GetObjectItem(root, nvs_config_get_settings(NVS_CONFIG_KEY_OVERSHOT_POWER_LIMIT)->nvs_key_name)) && cJSON_IsNumber(item)) { + AUTO_TUNE.overshot_power_limit = item->valuedouble; + nvs_config_set_float(NVS_CONFIG_KEY_OVERSHOT_POWER_LIMIT, AUTO_TUNE.overshot_power_limit); + } + if ((item = cJSON_GetObjectItem(root, nvs_config_get_settings(NVS_CONFIG_KEY_OVERSHOT_FAN_LIMIT)->nvs_key_name)) && cJSON_IsNumber(item)) { + AUTO_TUNE.overshot_fanspeed = (uint16_t)item->valuedouble; + nvs_config_set_u16(NVS_CONFIG_KEY_OVERSHOT_FAN_LIMIT, (uint16_t)AUTO_TUNE.overshot_fanspeed); + } + if ((item = cJSON_GetObjectItem(root, nvs_config_get_settings(NVS_CONFIG_KEY_MAX_TEMP_VR)->nvs_key_name)) && cJSON_IsNumber(item)) { + AUTO_TUNE.max_temp_vr = (uint16_t)item->valueint; + nvs_config_set_u16(NVS_CONFIG_KEY_MAX_TEMP_VR, (uint16_t)AUTO_TUNE.max_temp_vr); + } + + cJSON_Delete(root); + httpd_resp_send_chunk(req, NULL, 0); + return ESP_OK; +} esp_err_t start_rest_server(void * pvParameters) { GLOBAL_STATE = (GlobalState *) pvParameters; @@ -1254,7 +1371,7 @@ esp_err_t start_rest_server(void * pvParameters) config.uri_match_fn = httpd_uri_match_wildcard; config.stack_size = 8192; config.max_open_sockets = 20; - config.max_uri_handlers = 20; + config.max_uri_handlers = 22; config.close_fn = websocket_close_fn; config.lru_purge_enable = true; @@ -1370,6 +1487,21 @@ esp_err_t start_rest_server(void * pvParameters) }; httpd_register_uri_handler(server, &update_post_ota_www); + httpd_uri_t autotune_get_uri = { + .uri = "/api/system/autotune", + .method = HTTP_GET, + .handler = GET_autotune_info, + .user_ctx = rest_context + }; + httpd_register_uri_handler(server, &autotune_get_uri); + + httpd_uri_t autotune_post_uri = { + .uri = "/api/system/autotune", + .method = HTTP_POST, + .handler = POST_autotune_update, + .user_ctx = rest_context + }; + httpd_register_uri_handler(server, &autotune_post_uri); httpd_uri_t ws = { .uri = "/api/ws", .method = HTTP_GET, diff --git a/main/http_server/openapi.yaml b/main/http_server/openapi.yaml index c7bd3a27f..4e67db2bc 100644 --- a/main/http_server/openapi.yaml +++ b/main/http_server/openapi.yaml @@ -17,6 +17,62 @@ servers: components: schemas: + AutotuneSettings: + type: object + properties: + power_limit: + type: number + description: Power limit (in watts) + minimum: 0 + maximum: 60 + fan_limit: + type: number + description: Fan speed limit (%) + minimum: 0 + maximum: 100 + max_volt_asic: + type: number + description: Maximum voltage for ASIC chips (volts) + minimum: 0.5 + maximum: 20 + max_freq_asic: + type: number + description: Maximum frequency for ASIC chips (MHz) + minimum: 1100 + maximum: 1400 + max_temp_asic: + type: number + description: Maximum temperature for ASIC chips (°C) + minimum: 30 + maximum: 90 + auto_tune: + type: boolean + description: Whether autotuning is enabled to optimize hashrate + osh_pow_limit: + type: number + description: Power limit during overshoot conditions (watts) + minimum: 0 + maximum: 2 + osh_fan_limit: + type: number + description: Fan speed limit during overshoot conditions (%) + minimum: 0 + maximum: 20 + max_temp_vr: + type: number + description: Maximum temperature for VR (°C) + minimum: 40 + maximum: 95 + required: + - power_limit + - fan_limit + - max_voltage_asic + - max_frequency_asic + - max_temp_asic + - auto_tune_hashrate + - overshoot_power_limit + - overshoot_fanspeed + - max_temp_vr GenericResponse: type: object required: @@ -120,6 +176,7 @@ components: - boardVersion - coreVoltage - coreVoltageActual + - coreVoltageSet - current - display - fallbackStratumExtranonceSubscribe @@ -138,6 +195,7 @@ components: - freeHeapInternal - freeHeapSpiram - frequency + - frequencySet - hashRate - hashRate_1m - hashRate_10m @@ -217,6 +275,9 @@ components: coreVoltageActual: type: number description: Actual measured ASIC core voltage + coreVoltageSet: + type: number + description: Actual set ASIC core voltage current: type: number description: Current draw in milliamps @@ -274,6 +335,9 @@ components: frequency: type: number description: ASIC frequency in MHz + frequencySet: + type: number + description: ASIC frequency set in MHz hashRate: type: number description: Current hashrate in Gh/s @@ -609,12 +673,24 @@ components: minimum: 1 examples: - 850 + coreVoltageSet: + type: integer + description: ASIC core voltage set in millivolts + minimum: 1 + examples: + - 850 frequency: type: integer description: ASIC frequency in MHz minimum: 1 examples: - 450 + frequencySet: + type: integer + description: ASIC frequency set in MHz + minimum: 1 + examples: + - 450 rotation: type: integer description: Whether to rotate the screen orientation (0, 90, 180, 270 degrees) @@ -893,3 +969,37 @@ paths: description: Unauthorized - Client not in allowed network range '500': description: Internal server error + /api/system/autotune: + post: + summary: Update autotuning configuration + description: Updates the current autotuning settings via a JSON body. Requires valid authentication. + tags: + - Autotune + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AutotuneSettings' + responses: + '204': + description: Settings updated successfully (no content returned) + operationId: updateAutotuneSettings # <-- renamed from autotune + + get: + summary: Get current autotuning configuration + description: Returns the current power, fan, temperature, and frequency limits used in autotuning. + tags: + - Autotune + responses: + '200': + description: Successful response with autotuning settings in JSON format + content: + application/json: + schema: + $ref: '#/components/schemas/AutotuneSettings' + '401': + description: Unauthorized - Client not in allowed network range + '500': + description: Internal server error + operationId: getAutotuneSettings # <-- renamed from autotune diff --git a/main/nvs_config.c b/main/nvs_config.c index 5d5fdeea3..e2a269cf8 100644 --- a/main/nvs_config.c +++ b/main/nvs_config.c @@ -42,8 +42,8 @@ static const char * TAG = "nvs_config"; static QueueHandle_t nvs_save_queue = NULL; static nvs_handle_t handle; - -static Settings settings[NVS_CONFIG_COUNT] = { +//removed NVS_CONFIG_COUNT to have the ability to control the settings read by http loop +static Settings settings[] = { [NVS_CONFIG_WIFI_SSID] = {.nvs_key_name = "wifissid", .type = TYPE_STR, .default_value = {.str = (char *)CONFIG_ESP_WIFI_SSID}, .rest_name = "ssid", .min = 1, .max = 32}, [NVS_CONFIG_WIFI_PASS] = {.nvs_key_name = "wifipass", .type = TYPE_STR, .default_value = {.str = (char *)CONFIG_ESP_WIFI_PASSWORD}, .rest_name = "wifiPass", .min = 0, .max = 63}, [NVS_CONFIG_HOSTNAME] = {.nvs_key_name = "hostname", .type = TYPE_STR, .default_value = {.str = (char *)CONFIG_LWIP_LOCAL_HOSTNAME}, .rest_name = "hostname", .min = 1, .max = 32}, @@ -107,11 +107,21 @@ static Settings settings[NVS_CONFIG_COUNT] = { [NVS_CONFIG_TPS546] = {.nvs_key_name = "TPS546", .type = TYPE_BOOL}, [NVS_CONFIG_TMP1075] = {.nvs_key_name = "TMP1075", .type = TYPE_BOOL}, [NVS_CONFIG_POWER_CONSUMPTION_TARGET] = {.nvs_key_name = "power_cons_tgt", .type = TYPE_U16}, + + [NVS_CONFIG_KEY_POWER_LIMIT] = {.nvs_key_name = "power_limit", .type = TYPE_U16, .default_value = {.u16 = 20}}, + [NVS_CONFIG_KEY_FAN_LIMIT] = {.nvs_key_name = "fan_limit", .type = TYPE_U16, .default_value = {.u16 = 75},}, + [NVS_CONFIG_KEY_MAX_VOLTAGE_ASIC] = {.nvs_key_name = "max_volt_asic", .type = TYPE_U16, .default_value = {.u16 = 1400},}, + [NVS_CONFIG_KEY_MAX_FREQUENCY_ASIC] = {.nvs_key_name = "max_freq_asic", .type = TYPE_U16, .default_value = {.u16 = 1000},}, + [NVS_CONFIG_KEY_MAX_TEMP_ASIC] = {.nvs_key_name = "max_temp_asic", .type = TYPE_U16, .default_value = {.u16 = 61},}, + [NVS_CONFIG_KEY_AUTO_TUNE_ENABLE] = {.nvs_key_name = "auto_tune", .type = TYPE_BOOL, .default_value = {.b = false}}, + [NVS_CONFIG_KEY_OVERSHOT_POWER_LIMIT] = {.nvs_key_name = "osh_pow_limit", .type = TYPE_FLOAT, .default_value = {.f = 0.2f},}, + [NVS_CONFIG_KEY_OVERSHOT_FAN_LIMIT] = {.nvs_key_name = "osh_fan_limit", .type = TYPE_U16, .default_value = {.u16 = 10},}, + [NVS_CONFIG_KEY_MAX_TEMP_VR] = {.nvs_key_name = "max_temp_vr", .type = TYPE_U16, .default_value = {.u16 = 85},} }; Settings *nvs_config_get_settings(NvsConfigKey key) { - if (key < 0 || key >= NVS_CONFIG_COUNT) { + if (key < 0 || key > NVS_ALL_CONFIG_COUNT) { ESP_LOGE(TAG, "Invalid key enum %d", key); return NULL; } @@ -226,7 +236,7 @@ esp_err_t nvs_config_init(void) } // Load all - for (NvsConfigKey key = 0; key < NVS_CONFIG_COUNT; key++) { + for (NvsConfigKey key = 0; key < NVS_ALL_CONFIG_COUNT; key++) { Settings *setting = &settings[key]; nvs_config_init_fallback(key, setting); diff --git a/main/nvs_config.h b/main/nvs_config.h index 14b600a2d..3a41bfdbb 100644 --- a/main/nvs_config.h +++ b/main/nvs_config.h @@ -70,7 +70,20 @@ typedef enum { NVS_CONFIG_TPS546, NVS_CONFIG_TMP1075, NVS_CONFIG_POWER_CONSUMPTION_TARGET, - NVS_CONFIG_COUNT + //all after that get not read when looping through the settings,but is still avail due nvs + NVS_CONFIG_COUNT, + + NVS_CONFIG_KEY_POWER_LIMIT, + NVS_CONFIG_KEY_FAN_LIMIT, + NVS_CONFIG_KEY_MAX_VOLTAGE_ASIC, + NVS_CONFIG_KEY_MAX_FREQUENCY_ASIC, + NVS_CONFIG_KEY_MAX_TEMP_ASIC, + NVS_CONFIG_KEY_AUTO_TUNE_ENABLE, + NVS_CONFIG_KEY_OVERSHOT_POWER_LIMIT, + NVS_CONFIG_KEY_OVERSHOT_FAN_LIMIT, + NVS_CONFIG_KEY_MAX_TEMP_VR, + NVS_ALL_CONFIG_COUNT + } NvsConfigKey; typedef enum { @@ -92,6 +105,7 @@ typedef union { } ConfigValue; typedef struct { + //max key char length 15! const char *nvs_key_name; ConfigType type; ConfigValue value; diff --git a/main/tasks/hashrate_monitor_task.c b/main/tasks/hashrate_monitor_task.c index 09f19612a..dbad7013b 100644 --- a/main/tasks/hashrate_monitor_task.c +++ b/main/tasks/hashrate_monitor_task.c @@ -13,7 +13,7 @@ #define HASHRATE_UNIT 0x100000uLL // Hashrate register unit (2^24 hashes) -#define POLL_RATE 5000 +#define POLL_RATE 1000 #define HASHRATE_1M_SIZE (60000 / POLL_RATE) // 12 #define HASHRATE_10M_SIZE 10 #define HASHRATE_1H_SIZE 6 diff --git a/main/tasks/power_management_task.c b/main/tasks/power_management_task.c index c5c37a43d..cac9be7c1 100644 --- a/main/tasks/power_management_task.c +++ b/main/tasks/power_management_task.c @@ -14,6 +14,7 @@ #include "PID.h" #include "power.h" #include "asic.h" +#include "auto_tune.h" #include "bm1370.h" #include "utils.h" #include "asic_init.h" @@ -21,7 +22,6 @@ #include "driver/uart.h" #define EPSILON 0.0001f -#define POLL_RATE 1800 #define MAX_TEMP 90.0 #define THROTTLE_TEMP 75.0 #define SAFE_TEMP 45.0 @@ -33,6 +33,7 @@ #define TPS546_THROTTLE_TEMP 105.0 #define TPS546_MAX_TEMP 145.0 +#define POLL_RATE 1000 #define ASIC_REDUCTION 100.0 @@ -98,7 +99,8 @@ void POWER_MANAGEMENT_task(void * pvParameters) pid_set_mode(&pid, AUTOMATIC); // This calls pid_initialize() internally vTaskDelay(500 / portTICK_PERIOD_MS); - uint16_t last_core_voltage = 0.0; + auto_tune_init(GLOBAL_STATE); + float last_core_voltage = 0.0; uint16_t last_known_asic_voltage = 0; float last_known_asic_frequency = 0.0; @@ -131,7 +133,7 @@ void POWER_MANAGEMENT_task(void * pvParameters) } power_management->fan_perc = 100; Thermal_set_fan_percent(&GLOBAL_STATE->DEVICE_CONFIG, 1); - + auto_tune_set_auto_tune_hashrate(false); VCORE_set_voltage(GLOBAL_STATE, 0.0f); ESP_LOGI(TAG, "Setting RST pin to low due to overheat condition"); @@ -210,7 +212,7 @@ void POWER_MANAGEMENT_task(void * pvParameters) } //enable the PID auto control for the FAN if set - if (nvs_config_get_bool(NVS_CONFIG_AUTO_FAN_SPEED)) { + if(nvs_config_get_bool(NVS_CONFIG_AUTO_FAN_SPEED)){ if (power_management->chip_temp_avg >= 0) { // Ignore invalid temperature readings (-1) if (power_management->chip_temp2_avg > power_management->chip_temp_avg) { pid_input = power_management->chip_temp2_avg; @@ -284,17 +286,27 @@ void POWER_MANAGEMENT_task(void * pvParameters) } } - uint16_t core_voltage = nvs_config_get_u16(NVS_CONFIG_ASIC_VOLTAGE); - float asic_frequency = nvs_config_get_float(NVS_CONFIG_ASIC_FREQUENCY); + float core_voltage = 0; + float asic_frequency = 0; + + if (!auto_tune_get_auto_tune_hashrate()) { + core_voltage = nvs_config_get_u16(NVS_CONFIG_ASIC_VOLTAGE); + asic_frequency = nvs_config_get_float(NVS_CONFIG_ASIC_FREQUENCY); + } else { + auto_tune(); + core_voltage = auto_tune_get_voltage(); + asic_frequency = auto_tune_get_frequency(); + } if (core_voltage != last_core_voltage) { - ESP_LOGI(TAG, "setting new vcore voltage to %umV", core_voltage); + ESP_LOGI(TAG, "set vcore voltage from %fmV to %fmV", last_core_voltage, core_voltage); VCORE_set_voltage(GLOBAL_STATE, (double) core_voltage / 1000.0); last_core_voltage = core_voltage; + power_management->core_voltage = core_voltage; } if (asic_frequency != last_asic_frequency) { - ESP_LOGI(TAG, "New ASIC frequency requested: %g MHz (current: %g MHz)", asic_frequency, last_asic_frequency); + ESP_LOGI(TAG, "set frequency from %.2f MHz to %.2f MHz", last_asic_frequency, asic_frequency); bool success = ASIC_set_frequency(GLOBAL_STATE, asic_frequency); diff --git a/main/tasks/statistics_task.c b/main/tasks/statistics_task.c index 122dbeed3..ac32d3bac 100644 --- a/main/tasks/statistics_task.c +++ b/main/tasks/statistics_task.c @@ -148,6 +148,8 @@ void statistics_task(void * pvParameters) statsData.wifiRSSI = wifiRSSI; statsData.freeHeap = esp_get_free_heap_size(); statsData.responseTime = sys_module->response_time; + statsData.frequency = power_management->frequency_value; + statsData.core_voltage = power_management->core_voltage; addStatisticData(&statsData); } diff --git a/main/tasks/statistics_task.h b/main/tasks/statistics_task.h index 3705f62c7..f0ea8c827 100644 --- a/main/tasks/statistics_task.h +++ b/main/tasks/statistics_task.h @@ -14,9 +14,11 @@ struct StatisticsData float chipTemperature; float vrTemperature; float power; + float frequency; float voltage; float current; int16_t coreVoltageActual; + float core_voltage; float fanSpeed; uint16_t fanRPM; uint16_t fan2RPM; diff --git a/main/thermal/auto_tune.c b/main/thermal/auto_tune.c new file mode 100644 index 000000000..130b8093e --- /dev/null +++ b/main/thermal/auto_tune.c @@ -0,0 +1,300 @@ +#include "auto_tune.h" +#include "PID.h" +#include "esp_log.h" +#include "global_state.h" +#include "nvs_config.h" +#include +#include +#define POLL_RATE 1000 + +static const char * TAG = "auto_tune"; + +auto_tune_settings AUTO_TUNE = { + .power_limit = 20, + .fan_limit = 75, + .step_volt = 0.1, + .step_freq_rampup = 0.5, + .step_freq = 0.2, + .autotune_step_frequency = 0, + .max_voltage_asic = 1400, + .max_frequency_asic = 1000, + .max_temp_asic = 65, + .max_temp_vr = 85, + .frequency = 525, + .voltage = 1150, + .auto_tune_hashrate = false, + .overshot_power_limit = 0.2, // watt + .overshot_fanspeed = 5, //% +}; + +#define HASHRATE_HISTORY_SIZE 30 +float last_core_voltage_auto; +float last_asic_frequency_auto; +float last_hashrate_auto; +float current_hashrate_auto; +float hashrate_history[HASHRATE_HISTORY_SIZE]; +int history_index = 0; +bool history_initialized = false; + +bool lastVoltageSet = false; +const int waitTime = 30; +int waitCounter = 0; +GlobalState * GLOBAL_STATE; + +#define MIN_FREQ 400 +#define MIN_VOLTAGE 1000 + +enum TuneState +{ + sleep_before_warmup, + warmup, + working +}; + +enum TuneState state; + +void update_hashrate_history(float new_value) +{ + // Initialize history if not already done + if (!history_initialized) { + for (int i = 0; i < HASHRATE_HISTORY_SIZE; i++) { + hashrate_history[i] = new_value; + } + history_initialized = true; + } + + // Add new value to circular buffer + hashrate_history[history_index] = new_value; + history_index = (history_index + 1) % HASHRATE_HISTORY_SIZE; +} + +void auto_tune_init(GlobalState * _GLOBAL_STATE) +{ + GLOBAL_STATE = _GLOBAL_STATE; + AUTO_TUNE.frequency = nvs_config_get_float(NVS_CONFIG_ASIC_FREQUENCY); + AUTO_TUNE.voltage = nvs_config_get_u16(NVS_CONFIG_ASIC_VOLTAGE); + AUTO_TUNE.power_limit = nvs_config_get_u16(NVS_CONFIG_KEY_POWER_LIMIT); + AUTO_TUNE.fan_limit = nvs_config_get_u16(NVS_CONFIG_KEY_FAN_LIMIT); + AUTO_TUNE.max_voltage_asic = nvs_config_get_u16(NVS_CONFIG_KEY_MAX_VOLTAGE_ASIC); + AUTO_TUNE.max_frequency_asic = nvs_config_get_u16(NVS_CONFIG_KEY_MAX_FREQUENCY_ASIC); + AUTO_TUNE.max_temp_asic = nvs_config_get_u16(NVS_CONFIG_KEY_MAX_TEMP_ASIC); + AUTO_TUNE.auto_tune_hashrate = nvs_config_get_bool(NVS_CONFIG_KEY_AUTO_TUNE_ENABLE); + AUTO_TUNE.overshot_power_limit = nvs_config_get_float(NVS_CONFIG_KEY_OVERSHOT_POWER_LIMIT); + AUTO_TUNE.overshot_fanspeed = nvs_config_get_u16(NVS_CONFIG_KEY_OVERSHOT_FAN_LIMIT); + AUTO_TUNE.max_temp_vr = nvs_config_get_u16(NVS_CONFIG_KEY_MAX_TEMP_VR); + + last_core_voltage_auto = AUTO_TUNE.voltage; + last_asic_frequency_auto = AUTO_TUNE.frequency; + last_hashrate_auto = GLOBAL_STATE->SYSTEM_MODULE.current_hashrate; + current_hashrate_auto = last_hashrate_auto; + + // Initialize hashrate history + update_hashrate_history(last_hashrate_auto); + + state = sleep_before_warmup; + waitCounter = 45 * 1000 / POLL_RATE; +} + +bool waitForStartUp() +{ + return current_hashrate_auto > 0 && waitCounter <= 0; +} + +bool can_increase_values() +{ + return GLOBAL_STATE->POWER_MANAGEMENT_MODULE.fan_perc < AUTO_TUNE.fan_limit && + GLOBAL_STATE->POWER_MANAGEMENT_MODULE.power < AUTO_TUNE.power_limit && + GLOBAL_STATE->POWER_MANAGEMENT_MODULE.chip_temp_avg < AUTO_TUNE.max_temp_asic && + GLOBAL_STATE->POWER_MANAGEMENT_MODULE.chip_temp2_avg < AUTO_TUNE.max_temp_asic && + GLOBAL_STATE->POWER_MANAGEMENT_MODULE.vr_temp < AUTO_TUNE.max_temp_vr; +} + +bool limithit() +{ + return GLOBAL_STATE->POWER_MANAGEMENT_MODULE.fan_perc > AUTO_TUNE.fan_limit || + GLOBAL_STATE->POWER_MANAGEMENT_MODULE.power > AUTO_TUNE.power_limit || + GLOBAL_STATE->POWER_MANAGEMENT_MODULE.chip_temp_avg > AUTO_TUNE.max_temp_asic || + GLOBAL_STATE->POWER_MANAGEMENT_MODULE.chip_temp2_avg > AUTO_TUNE.max_temp_asic; + +} + +bool critical_limithit() +{ + return GLOBAL_STATE->POWER_MANAGEMENT_MODULE.chip_temp_avg > AUTO_TUNE.max_temp_asic || + GLOBAL_STATE->POWER_MANAGEMENT_MODULE.chip_temp2_avg > AUTO_TUNE.max_temp_asic || + GLOBAL_STATE->POWER_MANAGEMENT_MODULE.power >= AUTO_TUNE.power_limit + AUTO_TUNE.overshot_power_limit || + GLOBAL_STATE->POWER_MANAGEMENT_MODULE.fan_perc >= AUTO_TUNE.fan_limit + AUTO_TUNE.overshot_fanspeed || + GLOBAL_STATE->POWER_MANAGEMENT_MODULE.vr_temp > AUTO_TUNE.max_temp_vr; +} + +bool hashrate_increased_since_last_set() +{ + if (!history_initialized) { + return false; // Not enough data yet + } + + int last_set_index = history_index; + + // Check if hashrate increased since that point + float last_value = hashrate_history[last_set_index]; + bool increased = false; + + for (int i = 1; i < HASHRATE_HISTORY_SIZE; i++) { + int idx = (last_set_index + i) % HASHRATE_HISTORY_SIZE; + if (hashrate_history[idx] > last_value) { + increased = true; + break; + } + } + + return increased; +} + +static inline float clamp(float val, float min, float max) +{ + return (val < min) ? min : ((val > max) ? max : val); +} + +void increase_values() +{ + if (!lastVoltageSet) { + last_asic_frequency_auto += AUTO_TUNE.autotune_step_frequency;; + } else { + last_core_voltage_auto += AUTO_TUNE.step_volt; + } +} + +void respectLimits() +{ + last_asic_frequency_auto = clamp(last_asic_frequency_auto, MIN_FREQ, AUTO_TUNE.max_frequency_asic); + last_core_voltage_auto = clamp(last_core_voltage_auto, MIN_VOLTAGE, AUTO_TUNE.max_voltage_asic); + + if (last_asic_frequency_auto == MIN_FREQ || last_core_voltage_auto == MIN_VOLTAGE) { + lastVoltageSet = true; // Assuming default voltage set to be initial value + } +} + +bool check_dead_cores() +{ + int asic_count = GLOBAL_STATE->DEVICE_CONFIG.family.asic_count; + int domains = GLOBAL_STATE->DEVICE_CONFIG.family.asic.hash_domains; + bool core_died = false; + for(int i = 0; i < asic_count; i++) { + float avg_hash = 0; + for(int d = 0; d < domains; d++) { + avg_hash += GLOBAL_STATE->HASHRATE_MONITOR_MODULE.domain_measurements[i][d].hashrate; + } + avg_hash /= domains; + for(int d = 0; d < domains; d++) { + if(GLOBAL_STATE->HASHRATE_MONITOR_MODULE.domain_measurements[i][d].hashrate <= avg_hash * 0.1f) + core_died = true; + } + } + if(core_died) + { + last_asic_frequency_auto -= 1.f; + last_core_voltage_auto += AUTO_TUNE.step_volt; + lastVoltageSet = true; + ESP_LOGI(TAG,"Core died, increase voltage"); + } + return core_died; +} + +void dowork() +{ + if (critical_limithit()) { + last_asic_frequency_auto -= AUTO_TUNE.step_freq; + last_core_voltage_auto -= AUTO_TUNE.step_volt; + } else if (!check_dead_cores() && can_increase_values()) { + // Check if error increased since last voltage/frequency set + bool error_increased = hashrate_increased_since_last_set(); + + // If error did increase, switch the setting + if (error_increased) { + lastVoltageSet = !lastVoltageSet; + } + increase_values(); + } + + ESP_LOGI(TAG, "Hashrate %f Voltage %f Frequency %f", current_hashrate_auto, last_core_voltage_auto, last_asic_frequency_auto); + + respectLimits(); + AUTO_TUNE.voltage = last_core_voltage_auto; + AUTO_TUNE.frequency = last_asic_frequency_auto; +} + +void auto_tune() +{ + bool pid_active = nvs_config_get_bool(NVS_CONFIG_AUTO_FAN_SPEED); + if(!pid_active) + { + int manspeed = nvs_config_get_u16(NVS_CONFIG_MANUAL_FAN_SPEED); + if(manspeed > AUTO_TUNE.fan_limit) + AUTO_TUNE.fan_limit = manspeed + 1; + } + else + { + int targettemp = nvs_config_get_u16(NVS_CONFIG_TEMP_TARGET); + if(targettemp > AUTO_TUNE.max_temp_asic) + AUTO_TUNE.max_temp_asic = targettemp + 1; + } + current_hashrate_auto = GLOBAL_STATE->SYSTEM_MODULE.hashrate_10m; + update_hashrate_history(current_hashrate_auto); + + switch (state) { + case sleep_before_warmup: + if (GLOBAL_STATE->POWER_MANAGEMENT_MODULE.chip_temp_avg == -1) { + break; + } + + if (waitCounter-- > 0) { + ESP_LOGI(TAG, "state sleep_bevor_warmup %i", waitCounter); + break; + } + + if (waitForStartUp()) { + state = warmup; + } + break; + + case warmup: + AUTO_TUNE.autotune_step_frequency = AUTO_TUNE.step_freq_rampup; + dowork(); + if (limithit()) { + AUTO_TUNE.autotune_step_frequency = AUTO_TUNE.step_freq; + state = working; + } + break; + + case working: + if (limithit() && !critical_limithit()) { + check_dead_cores(); + break; // Added this line to stop adjusting when limit is hit + + } else { + dowork(); // Resume adjustments once limits are no longer breached + } + break; + } + last_hashrate_auto = current_hashrate_auto; +} + +float auto_tune_get_frequency() +{ + return AUTO_TUNE.frequency; +} + +float auto_tune_get_voltage() +{ + return AUTO_TUNE.voltage; +} + +bool auto_tune_get_auto_tune_hashrate() +{ + return AUTO_TUNE.auto_tune_hashrate; +} + +void auto_tune_set_auto_tune_hashrate(bool enable) +{ + AUTO_TUNE.auto_tune_hashrate = enable; +} \ No newline at end of file diff --git a/main/thermal/auto_tune.h b/main/thermal/auto_tune.h new file mode 100644 index 000000000..36ef59385 --- /dev/null +++ b/main/thermal/auto_tune.h @@ -0,0 +1,34 @@ +#ifndef AUTO_TUNE_H_ +#define AUTO_TUNE_H_ +#include +#include +#include "global_state.h" + +typedef struct +{ + float power_limit; + uint16_t fan_limit; + float step_volt; + float step_freq_rampup; + float step_freq; + float autotune_step_frequency; + uint8_t autotune_read_tick; + uint16_t max_voltage_asic; + uint16_t max_frequency_asic; + uint8_t max_temp_asic; + uint16_t max_temp_vr; + float frequency; + float voltage; + bool auto_tune_hashrate; + float overshot_power_limit; + uint16_t overshot_fanspeed; +} auto_tune_settings; + +extern auto_tune_settings AUTO_TUNE; +void auto_tune_init(GlobalState * gs); +void auto_tune(); +float auto_tune_get_frequency(); +float auto_tune_get_voltage(); +bool auto_tune_get_auto_tune_hashrate(); +void auto_tune_set_auto_tune_hashrate(bool enable); +#endif \ No newline at end of file diff --git a/readme.md b/readme.md index 219d545b8..192645a78 100755 --- a/readme.md +++ b/readme.md @@ -60,6 +60,7 @@ 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/system/autotune` Get Autotune settings information **POST** @@ -67,6 +68,7 @@ Available API endpoints: * `/api/system/identify` Identify the device * `/api/system/OTA` Update system firmware * `/api/system/OTAWWW` Update AxeOS +* `/api/system/autotune` Update Autotune settings **PATCH**