DCENT_OS Off-Grid and Solar Reference: Battery Zones and Inverter Providers
Home » DCENT_OS — Open-Source Antminer Firmware » DCENT_OS Off-Grid and Solar Reference: Battery Zones and Inverter Providers
DCENT_OS carries a voltage-driven off-grid controller and a solar-telemetry client with seven providers. Both are real Rust in the public release, and neither appears in any configuration file the project ships. This page documents exactly that: what the code does, what the daemon will parse, what you would have to add to the miner to use it, and where the evidence stops.
How to read this page
Nothing on this page has been verified against a miner on a bench. Every statement here is a statement about the published GPL-3.0 source, or about the configuration file that ships with it. Each claim carries one of four grades:
- Verified (in source) — read in the public release tree; the file and line are printed beside it.
- Verified (shipped config) — a real key and value in the
dcentrald.tomlthat ships in the repository. - Inferred — arithmetic, or a consequence that follows from Verified facts. Labelled, never dressed as an observation.
- Unknown — the source does not say, or two files say different things. We box it rather than pick one.
There is deliberately no Supported grade on this page. That grade would mean tested on hardware, and we have not done that for anything described here. When we have, this page changes.
Read this before the tables — the honest state of play
We searched every TOML file in the release tree. There is no [power.offgrid]
section and no [solar] section in any of them, and the daemon's own default
for off-grid is None (dcentrald/src/config.rs:5479). Compare the LED,
MQTT and webhook features, which all ship with a worked example in
dcentrald.toml.
So the key names and defaults below are the schema the daemon parses, read out
of config.rs — not a configuration we have run, and not a configuration the project
publishes an example of. We have not connected DCENT_OS to a battery bank, an inverter, or a
gateway. If you are planning a build around this, plan around the source being real and the
integration being unproven.
Before anything else: this feature needs a part your miner does not have
Off-grid control reads DC bus voltage, and no stock Antminer wires its DC bus into an
ADC channel. You have to add the sensing yourself. The daemon accepts exactly three
backends (dcentrald-hal/src/adc.rs:28-63): a TI INA226 I2C power
monitor — the one the source calls "recommended for off-grid", defaulting to I2C bus 0, address
0x40, a 10 mΩ shunt and a 1.0 divider (adc.rs:65-76); a
Linux IIO sysfs ADC channel whose path you name; or a
simulated source, which is for testing and reports numbers nothing measured.
Setting [power.offgrid] enabled = true without an adc backend
does not fall back to normal mining — it stops the miner hashing. The daemon detects the
missing backend at start-up, puts the controller straight into its sensor-fault state and puts the
curtailment controller into sleep (dcentrald/src/daemon.rs:6466-6484). Sensor-fault
sets target_freq_mhz = 0 (dcentrald-thermal/src/offgrid.rs:482-485), and
the curtailment sleep state is documented as "hash boards de-energized, fans dropped to the
controller's sleep_fan_pwm", about 25 W
(dcentrald/src/config.rs:5296-5300).
The firmware is right to do this. Without a voltage reading there is no deep-discharge protection, and the alternative — carrying on at full frequency against an unknown battery — is the dangerous one. But it means the config key below is not a switch you can try. The daemon says so itself, in the telemetry it emits at that moment:
"Off-grid mode requires an explicit ADC backend. Configure INA226, Sysfs ADC, or an intentional simulated source before enabling battery protection."
Verified (in source) — adc.rs:28-63, :65-76;
daemon.rs:6466-6484; offgrid.rs:482-485;
config.rs:5296-5300. Read on a bench: no. This is what the code is written to do.
Credit where it is due
The off-grid controller's own header names its predecessor: "Inspired by Gridless Compute's
Jua-Kali project (Go-based battery voltage control loop for solar-powered mining in Africa).
Reimplemented from scratch in Rust with tighter DCENT_OS integration and 5-zone state machine."
(dcentrald-thermal/src/offgrid.rs:19-21). Gridless solved this problem first, in the
field, where it mattered most. We are standing on their work.
Five voltage zones
The controller reads DC bus voltage and sorts it into one of five zones, then acts. It never jumps: it moves frequency one step at a time.
| Zone | Condition | What the controller does |
|---|---|---|
| Critical | V < critical_v | Target frequency 0, enter deep sleep. This is deep-discharge protection. |
| Low | V < low_v | Ramp frequency down one step. If already asleep, stay asleep until recovery_v. |
| Normal | V <= high_v | Hold. No change. |
| High | V <= full_v | Ramp frequency up one step — the source's comment reads "solar surplus available". |
| Full | above full_v | Mine at the configured maximum frequency. |
Precision point: normal_v is not a threshold
The presets below list a normal_v, but classify_zone() never reads it
— only critical_v, low_v, high_v and full_v
decide a zone (offgrid.rs:390-411). normal_v is the nominal/display
value. The Normal zone is the closed interval from low_v to high_v.
The edges are strict where it matters: a bus sitting at exactly critical_v
is Low, not Critical, because that comparison is <. The repository
pins that boundary in its own test (offgrid.rs:696-718).
Six chemistry presets, plus a custom fallback
| Config string | Label in the firmware | critical | low | normal | high | full | recovery |
|---|---|---|---|---|---|---|---|
lifepo4_48v (default) | LiFePO4 48V (16S) | 40.0 | 47.0 | 51.2 | 53.6 | 54.4 | 49.0 |
lifepo4_24v | LiFePO4 24V (8S) | 20.0 | 23.5 | 25.6 | 26.8 | 27.2 | 24.5 |
lifepo4_12v | LiFePO4 12V (4S) | 10.0 | 11.8 | 12.8 | 13.4 | 13.6 | 12.2 |
lead_acid_48v | Lead Acid 48V (24 cells) | 42.0 | 46.0 | 48.0 | 50.4 | 57.6 | 48.0 |
lead_acid_24v | Lead Acid 24V (12 cells) | 21.0 | 23.0 | 24.0 | 25.2 | 28.8 | 24.0 |
lead_acid_12v | Lead Acid 12V (6 cells) | 10.5 | 11.5 | 12.0 | 12.6 | 14.4 | 12.0 |
custom (not a chemistry — the fallback slot) | Custom | 10.0 | 11.5 | 12.0 | 13.0 | 14.0 | 12.0 |
The 48 V LiFePO4 row carries its per-cell derivation in the source comments
(battery.rs:52-57): 40.0 V is 2.50 V/cell across 16S, 51.2 V is the 3.20 V/cell nominal,
54.4 V is 3.40 V/cell near absorption. The module names its chemistry references
(battery.rs:9-11): LiFePO4 practice from BattleBorn and EG4, lead-acid from Trojan and
Crown.
A typo in the preset name does not raise an error
Verified (in source): the daemon matches six exact lowercase strings and
anything else falls through to Custom without warning
(dcentrald/src/config.rs:546-554). Custom's thresholds are the 12 V set,
and they are internally consistent — so every validation rule below still passes.
Inferred, and stated as inference: a 48 V bank configured with a mistyped
preset name would then sit permanently above full_v, classify as Full, and never
curtail. Unknown: whether the dashboard or the REST layer rejects the typo before
it reaches the daemon — we did not resolve that, so we are not telling you this happens,
only that the fallback is silent and the six accepted strings are the ones in the table above.
The controller loop
| Parameter | Value | Where |
|---|---|---|
| Tick interval | 2000 ms (accepted range 500 – 600000) | config.rs:5445, :605 |
| Voltage filter | exponential moving average, alpha 0.3 | offgrid.rs:216 |
| History for rate-of-change | last 30 samples | offgrid.rs:228-230 |
| Frequency step | 25 MHz (floored at 5) | config.rs:5439, offgrid.rs:180 |
| Minimum frequency | 200 MHz (floored at 100, and clamped down to your ceiling) | config.rs:5442, offgrid.rs:179 |
| Maximum frequency | your mining target frequency | offgrid.rs:160 |
Waking is gentle by design: whichever way the bus recovers, the controller restarts at the
minimum frequency and ramps from there rather than snapping back to the previous target
(offgrid.rs:243, :272, :302, :325).
What happens when the voltage sensor lies
This is the best-engineered part of the module and it is worth understanding, because the obvious implementation fails in the dangerous direction.
If an ADC returns a non-finite reading — NaN, or an infinity — then every <
comparison against it is false, so a naive threshold ladder falls through to its last branch. Here
that last branch is Full, which would ramp frequency up on a broken sensor
and switch off the very protection the controller exists to provide. A NaN would also poison an
exponential moving average permanently, staying stuck even after the sensor recovers.
DCENT_OS guards this twice, and the source says why in both places (Verified (in source)):
tick()catches a non-finite reading before it reaches the filter or the classifier, enters the same sensor-fault state described in the hardware box above, and returns Sleep (offgrid.rs:198-209).classify_zone()independently maps any non-finite voltage to Critical, so the fail-open "can't be reintroduced by a caller that forgets to pre-filter" (offgrid.rs:391-399).
Both are pinned by a test covering NaN and both infinities (offgrid.rs:729-749).
A sensor that fails while running therefore lands in exactly the same place as a sensor that was
never fitted: frequency target zero, boards asleep, waiting for a real reading.
The daemon refuses to start on an unsafe off-grid configuration
With off-grid enabled, these are checked at load and each one aborts startup
(Verified (in source) — dcentrald/src/config.rs:542-614):
| Rule | Why, in the source's words |
|---|---|
critical_v < low_v < high_v < full_v | ordering must be sane (:572-592) |
recovery_v > critical_v | "to prevent permanent sleep" (:594) |
critical_v >= 5.0 | "no battery can safely discharge below 5V" (:597) |
500 <= loop_interval_ms <= 600000 | the value is passed straight to a timer that panics on zero, and the release build aborts on panic (:599-613) |
Note what is not on that list: a missing adc backend does not abort
start-up. The daemon boots and goes to sleep instead — see the hardware box above.
The battery percentage is a straight line, and you should treat it as one
Verified (formula) · Unknown (accuracy)
The state-of-charge figure in the off-grid telemetry is a linear interpolation between
critical_v and full_v:
SoC% = (V − critical_v) / (full_v − critical_v) × 100, clamped to
0–100 (offgrid.rs:414-421). It is not coulomb counting and it is not a chemistry
curve.
That matters most on LiFePO4, whose discharge curve is famously flat through the middle of its range — a small voltage change covers a large slice of real capacity. The field is named "estimated" in the firmware and we are repeating the word deliberately. Use it as a coarse indicator; do not size a bank against it.
Solar: seven providers, graded by the firmware itself
The interesting thing here is not the provider list. It is that DCENT_OS ships a maturity grade per provider, in code, and one of them is deliberately marked down.
| Provider string | Stage the firmware declares | What it means here |
|---|---|---|
manual | live | Numbers you type in. Explicitly not telemetry-backed (lib.rs:820-822). |
victron | live | A backend exists in the dispatcher. |
bridge | live | Generic normalised payload contract. |
enphase | live | A backend exists in the dispatcher. |
solaredge | live | Cloud endpoints, including current power flow and overview. |
tesla | live | The local Gateway API — see below. |
ecoflow | limited | The firmware narrows its own claim — see below. |
Tesla — say exactly what it is
DCENT_OS implements a client for the local Tesla Gateway HTTP API. It requires
an http:// or https:// gateway address, and reads
/api/meters/aggregates and /api/system_status/soe
(solar.rs:1109-1136), mapping solar.instant_power,
load.instant_power and site.instant_power to production, consumption and
net grid (solar.rs:486-488). If the state-of-energy endpoint is unavailable because of
authentication, it degrades to "no battery SoC" rather than failing
(solar.rs:1123-1133).
That is the whole claim. We have read the client. We have never pointed it at a physical Powerwall. "DCENT_OS supports Tesla Powerwall" is a sentence we are not entitled to write, and we are not writing it.
EcoFlow — the firmware's own words, unedited
The stage reason is a string in the source, and it is a better disclaimer than anything we would
draft (lib.rs:799):
"EcoFlow support is intentionally narrow: DCENT_OS only accepts validated, normalized EcoFlow HTTP payloads that can be mapped safely to production, consumption or net-grid, optional battery SoC, and optional sample age/timestamp metadata. It does not claim direct EcoFlow cloud/local authentication coverage across device families."
Three payload shapes are accepted — bridge-contract, site-summary and
power-summary (lib.rs:802-806, field patterns at
solar.rs:18-76). The transport resolves by endpoint scheme to an HTTP bridge, an MQTT
bridge, or ecoflow-unsupported (lib.rs:846-869).
Two more things the solar layer tracks that are unusual and worth knowing about:
- It records which fields it recognised. A verification sample carries
matched_fields, alongside sample age, a staleness flag, consecutive failures and the last success timestamp (lib.rs:741-756). That is how you debug a bridge without guessing what the far end sent. - Commissioning is a state, not a boolean. Exactly four values:
pending_restart,manual_runtime,telemetry_live,telemetry_degraded(lib.rs:824-844).
The schema the daemon parses
These are field names and defaults read out of config.rs. There is no shipped
example to copy, and we have not run either section. They are printed so you can read the
source with the same map we used, not as a configuration to paste — and
[power.offgrid] enabled = true in particular is not a switch you can safely try without
first reading the hardware box at the top of this page.
[power.offgrid] — dcentrald/src/config.rs:5245-5285:
enabled (false) · battery_preset ("lifepo4_48v") ·
adc (no default — absent unless you supply it. This is the key that decides
whether the feature can run at all; see the hardware box above) ·
freq_step_mhz (25) · min_frequency_mhz (200) ·
loop_interval_ms (2000) · custom_critical_v · custom_low_v ·
custom_high_v · custom_full_v · custom_recovery_v.
[solar] — dcentrald/src/config.rs:5398-5434:
enabled (false) · inverter_brand ("manual" — this is the provider string
from the table above) · api_endpoint · api_key ·
solar_only_mode (false) · base_load_watts (500) ·
battery_threshold_pct (20) · battery_wake_hysteresis_pct (3) ·
provider_max_sample_age_ms (60000) ·
provider_failure_hysteresis_samples (1) · hybrid_import_deadband_watts (75)
· manual_production_watts (0) · manual_site_load_watts (0) ·
manual_battery_soc_pct.
Read this as source, not as a datasheet. These values are what the firmware is written to do. We have not confirmed them on a miner.
Planning an off-grid or solar build
This page is about the firmware. For the build itself, D-Central's off-grid and solar material is the better starting point:
- Off-grid Bitcoin mining: the sovereign home miner's guide — power, connectivity and independence.
- Run your miner off solar and battery: state-of-charge-driven mining — the practice this controller automates.
- Off-grid and solar-direct mining calculator — size the array and the bank.
- Off-grid power components — batteries, inverters, charge controllers, generators.
What this page does not tell you
- How to turn either feature on. No shipped configuration example exists, and we will not invent a procedure. When there is one, this page gains a how-to link.
- Whether it works. No battery, no inverter, no gateway has been connected to DCENT_OS by us.
- Which miners this applies to. Off-grid frequency control presumes a platform DCENT_OS can already drive; see hardware status for which lanes have evidence.
Source of record: the public GPL-3.0 release tree at
github.com/DCentralTech/DCENT_OS,
commit 6f61603 (2026-08-16). Line references are to that commit. Code moves; if a line
number has drifted, the file and the symbol name still find it. Reviewed 2026-08-19.
Companion tool — DCENT_Toolbox: Audit an off-grid miner read-only before changing anything.
Related products, repair, and setup paths
- how D-Central diagnoses ASIC repairs
- ASIC troubleshooting library
- ASIC manuals and repair guides
- replacement hashboards
- ASIC control boards
- ASIC power supplies
- S19 family replacement hashboard
- C52 replacement control board
- APW12 S19 power supply
- compare specs in the ASIC miner database
- compare ASIC miner specs
- ASIC miner database
- ASIC repair services
- Antminer S19 specs and profitability
- buy a tested Antminer S19
- Antminer S19 maintenance guide
- Antminer S19 repair service
- Antminer S21 specs
- Bitmain Antminer S21
- Antminer S21 maintenance guide
- BM1370BC S21 Pro chip
- Antminer S9 specs
- Bitmain Antminer S9
- Antminer S9 maintenance guide
- S9 hashboard repair parts bundle
Last reviewed August 19, 2026.
