fix(ams): stop showing the humidity drop index as a percentage (issue #3140)

Bambu sends two humidity fields that are not the same quantity.
humidity_raw is relative humidity in percent; humidity is a 1-5 drop
index, and it runs the other way -- OpenBambuAPI's push_info sample
pairs "humidity:30%" with "humidity_idx:4", so a high index means dry
where a high percentage means wet.

Four call sites used the index whenever no percentage arrived. A unit
sending only the index therefore rendered as "2%" in the green band
while being the second-wettest of the five steps, charted an average of
index values as a percentage, and sat under every humidity threshold
forever, since no index can reach one -- the alarm and auto-drying could
not fire for such a unit at all.

- utils/ams_humidity: one leaf helper, a percentage or None. The index
  is never converted; None is what every caller already handles.
- routes/printers, printer_manager, print_scheduler, main, bambu_mqtt:
  all five readings go through it, so the card, the websocket, the
  chart, the alarm and auto-drying cannot answer differently.
- main: a unit that reports the index and no usable percentage says so
  once per unit in the log, with its firmware versions requested. No
  supported printer is known to do this, and "known" is doing work
  there -- the alternative is a card that goes blank with no trace.

Three faults found while checking what else those paths touched:

- main: humidity_raw=float(x) if x else None stored NULL for a numeric
  0% while writing 0.0 to humidity on the same row.
- main: that same expression was unguarded, unlike the parse above it,
  so a non-numeric humidity_raw raised inside record_ams_history and
  aborted the pass for every printer, not just the one that sent it.
- routes/ams_history: the averages were tested for truthiness, so a
  window averaging exactly 0 reported no average while the min and max
  beside it reported 0.0.

An affected unit now reports no humidity rather than a number that means
the opposite: the indicator is hidden, the chart leaves a gap, the alarm
and auto-drying skip the unit. Temperature is untouched. Auto-drying's
outcome is unchanged either way -- an index could never cross the
threshold -- so only the intent moves.

No supported printer is known to be affected; the report came from an
install running X1Plus, which Bambuddy does not support. Verified
against 7927 recorded samples from seven AMS units including an AMS-HT:
not one used the fallback. Two percentages that did fall through to the
index no longer do -- a reading with a decimal point, and "38.0", which
int() rejected.
This commit is contained in:
maziggy
2026-09-24 11:59:30 +02:00
parent 08b4ae992f
commit 6e8d543d2a
10 changed files with 453 additions and 65 deletions
+6 -2
View File
@@ -93,10 +93,14 @@ async def get_ams_history(
],
min_humidity=stats.min_humidity,
max_humidity=stats.max_humidity,
avg_humidity=round(stats.avg_humidity, 1) if stats.avg_humidity else None,
# ``is not None``, not truthiness: an average of exactly 0 is a
# reading, and the min/max beside it would report it while the average
# showed an em dash. AVG over an empty or all-NULL window is the only
# case that has no answer (#3140).
avg_humidity=round(stats.avg_humidity, 1) if stats.avg_humidity is not None else None,
min_temperature=stats.min_temp,
max_temperature=stats.max_temp,
avg_temperature=round(stats.avg_temp, 1) if stats.avg_temp else None,
avg_temperature=round(stats.avg_temp, 1) if stats.avg_temp is not None else None,
)
+6 -16
View File
@@ -90,6 +90,7 @@ from backend.app.services.printer_media import (
)
from backend.app.services.slicer_filament_resolver import _ORCA_PROFILE_ID
from backend.app.services.slot_nozzle import resolve_slot_nozzle
from backend.app.utils.ams_humidity import ams_humidity_percent
from backend.app.utils.filament_ids import filament_id_to_setting_id
from backend.app.utils.filament_types import is_material_name, printer_filament_type
from backend.app.utils.fts_routing import slot_extruder
@@ -586,22 +587,11 @@ async def get_printer_status(
exists=tray_data.get("exists"),
)
)
# Prefer humidity_raw (percentage) over humidity (index 1-5)
# humidity_raw is the actual percentage value from the sensor
humidity_raw = ams_data.get("humidity_raw")
humidity_idx = ams_data.get("humidity")
humidity_value = None
if humidity_raw is not None:
try:
humidity_value = int(humidity_raw)
except (ValueError, TypeError):
pass # Skip unparseable humidity; will try index fallback
if humidity_value is None and humidity_idx is not None:
try:
humidity_value = int(humidity_idx)
except (ValueError, TypeError):
pass # Skip unparseable humidity index; humidity remains None
# Percentage only. The 1-5 ``humidity`` index is never substituted
# for one -- it is inverted, so it would read as the opposite of
# what it means (#3140). See utils/ams_humidity.
humidity_pct = ams_humidity_percent(ams_data)
humidity_value = int(round(humidity_pct)) if humidity_pct is not None else None
# AMS-HT has 1 tray, regular AMS has 4 trays
is_ams_ht = len(trays) == 1