feat: add histogram_interval setting and update UI with new field.
This commit is contained in:
@@ -307,10 +307,16 @@ producing only ~1071 bytes instead of ~2126.
|
|||||||
|
|
||||||
### SUB 1A — anchor search range
|
### SUB 1A — anchor search range
|
||||||
|
|
||||||
`_decode_compliance_config_into()` locates sample_rate and record_time via the anchor
|
`_decode_compliance_config_into()` locates fields via the **6-byte stable anchor**
|
||||||
`b'\x01\x2c\x00\x00\xbe\x80\x00\x00\x00\x00'`. Search range is `cfg[0:150]`.
|
`b'\xbe\x80\x00\x00\x00\x00'`. Search range is `cfg[0:150]`.
|
||||||
|
|
||||||
Do not narrow this to `cfg[40:100]` — the old range was only accidentally correct because
|
**IMPORTANT — the "10-byte anchor" `\x01\x2c\x00\x00\xbe\x80\x00\x00\x00\x00` is NOT fully constant.**
|
||||||
|
The first 2 bytes (`\x01\x2c` = 300) are the `histogram_interval_sec` field (uint16 BE, seconds) —
|
||||||
|
the value 300 is just the 5-minute default. When histogram interval is set to a different value
|
||||||
|
(e.g. 15min = 0x0384 = `\x03\x84`), those bytes change. Only the 6-byte suffix
|
||||||
|
`\xbe\x80\x00\x00\x00\x00` is truly constant. The code already uses the 6-byte anchor.
|
||||||
|
|
||||||
|
Do not narrow the search range to `cfg[40:100]` — the old range was only accidentally correct because
|
||||||
the orphaned-send bug was prepending a 44-byte spurious header, pushing the anchor from
|
the orphaned-send bug was prepending a 44-byte spurious header, pushing the anchor from
|
||||||
its real position (cfg[11]) into the 40–100 window.
|
its real position (cfg[11]) into the 40–100 window.
|
||||||
|
|
||||||
@@ -360,8 +366,9 @@ Do NOT use fixed absolute offsets for sample_rate or record_time.
|
|||||||
|
|
||||||
| Field | How to find it |
|
| Field | How to find it |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **recording_mode** | **uint8 at anchor − 3 (write payload) / anchor − 4 (read response)** ✅ confirmed 2026-04-20 |
|
| **recording_mode** | **uint8 at anchor − 3 (write) / anchor − 4 (read)** ✅ confirmed 2026-04-20 |
|
||||||
| sample_rate | uint16 BE at anchor − 2 |
|
| sample_rate | uint16 BE at anchor − 2 |
|
||||||
|
| **histogram_interval_sec** | **uint16 BE at anchor − 4 (seconds); same offset in read & write** ✅ confirmed 2026-04-20 |
|
||||||
| record_time | float32 BE at anchor + 10 |
|
| record_time | float32 BE at anchor + 10 |
|
||||||
| trigger_level_geo | float32 BE, located in channel block |
|
| trigger_level_geo | float32 BE, located in channel block |
|
||||||
| alarm_level_geo | float32 BE, adjacent to trigger_level_geo |
|
| alarm_level_geo | float32 BE, adjacent to trigger_level_geo |
|
||||||
@@ -370,7 +377,20 @@ Do NOT use fixed absolute offsets for sample_rate or record_time.
|
|||||||
| setup_name | ASCII, null-padded, in cfg body |
|
| setup_name | ASCII, null-padded, in cfg body |
|
||||||
| project / client / operator / sensor_location | ASCII, label-value pairs |
|
| project / client / operator / sensor_location | ASCII, label-value pairs |
|
||||||
|
|
||||||
Anchor: `b'\x01\x2c\x00\x00\xbe\x80\x00\x00\x00\x00'`, search `cfg[0:150]`
|
**True stable anchor: `b'\xbe\x80\x00\x00\x00\x00'` (6-byte suffix), search `cfg[0:150]`.**
|
||||||
|
The old "10-byte anchor" `b'\x01\x2c\x00\x00\xbe\x80\x00\x00\x00\x00'` is partially variable:
|
||||||
|
bytes `\x01\x2c` = 300 (5-minute default histogram interval); changes when interval changes.
|
||||||
|
|
||||||
|
**Field layout relative to the 6-byte anchor (write payload / E5 read — noted where different):**
|
||||||
|
|
||||||
|
| Offset | Field | Format | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| anchor − 7 (write) / anchor − 8 (read) | recording_mode | uint8 | E5 read has extra `0x10` at anchor−7 |
|
||||||
|
| anchor − 6 | sample_rate | uint16 BE | same in read & write |
|
||||||
|
| anchor − 4 | histogram_interval_sec | uint16 BE | seconds; same in read & write ✅ 2026-04-20 |
|
||||||
|
| anchor − 2 | `0x00 0x00` | padding | |
|
||||||
|
| anchor | `\xbe\x80\x00\x00\x00\x00` | anchor | |
|
||||||
|
| anchor + 6 | record_time | float32 BE | same in read & write |
|
||||||
|
|
||||||
**recording_mode enum** (confirmed 2026-04-20 from 4-20-26 captures):
|
**recording_mode enum** (confirmed 2026-04-20 from 4-20-26 captures):
|
||||||
|
|
||||||
@@ -382,7 +402,7 @@ Anchor: `b'\x01\x2c\x00\x00\xbe\x80\x00\x00\x00\x00'`, search `cfg[0:150]`
|
|||||||
| `0x03` | Histogram |
|
| `0x03` | Histogram |
|
||||||
| `0x04` | Histogram + Continuous |
|
| `0x04` | Histogram + Continuous |
|
||||||
|
|
||||||
**Offset note:** The write payload (SUB 71 cfg) has recording_mode 3 bytes before the anchor (`anchor_pos − 3`). The E5 read response has it 4 bytes before (`anchor_pos − 4`), with an extra `0x10` byte sitting between recording_mode and sample_rate in the read format. Use `anchor_pos − 3` when encoding writes; use `anchor_pos − 4` when decoding reads.
|
**DLE escaping in write frames — CONFIRMED 2026-04-20:** Write frame data payloads DO escape `0x03` (ETX) bytes with a `0x10` DLE prefix. For histogram_interval = 900 (0x0384), the wire carries `10 03 84` — the `0x03` high byte is preceded by a DLE escape. After DLE destuffing (`10 XX → XX`), the logical field value is correctly `03 84` = 900. The CLAUDE.md claim that write frame data is "written RAW" was incorrect; at minimum ETX (0x03) bytes are escaped. S3FrameParser handles this transparently so the decoded `compliance_raw` always contains logical (destuffed) bytes.
|
||||||
|
|
||||||
### SUB 0C — Waveform Record (210 bytes = data[11:11+0xD2])
|
### SUB 0C — Waveform Record (210 bytes = data[11:11+0xD2])
|
||||||
|
|
||||||
@@ -721,7 +741,7 @@ offsets in the raw 1A/E5 payload. Only fields with `✅` have confirmed offsets
|
|||||||
- Record Stop Mode: Fixed Record Time / Auto / Manual Stop (enum) ❓ (byte near recording_mode; data[40] in E5 sf1 changed 0x01→0x00 alongside Continuous→Single Shot — may be this field)
|
- Record Stop Mode: Fixed Record Time / Auto / Manual Stop (enum) ❓ (byte near recording_mode; data[40] in E5 sf1 changed 0x01→0x00 alongside Continuous→Single Shot — may be this field)
|
||||||
- Sample Rate: Standard 1024 / Fast 2048 / Faster 4096 sps ✅ (anchor−2)
|
- Sample Rate: Standard 1024 / Fast 2048 / Faster 4096 sps ✅ (anchor−2)
|
||||||
- Record Time: float, seconds ✅ (anchor+10)
|
- Record Time: float, seconds ✅ (anchor+10)
|
||||||
- Histogram Interval: 5 / 15 / 30 / 60 minutes (enum, mode-gated)
|
- Histogram Interval: 2s / 5s / 15s / 1m / 5m / 15m ✅ (uint16 BE seconds at anchor−4, same in read & write; mode-gated to Histogram/Histogram+Continuous) — confirmed 2026-04-20
|
||||||
- Storage Mode: Save All Data / Save Triggered (enum)
|
- Storage Mode: Save All Data / Save Triggered (enum)
|
||||||
- Geophone Type: Standard Triaxial / 4.5 Hz (bool/enum)
|
- Geophone Type: Standard Triaxial / 4.5 Hz (bool/enum)
|
||||||
- Geophone Channels: Enable all geophones (bool), Trigger Source (bool)
|
- Geophone Channels: Enable all geophones (bool), Trigger Source (bool)
|
||||||
|
|||||||
+54
-36
@@ -847,9 +847,10 @@ class MiniMateClient:
|
|||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
# Recording parameters
|
# Recording parameters
|
||||||
recording_mode: Optional[int] = None,
|
recording_mode: Optional[int] = None,
|
||||||
sample_rate: Optional[int] = None,
|
sample_rate: Optional[int] = None,
|
||||||
record_time: Optional[float] = None,
|
record_time: Optional[float] = None,
|
||||||
|
histogram_interval_sec: Optional[int] = None,
|
||||||
# Threshold parameters (geo channels, in/s)
|
# Threshold parameters (geo channels, in/s)
|
||||||
trigger_level_geo: Optional[float] = None,
|
trigger_level_geo: Optional[float] = None,
|
||||||
alarm_level_geo: Optional[float] = None,
|
alarm_level_geo: Optional[float] = None,
|
||||||
@@ -919,6 +920,7 @@ class MiniMateClient:
|
|||||||
recording_mode=recording_mode,
|
recording_mode=recording_mode,
|
||||||
sample_rate=sample_rate,
|
sample_rate=sample_rate,
|
||||||
record_time=record_time,
|
record_time=record_time,
|
||||||
|
histogram_interval_sec=histogram_interval_sec,
|
||||||
trigger_level_geo=trigger_level_geo,
|
trigger_level_geo=trigger_level_geo,
|
||||||
alarm_level_geo=alarm_level_geo,
|
alarm_level_geo=alarm_level_geo,
|
||||||
project=project,
|
project=project,
|
||||||
@@ -1657,12 +1659,13 @@ def _encode_compliance_config(
|
|||||||
sample_rate: Optional[int] = None,
|
sample_rate: Optional[int] = None,
|
||||||
record_time: Optional[float] = None,
|
record_time: Optional[float] = None,
|
||||||
trigger_level_geo: Optional[float] = None,
|
trigger_level_geo: Optional[float] = None,
|
||||||
alarm_level_geo: Optional[float] = None,
|
alarm_level_geo: Optional[float] = None,
|
||||||
project: Optional[str] = None,
|
histogram_interval_sec: Optional[int] = None,
|
||||||
client_name: Optional[str] = None,
|
project: Optional[str] = None,
|
||||||
operator: Optional[str] = None,
|
client_name: Optional[str] = None,
|
||||||
seis_loc: Optional[str] = None,
|
operator: Optional[str] = None,
|
||||||
notes: Optional[str] = None,
|
seis_loc: Optional[str] = None,
|
||||||
|
notes: Optional[str] = None,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
"""
|
"""
|
||||||
Patch a live 2126-byte compliance buffer (read from the device) with any
|
Patch a live 2126-byte compliance buffer (read from the device) with any
|
||||||
@@ -1674,13 +1677,14 @@ def _encode_compliance_config(
|
|||||||
DLE-jitter shifts):
|
DLE-jitter shifts):
|
||||||
|
|
||||||
Anchor: b'\\xbe\\x80\\x00\\x00\\x00\\x00' (confirmed stable, both BE11529 and BE18189)
|
Anchor: b'\\xbe\\x80\\x00\\x00\\x00\\x00' (confirmed stable, both BE11529 and BE18189)
|
||||||
recording_mode → uint8 at anchor_pos - 7 (write payload; one byte earlier than read format)
|
recording_mode → uint8 at anchor_pos - 7 (write payload)
|
||||||
Values: 0x00=Single Shot, 0x01=Continuous, 0x03=Histogram, 0x04=Histogram+Continuous
|
Values: 0x00=Single Shot, 0x01=Continuous, 0x03=Histogram, 0x04=Histogram+Continuous
|
||||||
NOTE: In the E5 read response the field is at anchor_pos - 8 due to an extra
|
NOTE: In the E5 read response (decode) field is at anchor_pos - 8 due to an
|
||||||
0x10 byte at anchor_pos - 7 present only in the read format. The write
|
extra 0x10 byte at read anchor_pos - 7. Write payload has no extra byte.
|
||||||
payload does NOT have this extra byte — use anchor_pos - 7 here.
|
sample_rate → uint16 BE at anchor_pos - 6
|
||||||
sample_rate → uint16 BE at anchor_pos - 6
|
histogram_interval_sec → uint16 BE at anchor_pos - 4 (seconds; mode-gated to Histogram/Histogram+Continuous)
|
||||||
record_time → float32 BE at anchor_pos + 6
|
Valid values: 2, 5, 15, 60, 300, 900 (= 2s, 5s, 15s, 1m, 5m, 15m)
|
||||||
|
record_time → float32 BE at anchor_pos + 6
|
||||||
|
|
||||||
Channel block (anchored on b"Tran" with unit-string guard):
|
Channel block (anchored on b"Tran" with unit-string guard):
|
||||||
trigger_level_geo → float32 BE at tran_pos + 34
|
trigger_level_geo → float32 BE at tran_pos + 34
|
||||||
@@ -1730,6 +1734,14 @@ def _encode_compliance_config(
|
|||||||
struct.pack_into(">H", buf, _anc - 6, sample_rate)
|
struct.pack_into(">H", buf, _anc - 6, sample_rate)
|
||||||
log.debug("_encode_compliance_config: sample_rate=%d -> offset %d", sample_rate, _anc - 6)
|
log.debug("_encode_compliance_config: sample_rate=%d -> offset %d", sample_rate, _anc - 6)
|
||||||
|
|
||||||
|
if histogram_interval_sec is not None:
|
||||||
|
if _anc < 4:
|
||||||
|
log.warning("_encode_compliance_config: anchor not found — cannot write histogram_interval")
|
||||||
|
else:
|
||||||
|
struct.pack_into(">H", buf, _anc - 4, histogram_interval_sec)
|
||||||
|
log.debug("_encode_compliance_config: histogram_interval=%ds -> offset %d",
|
||||||
|
histogram_interval_sec, _anc - 4)
|
||||||
|
|
||||||
if record_time is not None:
|
if record_time is not None:
|
||||||
if _anc < 0 or _anc + 10 > len(buf):
|
if _anc < 0 or _anc + 10 > len(buf):
|
||||||
log.warning("_encode_compliance_config: anchor not found — cannot write record_time")
|
log.warning("_encode_compliance_config: anchor not found — cannot write record_time")
|
||||||
@@ -1841,29 +1853,27 @@ def _decode_compliance_config_into(data: bytes, info: DeviceInfo) -> None:
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.warning("compliance_config: setup_name extraction failed: %s", exc)
|
log.warning("compliance_config: setup_name extraction failed: %s", exc)
|
||||||
|
|
||||||
# ── Record time + sample rate — anchor-relative ───────────────────────────
|
# ── recording_mode / sample_rate / histogram_interval / record_time ─────────
|
||||||
# The 10-byte anchor sits between sample_rate and record_time in the cfg.
|
# 6-byte stable anchor: b'\xbe\x80\x00\x00\x00\x00' — confirmed across BE11529
|
||||||
# Absolute offsets are NOT reliable because sample_rate = 4096 (0x1000) is
|
# and BE18189. The 4 bytes immediately before the anchor are NOT constant:
|
||||||
# DLE-escaped in the raw S3 frame (10 10 00 → 10 00 after unstuffing),
|
# bytes -4:-2 are the histogram_interval (uint16 BE, seconds), and bytes -2:0
|
||||||
# making frame C 1 byte shorter than for 1024/2048 and shifting everything.
|
# are zero padding. The old "10-byte anchor" (\x01\x2c\x00\x00 prefix) was
|
||||||
# sample_rate: uint16_BE at anchor - 2
|
# only constant when the histogram interval happened to be 5 min (0x012C = 300).
|
||||||
# record_time: float32_BE at anchor + 10
|
#
|
||||||
# 6-byte suffix anchor — confirmed stable across BE11529 and bench unit (BE18189).
|
# E5 read format layout relative to 6-byte anchor:
|
||||||
# The preceding 4 bytes (old anchor prefix 01 2c / 00 3c) vary by unit config;
|
# _anchor - 8 : recording_mode (uint8)
|
||||||
# only be 80 00 00 00 00 is constant.
|
# _anchor - 7 : 0x10 (extra byte E5 read only; absent in SUB 71 write)
|
||||||
# sample_rate : uint16 BE at anchor_pos - 6
|
# _anchor - 6 : sample_rate_hi (uint16 BE, MSB)
|
||||||
# record_time : float32 BE at anchor_pos + 6
|
# _anchor - 5 : sample_rate_lo (uint16 BE, LSB)
|
||||||
|
# _anchor - 4 : histogram_interval_hi (uint16 BE, seconds, MSB)
|
||||||
|
# _anchor - 3 : histogram_interval_lo (uint16 BE, seconds, LSB)
|
||||||
|
# _anchor - 2 : 0x00 (padding)
|
||||||
|
# _anchor - 1 : 0x00 (padding)
|
||||||
|
# _anchor : \xbe\x80\x00\x00\x00\x00 (6-byte anchor)
|
||||||
|
# _anchor + 6 : record_time (float32 BE)
|
||||||
_ANCHOR = b'\xbe\x80\x00\x00\x00\x00'
|
_ANCHOR = b'\xbe\x80\x00\x00\x00\x00'
|
||||||
_anchor = data.find(_ANCHOR, 0, 150)
|
_anchor = data.find(_ANCHOR, 0, 150)
|
||||||
if _anchor >= 8 and _anchor + 10 <= len(data):
|
if _anchor >= 8 and _anchor + 10 <= len(data):
|
||||||
# Layout (E5 read format, relative to 6-byte anchor suffix):
|
|
||||||
# _anchor - 8 : recording_mode (uint8)
|
|
||||||
# _anchor - 7 : 0x10 (extra byte present in E5 read only; absent in SUB 71 write)
|
|
||||||
# _anchor - 6 : sample_rate_hi (uint8, MSB of uint16 BE)
|
|
||||||
# _anchor - 5 : sample_rate_lo (uint8, LSB of uint16 BE)
|
|
||||||
# _anchor - 4 : \x01\x2c\x00\x00 (10-byte anchor prefix)
|
|
||||||
# _anchor : \xbe\x80\x00\x00\x00\x00 (6-byte anchor suffix)
|
|
||||||
# _anchor + 6 : record_time (float32 BE)
|
|
||||||
try:
|
try:
|
||||||
config.recording_mode = data[_anchor - 8]
|
config.recording_mode = data[_anchor - 8]
|
||||||
log.debug(
|
log.debug(
|
||||||
@@ -1878,6 +1888,14 @@ def _decode_compliance_config_into(data: bytes, info: DeviceInfo) -> None:
|
|||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.warning("compliance_config: sample_rate extraction failed: %s", exc)
|
log.warning("compliance_config: sample_rate extraction failed: %s", exc)
|
||||||
|
try:
|
||||||
|
config.histogram_interval_sec = struct.unpack_from(">H", data, _anchor - 4)[0]
|
||||||
|
log.debug(
|
||||||
|
"compliance_config: histogram_interval = %d s (anchor@%d)",
|
||||||
|
config.histogram_interval_sec, _anchor
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
log.warning("compliance_config: histogram_interval extraction failed: %s", exc)
|
||||||
try:
|
try:
|
||||||
config.record_time = struct.unpack_from(">f", data, _anchor + 6)[0]
|
config.record_time = struct.unpack_from(">f", data, _anchor + 6)[0]
|
||||||
log.debug(
|
log.debug(
|
||||||
|
|||||||
@@ -340,10 +340,14 @@ class ComplianceConfig:
|
|||||||
# Recording parameters (✅ CONFIRMED from §7.6)
|
# Recording parameters (✅ CONFIRMED from §7.6)
|
||||||
recording_mode: Optional[int] = None # uint8: 0x00=Single Shot, 0x01=Continuous,
|
recording_mode: Optional[int] = None # uint8: 0x00=Single Shot, 0x01=Continuous,
|
||||||
# 0x03=Histogram, 0x04=Histogram+Continuous ✅ confirmed 2026-04-20
|
# 0x03=Histogram, 0x04=Histogram+Continuous ✅ confirmed 2026-04-20
|
||||||
# Read (E5 sf1): data[anchor_pos - 4]
|
# Read (E5): data[anchor_pos - 8] (6-byte anchor)
|
||||||
# Write (SUB 71 payload): cfg[anchor_pos - 3]
|
# Write (SUB 71): data[anchor_pos - 7]
|
||||||
record_time: Optional[float] = None # seconds (7.0, 10.0, 13.0, etc.)
|
sample_rate: Optional[int] = None # sps (1024, 2048, 4096)
|
||||||
sample_rate: Optional[int] = None # sps (1024, 2048, 4096, etc.)
|
histogram_interval_sec: Optional[int] = None # uint16 BE, seconds ✅ confirmed 2026-04-20
|
||||||
|
# anchor_pos - 4 (same offset in read & write)
|
||||||
|
# Valid values: 2, 5, 15, 60, 300, 900
|
||||||
|
# Mode-gated: only active in Histogram/Histogram+Continuous
|
||||||
|
record_time: Optional[float] = None # seconds (e.g. 3.0, 5.0, 8.0, 10.0)
|
||||||
|
|
||||||
# Trigger/alarm levels (✅ CONFIRMED per-channel at §7.6)
|
# Trigger/alarm levels (✅ CONFIRMED per-channel at §7.6)
|
||||||
# For now we store the first geo channel (Transverse) as representatives;
|
# For now we store the first geo channel (Transverse) as representatives;
|
||||||
|
|||||||
+9
-6
@@ -285,9 +285,10 @@ def _serialise_compliance_config(cc: Optional["ComplianceConfig"]) -> Optional[d
|
|||||||
if cc is None:
|
if cc is None:
|
||||||
return None
|
return None
|
||||||
return {
|
return {
|
||||||
"recording_mode": cc.recording_mode, # 0x00=Single Shot, 0x01=Continuous, 0x03=Histogram, 0x04=Histogram+Continuous
|
"recording_mode": cc.recording_mode, # 0x00=Single Shot, 0x01=Continuous, 0x03=Histogram, 0x04=Histogram+Continuous
|
||||||
"record_time": cc.record_time,
|
"sample_rate": cc.sample_rate,
|
||||||
"sample_rate": cc.sample_rate,
|
"histogram_interval_sec": cc.histogram_interval_sec, # seconds; None if not Histogram mode
|
||||||
|
"record_time": cc.record_time,
|
||||||
"trigger_level_geo": cc.trigger_level_geo,
|
"trigger_level_geo": cc.trigger_level_geo,
|
||||||
"alarm_level_geo": cc.alarm_level_geo,
|
"alarm_level_geo": cc.alarm_level_geo,
|
||||||
"geo_adc_scale": cc.geo_adc_scale, # hw scale factor (in/s)/V — informational only, do not write
|
"geo_adc_scale": cc.geo_adc_scale, # hw scale factor (in/s)/V — informational only, do not write
|
||||||
@@ -854,9 +855,10 @@ class DeviceConfigBody(BaseModel):
|
|||||||
notes : Extended notes.
|
notes : Extended notes.
|
||||||
"""
|
"""
|
||||||
# Recording parameters
|
# Recording parameters
|
||||||
recording_mode: Optional[int] = None
|
recording_mode: Optional[int] = None
|
||||||
sample_rate: Optional[int] = None
|
sample_rate: Optional[int] = None
|
||||||
record_time: Optional[float] = None
|
record_time: Optional[float] = None
|
||||||
|
histogram_interval_sec: Optional[int] = None # seconds: 2, 5, 15, 60, 300, 900 (mode-gated)
|
||||||
# Threshold parameters
|
# Threshold parameters
|
||||||
trigger_level_geo: Optional[float] = None
|
trigger_level_geo: Optional[float] = None
|
||||||
alarm_level_geo: Optional[float] = None
|
alarm_level_geo: Optional[float] = None
|
||||||
@@ -918,6 +920,7 @@ def device_config(
|
|||||||
recording_mode=body.recording_mode,
|
recording_mode=body.recording_mode,
|
||||||
sample_rate=body.sample_rate,
|
sample_rate=body.sample_rate,
|
||||||
record_time=body.record_time,
|
record_time=body.record_time,
|
||||||
|
histogram_interval_sec=body.histogram_interval_sec,
|
||||||
trigger_level_geo=body.trigger_level_geo,
|
trigger_level_geo=body.trigger_level_geo,
|
||||||
alarm_level_geo=body.alarm_level_geo,
|
alarm_level_geo=body.alarm_level_geo,
|
||||||
project=body.project,
|
project=body.project,
|
||||||
|
|||||||
+21
-3
@@ -824,6 +824,20 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="cfg-field">
|
||||||
|
<label>Histogram Interval</label>
|
||||||
|
<select id="cfg-histogram-interval">
|
||||||
|
<option value="">— unchanged —</option>
|
||||||
|
<option value="2">2 seconds</option>
|
||||||
|
<option value="5">5 seconds</option>
|
||||||
|
<option value="15">15 seconds</option>
|
||||||
|
<option value="60">1 minute</option>
|
||||||
|
<option value="300">5 minutes</option>
|
||||||
|
<option value="900">15 minutes</option>
|
||||||
|
</select>
|
||||||
|
<div class="hint">Only active in Histogram / Histogram + Continuous mode</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="cfg-field">
|
<div class="cfg-field">
|
||||||
<label>Record Time (seconds)</label>
|
<label>Record Time (seconds)</label>
|
||||||
<input type="number" id="cfg-record-time" step="0.5" min="0.5" max="60" placeholder="e.g. 3.0" />
|
<input type="number" id="cfg-record-time" step="0.5" min="0.5" max="60" placeholder="e.g. 3.0" />
|
||||||
@@ -1336,6 +1350,7 @@ function populateDeviceTab() {
|
|||||||
const complianceRows = [
|
const complianceRows = [
|
||||||
['Recording Mode', cc.recording_mode != null ? (RECORDING_MODE_LABELS[cc.recording_mode] || `0x${cc.recording_mode.toString(16).padStart(2,'0')}`) : '—'],
|
['Recording Mode', cc.recording_mode != null ? (RECORDING_MODE_LABELS[cc.recording_mode] || `0x${cc.recording_mode.toString(16).padStart(2,'0')}`) : '—'],
|
||||||
['Sample Rate', cc.sample_rate != null ? `${cc.sample_rate} sps` : '—'],
|
['Sample Rate', cc.sample_rate != null ? `${cc.sample_rate} sps` : '—'],
|
||||||
|
['Histogram Interval', cc.histogram_interval_sec != null ? (() => { const s = cc.histogram_interval_sec; return s < 60 ? `${s}s` : `${s/60}m`; })() : '—'],
|
||||||
['Record Time', cc.record_time != null ? `${cc.record_time.toFixed(2)} s` : '—'],
|
['Record Time', cc.record_time != null ? `${cc.record_time.toFixed(2)} s` : '—'],
|
||||||
['Trigger Level (geo)', cc.trigger_level_geo != null ? `${cc.trigger_level_geo.toFixed(4)} in/s` : '—'],
|
['Trigger Level (geo)', cc.trigger_level_geo != null ? `${cc.trigger_level_geo.toFixed(4)} in/s` : '—'],
|
||||||
['Alarm Level (geo)', cc.alarm_level_geo != null ? `${cc.alarm_level_geo.toFixed(4)} in/s` : '—'],
|
['Alarm Level (geo)', cc.alarm_level_geo != null ? `${cc.alarm_level_geo.toFixed(4)} in/s` : '—'],
|
||||||
@@ -1370,9 +1385,10 @@ function renderTable(id, rows) {
|
|||||||
function populateConfigFromDeviceInfo() {
|
function populateConfigFromDeviceInfo() {
|
||||||
if (!unitInfo) return;
|
if (!unitInfo) return;
|
||||||
const cc = unitInfo.compliance_config || {};
|
const cc = unitInfo.compliance_config || {};
|
||||||
if (cc.recording_mode != null) qs('cfg-recording-mode', String(cc.recording_mode));
|
if (cc.recording_mode != null) qs('cfg-recording-mode', String(cc.recording_mode));
|
||||||
if (cc.sample_rate) qs('cfg-sample-rate', String(cc.sample_rate));
|
if (cc.sample_rate) qs('cfg-sample-rate', String(cc.sample_rate));
|
||||||
if (cc.record_time != null) qs('cfg-record-time', cc.record_time.toFixed(1));
|
if (cc.histogram_interval_sec != null) qs('cfg-histogram-interval', String(cc.histogram_interval_sec));
|
||||||
|
if (cc.record_time != null) qs('cfg-record-time', cc.record_time.toFixed(1));
|
||||||
if (cc.trigger_level_geo != null) qs('cfg-trigger', cc.trigger_level_geo.toFixed(4));
|
if (cc.trigger_level_geo != null) qs('cfg-trigger', cc.trigger_level_geo.toFixed(4));
|
||||||
if (cc.alarm_level_geo != null) qs('cfg-alarm', cc.alarm_level_geo.toFixed(4));
|
if (cc.alarm_level_geo != null) qs('cfg-alarm', cc.alarm_level_geo.toFixed(4));
|
||||||
if (cc.project) qs('cfg-project', cc.project);
|
if (cc.project) qs('cfg-project', cc.project);
|
||||||
@@ -1417,6 +1433,8 @@ async function writeConfig() {
|
|||||||
if (rm !== '') body.recording_mode = parseInt(rm, 10);
|
if (rm !== '') body.recording_mode = parseInt(rm, 10);
|
||||||
const sr = qs('cfg-sample-rate').value;
|
const sr = qs('cfg-sample-rate').value;
|
||||||
if (sr) body.sample_rate = parseInt(sr, 10);
|
if (sr) body.sample_rate = parseInt(sr, 10);
|
||||||
|
const hi = qs('cfg-histogram-interval').value;
|
||||||
|
if (hi !== '') body.histogram_interval_sec = parseInt(hi, 10);
|
||||||
const rt = qs('cfg-record-time').value;
|
const rt = qs('cfg-record-time').value;
|
||||||
if (rt) body.record_time = parseFloat(rt);
|
if (rt) body.record_time = parseFloat(rt);
|
||||||
const trig = qs('cfg-trigger').value;
|
const trig = qs('cfg-trigger').value;
|
||||||
|
|||||||
Reference in New Issue
Block a user