Guarded Bitaxe controller that polls AxeOS and adjusts frequency, core voltage, and fan settings in small steps. It is designed to keep the miner inside explicit thermal and electrical limits first, then push upward only when there is headroom.
Two modes are supported:
rules: local control loop with no external AI dependencyopenai: asks an LLM for the next action, but still enforces the same hard safety rails
The controller can tune in two styles:
- custom numeric steps via
BITAXE_FREQ_STEPandBITAXE_VOLTAGE_STEP - ASIC option stepping via
BITAXE_USE_ASIC_OPTIONS=true
It also keeps a local learning file. The learning layer records observed frequency/core-voltage pairs, marks them stable or unstable against your guardrails, scores them using 10-minute hashrate plus efficiency, remembers the best safe setting, and avoids retrying settings that repeatedly failed.
This controller targets the AxeOS API exposed by bitaxeorg/ESP-Miner:
GET /api/system/infoGET /api/system/asicPATCH /api/system
The upstream ESP-Miner README documents those endpoints and points to main/http_server/openapi.yaml for the full schema.
This is not an unrestricted autotuner.
- It only changes one step at a time.
- It enforces min/max frequency and voltage.
- It clamps operator and AI tuning rails to code-level absolute caps.
- It applies emergency rollback if thermal limits are exceeded.
- It raises fan speed before pushing performance.
- It waits between tuning steps so the miner can settle.
- It learns only from settings that remain inside your configured temperature, power, error-rate, input-voltage, and domain-spread limits.
- It can adapt its cooldown: faster when a setting has stable history and plenty of headroom, slower near heat, power, error, or domain-spread limits.
You should keep BITAXE_DRY_RUN=true until you confirm the reported field names and values match your firmware.
- Copy
.env.exampleto.env. - Set
BITAXE_URLto your miner, for examplehttp://192.168.1.50. - Start with conservative limits for your specific board and cooling setup.
- Run one dry-run pass:
set -a
source .env
set +a
python3 controller.py --once- If the state looks correct, keep dry-run on for a few loop cycles:
set -a
source .env
set +a
python3 controller.py- Only then set
BITAXE_DRY_RUN=false.
Both services run on the machine where you install them, not on the Bitaxe itself.
bitaxe-agent.service: the controller loop that polls and tunes the minerbitaxe-agent-ui.service: the local dashboard on port8787by default
The checked-in service files are examples and expect:
- project path:
/home/x/git/bitaxe_agent - env file:
/home/x/git/bitaxe_agent/.env
Portable install for the current checkout:
chmod +x install_linux.sh
./install_linux.sh
sudo systemctl status bitaxe-agent
sudo systemctl status bitaxe-agent-uiAfter that, open http://YOUR-HOST-IP:8787/ in a browser on your LAN.
The installer generates systemd units for the current checkout path, so the folder can be named bitaxe_agent, Bitaxe_agent, or anything else. It also creates local status.json and learning.json files if they do not exist.
The dashboard can monitor several miners at once by reading their local status files. Keep one controller process per miner so each device has its own guardrails, learning file, and status file.
- Copy
swarm.example.jsontoswarm.json. - Add one entry per miner with a unique
status_file. - Run a separate controller service for each miner, each with its own
.envor environment file.
Example swarm entry:
{
"id": "garage",
"name": "Garage Bitaxe",
"url": "http://192.168.1.50",
"api_profile": "axeos",
"status_file": "status-garage.json"
}Supported miner profiles are:
axeos: AxeOS / ESP-Miner compatible Bitaxe devicesgeneric-json: read-compatible HTTP JSON miners where paths are configured manuallynerdminerandesp32: ESP32/NerdMiner monitoring profiles. Stock NerdMiner_v2 currently uses WiFiManager for setup and does not publish a full live telemetry API, so the dashboard can show that the device is reachable and will normalize JSON stats if custom firmware exposes/api/status,/status,/api, or/.futurebitandbraiins: placeholders for configured-path monitoring; write actions still require compatible fields
For non-AxeOS miners, start with BITAXE_DRY_RUN=true, set MINER_INFO_PATH, MINER_ASIC_PATH, and MINER_SETTINGS_PATH, then verify the dashboard fields before allowing live control.
For NerdMiner serial logs in the dashboard, connect the ESP32 by USB and make sure the service user can read the serial port:
sudo usermod -aG dialout $USERLog out and back in, then restart bitaxe-agent-ui. If auto-detect picks the wrong port, set NERDMINER_SERIAL_PORT=/dev/ttyUSB0 in .env.
Stock NerdMiner_v2 does not expose the config/status API that this dashboard needs for live pool, Wi-Fi, and wallet changes. For ESP32 boards without SD card access or a usable WiFiManager portal:
- Clone NerdMiner_v2 beside this project or set
NERDMINER_ROOT=/path/to/NerdMiner_v2in.env. - Open the dashboard, go to NerdMiner Tools, and enter the ESP32 Wi-Fi, pool, wallet, and optional device URL.
- Click Build Config Into Firmware. This installs the API patch and writes a private
src/config_api_local.hfile inside your local NerdMiner_v2 workspace. - Rebuild and flash the NerdMiner_v2 PlatformIO environment for your board.
- After the ESP32 boots on Wi-Fi, use Apply to Patched ESP32 for future live config changes.
The generated config_api_local.h may contain Wi-Fi/password/wallet values. It is written outside this repo and added to the local NerdMiner_v2 git exclude file when possible. Do not commit that generated file.
Patched firmware endpoints:
GET /api/status: simple live NerdMiner telemetry for the fleet dashboard.GET /api/config: current pool/wallet settings without exposing passwords.POST /api/config: updates Wi-Fi, pool, wallet, timezone, and stats settings, then restarts the ESP32 by default.
If a miner accepts different setting keys, map them in .env:
MINER_FREQUENCY_FIELD=frequency
MINER_VOLTAGE_FIELD=coreVoltage
MINER_FAN_SPEED_FIELD=fanspeed
MINER_AUTO_FAN_FIELD=autofanspeedThe parser already accepts several common telemetry names for temperature, frequency, voltage, hashrate, fan, power, and domain data. Write control is still guarded by the same min/max rails and dry-run switch.
The dashboard includes a Check Updates button that compares the local checkout with the configured Git upstream. It does not pull or restart automatically. To update manually:
cd ~/git/bitaxe_agent
git pull
sudo systemctl restart bitaxe-agent
sudo systemctl restart bitaxe-agent-uiSet:
BITAXE_MODE=openai- an AI API key in your private
.envfile AI_MODEL=gpt-4.1-minior another Responses API model available to your account
The model does not get direct authority over the miner. It only proposes the next action, and the controller clamps that action to your configured limits. Before calling OpenAI, the controller still runs the local safety guard; emergency thermal, power, error-rate, input-voltage, and domain-instability rollback decisions bypass AI advice.
Example:
BITAXE_MODE=openai
AI_MODEL=gpt-4.1-miniThis project calls the OpenAI Responses API directly at https://api.openai.com/v1/responses. If you later want a fuller OpenAI Agents SDK integration, keep the same safety split: expose read-only telemetry and a single guarded "propose action" tool, while controller.py remains the only code allowed to write to /api/system.
These values are in .env.example and windows.env.example:
BITAXE_LEARNING_ENABLED=true
BITAXE_LEARNING_MIN_SAMPLES=3
BITAXE_LEARNING_BAD_LIMIT=2
BITAXE_LEARNING_RESTORE_MARGIN=0.03
BITAXE_LEARNING_EFFICIENCY_WEIGHT=0.25
BITAXE_ADAPTIVE_COOLDOWN_ENABLED=true
BITAXE_ADAPTIVE_MIN_COOLDOWN_SECONDS=45
BITAXE_ADAPTIVE_MAX_COOLDOWN_SECONDS=240
BITAXE_ADAPTIVE_STABLE_SAMPLES=8
BITAXE_LEARNING_FILE=learning.jsonBITAXE_LEARNING_MIN_SAMPLES: how many stable samples a setting needs before it can become the best-known target.BITAXE_LEARNING_BAD_LIMIT: how many unstable samples make an unproven candidate blocked.BITAXE_LEARNING_RESTORE_MARGIN: if current hashrate is worse than the best stable setting by this fraction, the controller steps back toward the best known safe pair.BITAXE_LEARNING_EFFICIENCY_WEIGHT: how much GH/W contributes to the learned performance score alongside stable 10-minute hashrate.BITAXE_ADAPTIVE_MIN_COOLDOWN_SECONDS: fastest climb interval when the current setting has stable history and clean headroom.BITAXE_ADAPTIVE_MAX_COOLDOWN_SECONDS: slowest climb interval near safety limits or after unstable samples.BITAXE_ADAPTIVE_STABLE_SAMPLES: stable samples required before the controller is allowed to use the fastest climb interval.BITAXE_LEARNING_FILE: local persistent learning database.
Hard caps are also configured, but the Python controller still enforces conservative built-in ceilings even if .env is set higher:
BITAXE_ABSOLUTE_MAX_FREQUENCY=625
BITAXE_ABSOLUTE_MAX_VOLTAGE=1150
BITAXE_ABSOLUTE_MAX_POWER_W=18
BITAXE_CLIMB_POWER_RATIO=0.90
BITAXE_ABSOLUTE_MAX_EMERGENCY_TEMP_C=70
BITAXE_ABSOLUTE_MAX_VR_TEMP_C=75The learning score is risk-adjusted. It rewards stable 10-minute hashrate and GH/W, then subtracts penalties for high power, high fan, high temperature, error rate, and domain imbalance.
BITAXE_CLIMB_POWER_RATIO controls how much of the configured power budget may be used before the controller is allowed to raise frequency. For example, BITAXE_MAX_POWER_W=17.5 and BITAXE_CLIMB_POWER_RATIO=0.95 gives a climb gate of 16.625W.
These are starting points, not promises. Cooling, PSU quality, ambient temperature, and board silicon all matter.
| Profile | Max frequency | Max voltage | Cool temp | Max power | Cooldown |
|---|---|---|---|---|---|
| Cool | 525 MHz | 1060 mV | 61C | 15.5 W | 240 s |
| Balanced | 535 MHz | 1090 mV | 63C | 16.5 W | 180 s |
| Performance | 575 MHz | 1125 mV | 64C | 17.5 W | 120 s |
The included .env.example defaults to the balanced profile with slightly slower domain-spread confirmation, which is a better daily baseline after an overnight run than pushing straight to the highest observed hashrate. The dashboard preset buttons can apply these values to .env; restart bitaxe-agent after saving.
The private .env file is ignored by Git. Runtime status.json is also generated locally and now redacts common sensitive raw telemetry fields such as Wi-Fi identifiers, MAC/IP values, pool usernames, stratum URLs, scripts, certificates, and coinbase output data before the dashboard exposes the raw status panel.
- Different AxeOS versions may expose slightly different JSON field names.
controller.pyalready checks several common variants, but you should verify one sample response from your device. - This project is preconfigured for an AxeOS-compatible Bitaxe profile. Replace the sample miner URL and guardrails with values that match your own device before live control.
- The current write mapping uses
frequency,coreVoltage,fanspeed, andautofanspeedto match that firmware. - The UI writes safe config edits back to
.env. Restartbitaxe-agentafter saving changes so the controller reloads them. - The safest production pattern is to run this on the same LAN as the miner, behind your firewall, not exposed to the internet.
If this project helps you keep your miner cooler, safer, or just a little less dramatic, you can support it here:
- ESP-Miner README: https://github.com/bitaxeorg/ESP-Miner
- AxeOS API/OpenAPI spec: https://raw.githubusercontent.com/bitaxeorg/ESP-Miner/master/main/http_server/openapi.yaml
- OpenAI Responses API: https://platform.openai.com/docs/api-reference/responses
- OpenAI Structured Outputs: https://platform.openai.com/docs/guides/structured-outputs
- OpenAI Agents SDK: https://platform.openai.com/docs/guides/agents-sdk/