Complete asic-rs integration: categories, power-aware polling, VNish controls (BETA), 4-field temps, safety - #607
Conversation
…ntrols, 4-field temps, safety) Consolidated, live-tested integration on top of rewrite-to-asic-rs. Lib-gated and defensive throughout, so it runs on pyasic-rs 0.6.2 and lights up extra features as the corresponding lib changes land: - Offline resilience: load the entry from a cached device profile when the miner is unreachable at startup (entities show unavailable instead of failing setup). - Options flow: configurable scan interval, sensor-category toggles, only-type-relevant filter, power entity + boot timeout, firmware web password. - Power-aware polling: pause polling while a configured power entity is off; fast boot loop + boot-timeout alarm on power-on; only an explicit "off" suppresses polling. - Sensor categories: group sensors (miner summary / safety / board temps / board perf / fans) and only create the selected groups. - Safety: problem flag + human-readable alarm reason (lib-message + thermal-limit aware), cold/hot limit diagnostics, "tuning in progress" / "powered off" states. - 4-field temperatures: per-board inlet/outlet chip + miner-level coolant inlet/outlet. - VNish preset/throttle controls (BETA, REST shim in vnish.py) — to be dropped once asic-rs gains native VNish writes. - Meaningful icons for sensors that would otherwise fall back to the generic icon.
|
@b-rowan heads-up — I opened this against your |
|
If it's easier to get a feel for this than reading a 1.6k-line diff: there's an installable build of exactly this work as a pre-release — One caveat on hardware: the VNish-specific bits (preset/throttle controls, the device-read safety limits) only really light up on a VNish-flashed miner — the BOS path and the overall architecture/UX (sensor categories, capability gating, safety alarm) you'd see on your own gear. What do you run day to day? Knowing that helps me line up what's worth keeping validated vs. where I'm the only one with the hardware. |
A VNish preset change is applied live -- frequency, power and hashrate adopt the new preset within ~30s with no reboot (miner_state stays "mining", miner_state_time keeps climbing, no re-init). The restart_required field in the POST /settings response is the device's standing flag, not a per-change verdict: a preceding throttle change (which itself needs no restart) sets it sticky-true, so apply_preset was triggering unnecessary /mining/restart cycles. Drop the auto-restart; the preset takes effect on its own. Live-verified 2026-06-19 on an S19 Pro Hydro (VNish): at throttle 100, preset 3495->4400 moved chain freq 396->446 MHz and hashrate 143->160 TH/s within 30s with restart_required staying false throughout. (cherry picked from commit 3745a87)
|
One addition on top — I pushed a small standalone commit fixing an over-eager mining restart in the VNish preset path ( Why: the What: drop the auto-restart after a preset change — VNish applies a preset change live. How tested (S19 Pro Hydro, VNish, live): preset 4400→4250 via the integration's I kept it as its own commit so it's trivial to lift out — would you prefer this as a separate PR, or folded in here? |
|
Merged everything from the other PRs in, I like the separate ideas. What is left after those to merge I know I have some extra stuff yet to do on the asic-rs side, but then I imagine its pretty close on the |
|
Thanks for merging the split-out pieces — really nice to see them land! This branch will show conflicts now that those are in; I'm happy to rebase it clean the moment you want to dig in — just didn't want to keep churning a big rebase if it's not on your path yet. What's left here is the bigger, interlocking feature set that makes the integration work end-to-end: the power-level / preset control with the self-learning efficiency map, plus the safety-alarm and sensor-category model. Worth saying plainly — this isn't a greenfield change set I can cleanly slice up: it's the integration we run in production on our own miners every day, so the parts are entangled the way a live system is, not independent demos. Easiest way to get a feel for it without reading a big diff: that exact running build is up as a pre-release on There are also a couple of small ones still open on the lib side whenever you get to them ( |
|
Released asic-rs 0.7.0, has the vnish tuning support now, can you rework this to use that instead of the custom vnish integration? |
b-rowan
left a comment
There was a problem hiding this comment.
Couple things here -
- All the miner control functionality should come from asic-rs, if we need to add extra functionality or traits there to support new stuff that's fine, but I don't want to add weird patches to add functionality here.
- I would like to see this split up into a few PR's, and maybe after releasing 0.2.0. There are a few sets of changes here, such as adding the messages, adding a new translation set, etc.
Curious if you want these features added prior to merging #601 and releasing 2.0.0, or if they can wait until after?
| "scan_interval": "Update interval (seconds)", | ||
| "power_entity": "Power switch entity (optional)", | ||
| "boot_timeout": "Boot timeout (seconds)", | ||
| "password": "Firmware web password (BETA, for VNish)" |
There was a problem hiding this comment.
Can drop this with 0.7.0, use the built in password.
| # BETA VNish control state (populated only for VNish miners). | ||
| self.is_vnish: bool = False | ||
| self.vnish_presets: list[str] = [] | ||
| # name -> human Select label (tuned hashrate / "(untuned)" marker). | ||
| self.vnish_preset_labels: dict[str, str] = {} | ||
| self.vnish_preset: str | None = None | ||
| self.vnish_throttle: int | None = None | ||
| # VNish's own state verdict (mining / tuning / initializing / stopped …), | ||
| # polled from /summary alongside the throttle. Lets the safety-reason | ||
| # sensor say "tuning in progress" instead of a bare "OK". | ||
| self.vnish_state: str | None = None |
| # BETA: detect VNish firmware so the preset/throttle entities get added. | ||
| session = async_get_clientsession(self.hass) | ||
| self.is_vnish = await vnish.detect_vnish(session, self.ip) | ||
| detailed: list[dict] = [] | ||
| if self.is_vnish and self.password: | ||
| detailed = await vnish.fetch_presets(session, self.ip, self.password) | ||
| if self.is_vnish and not detailed: | ||
| # No live list (no password / fetch failed): fall back to bare names, | ||
| # no tuned/un-tuned label info available offline. | ||
| detailed = [{"name": n} for n in vnish.FALLBACK_PRESETS] | ||
| if self.is_vnish: | ||
| self.vnish_presets = [p["name"] for p in detailed] | ||
| self.vnish_preset_labels = { | ||
| p["name"]: vnish.preset_label( | ||
| p["name"], p.get("pretty"), p.get("status") | ||
| ) | ||
| for p in detailed | ||
| } | ||
|
|
| async def _async_update_vnish(self) -> None: | ||
| """BETA: refresh VNish preset/throttle. Never fails the main update.""" | ||
| session = async_get_clientsession(self.hass) | ||
| try: | ||
| self.vnish_throttle, self.vnish_state = await vnish.fetch_status( | ||
| session, self.ip | ||
| ) | ||
| if self.password: | ||
| self.vnish_preset = await vnish.fetch_current_preset( | ||
| session, self.ip, self.password | ||
| ) | ||
| except Exception as err: # noqa: BLE001 | ||
| _LOGGER.debug("VNish extra-poll failed for %s: %s", self.ip, err) |
| class VnishThrottleNumber(MinerEntity, NumberEntity): | ||
| """[BETA] Set the VNish throttle (percent of full power). | ||
|
|
||
| asic-rs is read-only for VNish power, so this drives the VNish REST API | ||
| directly (see vnish.py). Replace once asic-rs supports VNish writes. | ||
| """ | ||
|
|
||
| _attr_name = "VNish Throttle" | ||
| _attr_icon = "mdi:speedometer-slow" | ||
| _attr_native_unit_of_measurement = PERCENTAGE | ||
| _attr_native_min_value = float(vnish.THROTTLE_MIN) | ||
| _attr_native_max_value = float(vnish.THROTTLE_MAX) | ||
| _attr_native_step = 1.0 | ||
| _attr_mode = NumberMode.SLIDER | ||
|
|
||
| def __init__(self, coordinator: MinerCoordinator) -> None: | ||
| super().__init__(coordinator) | ||
| self._attr_unique_id = f"{self._device_unique_id}_vnish_throttle" | ||
|
|
||
| @property | ||
| def native_value(self) -> float | None: | ||
| return self.coordinator.vnish_throttle | ||
|
|
||
| async def async_set_native_value(self, value: float) -> None: | ||
| session = async_get_clientsession(self.hass) | ||
| ok, msg = await vnish.set_throttle( | ||
| session, self.coordinator.ip, self.coordinator.password, int(value) | ||
| ) | ||
| if ok: | ||
| self.coordinator.vnish_throttle = int(value) | ||
| self.async_write_ha_state() | ||
| else: | ||
| raise RuntimeError(f"VNish throttle {int(value)}% failed: {msg}") | ||
| await self.coordinator.async_request_refresh() | ||
|
|
||
|
|
| def _reports_chip_temp(data: MinerData) -> bool | None: | ||
| """Lib's reports_chip_temperature flag, or None if absent on this lib.""" | ||
| return getattr(getattr(data, "device_info", None), "reports_chip_temperature", None) |
There was a problem hiding this comment.
Not present in device_info.
|
|
||
| # Members of Miner-Summary that only make sense on liquid-cooled miners. When the | ||
| # lib reports cooling we trust it; otherwise the only_available None-gate decides. | ||
| _SUMMARY_HYDRO_ONLY = ("fluid_temperature", "outlet_fluid_temperature") |
There was a problem hiding this comment.
fluid_temperature is present as the passive environment temperature on some air cooled miners, not hydro only.
| # Capability gates (Schicht B / B1), defensive: True/False when the lib knows, | ||
| # None on stock 0.6.2 / when offline so the only_available None-gate decides. | ||
| is_hydro = _cooling_is_hydro(data) if data is not None else None | ||
| reports_chip = _reports_chip_temp(data) if data is not None else None |
| "scan_interval": "Update interval (seconds)", | ||
| "power_entity": "Power switch entity (optional)", | ||
| "boot_timeout": "Boot timeout (seconds)", | ||
| "password": "Firmware web password (BETA, for VNish)" |
There was a problem hiding this comment.
Drop with 0.7.0. These changes should come from asic-rs.
|
That's great news — native VNish tuning in 0.7.0 is exactly what our Plan on my side: pull 0.7.0 onto our own miners first and validate the native VNish tuning live on the S19 Pro Hydro (that it does what the shim did — set power limit / presets / throttle), then swap the integration over to it and update this PR. I'll keep it on |
|
I've started contributing the library pieces back to asic-rs as proper code, the way you asked. First one is up: native throttle — 256foundation/asic-rs#289. Native named-preset listing and the configured thermal-limit fields will follow (the preset side has a design question I'd value your input on). On the integration GUI itself: it now does what I need, so I'm keeping it as a runnable alpha in my fork rather than splitting it into a series of PRs — anyone who wants it can install it, and you're welcome to take any part that's useful. |
This is my complete, live-tested asic-rs integration, offered as one branch so you have the full picture for the rewrite — take it wholesale, or tell me how you'd like it split and I'll carve it up. It's lib-gated and defensive throughout, so it runs on
pyasic-rs==0.6.2today and the extra features light up as the matching lib changes land (set_power_limit#284 is merged; the 4-field temps pair with #286; the capability/message/thermal-limit bits pair with a few small lib PRs I'm sending next).Running live on an S19 Pro Hydro (VNish) and an S19K Pro (BraiinsOS).
On splitting: focused PRs were the original plan — #604/#605/#606 are exactly that. But the remaining features share files heavily, above all
coordinator.py(power-aware polling, offline resilience and the VNish poll all live there together), so cleanly splitting the rest into separate PRs is barely feasible without a lot of churn and rebasing. That's why I'm putting it up consolidated and asking how you'd prefer to handle it, rather than forcing an artificial split.What's in it
unavailableinstead of failing setup). (= feat: offline resilience — load entry from a cached device profile when the miner is unreachable at startup #604)off; fast boot loop + boot-timeout alarm on power-on; only an explicitoffsuppresses polling (unknown/unavailable doesn't).tuning in progress/powered offstates.vnish.py), clearly marked; meant to be dropped once asic-rs gains native VNish writes.On the overlap: this incorporates #604/#605/#606. Happy to keep those three as the small focused PRs and rebuild the rest on top, or to land this and close them — whatever's least work for you.