Skip to content

Complete asic-rs integration: categories, power-aware polling, VNish controls (BETA), 4-field temps, safety - #607

Open
pos-ei-don wants to merge 2 commits into
Schnitzel:rewrite-to-asic-rsfrom
pos-ei-don:pr-full-integration
Open

Complete asic-rs integration: categories, power-aware polling, VNish controls (BETA), 4-field temps, safety#607
pos-ei-don wants to merge 2 commits into
Schnitzel:rewrite-to-asic-rsfrom
pos-ei-don:pr-full-integration

Conversation

@pos-ei-don

@pos-ei-don pos-ei-don commented Jun 19, 2026

Copy link
Copy Markdown

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.2 today 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

  • Offline resilience — load the entry from a cached device profile when the miner is unreachable at startup (entities go unavailable instead of failing setup). (= feat: offline resilience — load entry from a cached device profile when the miner is unreachable at startup #604)
  • Options flow — configurable scan interval (= feat: options flow with a configurable scan interval #605), 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 (unknown/unavailable doesn't).
  • 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.
  • Meaningful icons for sensors that default to the generic icon. (= feat: meaningful icons for sensors that default to the generic icon #606)
  • VNish preset/throttle controls — BETA REST shim (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.

…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.
@pos-ei-don

Copy link
Copy Markdown
Author

@b-rowan heads-up — I opened this against your rewrite-to-asic-rs branch (didn't want it to slip by unnoticed). It's the full working integration I've been running; I'd really value your steer on whether you want it wholesale or split a particular way. No rush, whenever you get a chance.

@pos-ei-don

Copy link
Copy Markdown
Author

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 — v2.0.0-alpha22 on pos-ei-don/hass-miner (HACS custom repo). Happy for you to run it and tell me what feels off.

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)
@pos-ei-don

Copy link
Copy Markdown
Author

One addition on top — I pushed a small standalone commit fixing an over-eager mining restart in the VNish preset path (apply_preset).

Why: the restart_required field in the POST /api/v1/settings response is the device's standing flag, not a per-change verdict — a preceding throttle change sets it sticky-true. So apply_preset was firing /mining/restart after preset changes that don't actually need one.

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 select_option → chain freq 445→430 MHz adopted within ~30 s, miner_state stayed mining, miner_state_time climbed monotonically (no re-init), restart_required stayed false throughout. Reverting to 4400 behaved the same. (Raw-API cross-checks earlier confirmed the throttle-vs-preset flag behaviour.)

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?

@b-rowan

b-rowan commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

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 rewrite-to-asic-rs branch?

@pos-ei-don

Copy link
Copy Markdown
Author

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 pos-ei-don/hass-miner (HACS) — install the alpha, run it live, and treat this PR as something to test or toss. If it's not the direction you want, just close it, no hard feelings at all.

There are also a couple of small ones still open on the lib side whenever you get to them (256foundation/asic-rs#285 per-board chip temperature, #286 the 4-field temperature model), and I've a bit more integration work staged behind those — holding it until the entity-naming structure settles (my question in b-rowan/hass-asic-rs#1) so it builds on your direction. Going forward I'm glad to keep things as focused, individual PRs — that worked great for the pieces you just merged. No rush on any of it.

@b-rowan

b-rowan commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

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 b-rowan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can drop this with 0.7.0, use the built in password.

Comment on lines +58 to +68
# 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop with 0.7.0.

Comment on lines +252 to +270
# 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
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop with 0.7.0

Comment on lines +311 to +323
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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop with 0.7.0.

Comment on lines +49 to +84
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()


Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop with 0.7.0.

Comment on lines +103 to +105
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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fluid_temperature is present as the passive environment temperature on some air cooled miners, not hydro only.

Comment on lines +639 to +642
# 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop with 0.7.0.

"scan_interval": "Update interval (seconds)",
"power_entity": "Power switch entity (optional)",
"boot_timeout": "Boot timeout (seconds)",
"password": "Firmware web password (BETA, for VNish)"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop with 0.7.0.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop with 0.7.0. These changes should come from asic-rs.

@pos-ei-don

pos-ei-don commented Jun 22, 2026

Copy link
Copy Markdown
Author

That's great news — native VNish tuning in 0.7.0 is exactly what our vnish.py was a placeholder for (it's literally commented as a "BETA shim until asic-rs supports VNish writes"). Happy to rework this onto it and drop the custom REST shim.

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 rewrite-to-asic-rs and conflict-free.

@pos-ei-don

Copy link
Copy Markdown
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants