feat: add config API endpoint and JSON schema draft
This commit is contained in:
+85
-29
@@ -478,58 +478,98 @@ def device_event_waveform(
|
||||
|
||||
# ── Write endpoints ───────────────────────────────────────────────────────────
|
||||
|
||||
class ProjectInfoBody(BaseModel):
|
||||
"""Request body for POST /device/config/project."""
|
||||
project: Optional[str] = None
|
||||
client_name: Optional[str] = None
|
||||
operator: Optional[str] = None
|
||||
seis_loc: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
class DeviceConfigBody(BaseModel):
|
||||
"""
|
||||
Request body for POST /device/config.
|
||||
|
||||
All fields are optional — only supplied (non-null) fields are written to
|
||||
the device. All other config bytes are round-tripped verbatim.
|
||||
|
||||
Recording parameters
|
||||
--------------------
|
||||
sample_rate : Samples per second. Valid values: 1024, 2048, 4096.
|
||||
record_time : Record duration in seconds (e.g. 1.0, 2.0, 3.0).
|
||||
|
||||
Trigger / alarm thresholds (geo channels, in/s)
|
||||
------------------------------------------------
|
||||
trigger_level_geo : Trigger threshold in in/s (e.g. 0.5).
|
||||
alarm_level_geo : Alarm threshold in in/s (e.g. 1.0).
|
||||
max_range_geo : Full-scale calibration constant (e.g. 6.206).
|
||||
Rarely changed — only set if you know what you're doing.
|
||||
|
||||
Project / operator strings (max 41 ASCII characters each)
|
||||
----------------------------
|
||||
project : Project description.
|
||||
client_name : Client / company name.
|
||||
operator : Operator / technician name.
|
||||
seis_loc : Sensor location description.
|
||||
notes : Extended notes.
|
||||
"""
|
||||
# Recording parameters
|
||||
sample_rate: Optional[int] = None
|
||||
record_time: Optional[float] = None
|
||||
# Threshold parameters
|
||||
trigger_level_geo: Optional[float] = None
|
||||
alarm_level_geo: Optional[float] = None
|
||||
max_range_geo: Optional[float] = None
|
||||
# Project / operator strings
|
||||
project: Optional[str] = None
|
||||
client_name: Optional[str] = None
|
||||
operator: Optional[str] = None
|
||||
seis_loc: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
@app.post("/device/config/project")
|
||||
def device_config_project(
|
||||
body: ProjectInfoBody,
|
||||
@app.post("/device/config")
|
||||
def device_config(
|
||||
body: DeviceConfigBody,
|
||||
port: Optional[str] = Query(None, description="Serial port (e.g. COM5)"),
|
||||
baud: int = Query(38400, description="Serial baud rate"),
|
||||
host: Optional[str] = Query(None, description="TCP host — modem IP or ACH relay"),
|
||||
tcp_port: int = Query(DEFAULT_TCP_PORT, description=f"TCP port (default {DEFAULT_TCP_PORT})"),
|
||||
) -> dict:
|
||||
"""
|
||||
POC: Read the current device config, patch ASCII project/client/operator/
|
||||
sensor-location/notes fields, and write the modified config back.
|
||||
Read the current device config, apply any supplied changes to the compliance
|
||||
block, and write the full config back.
|
||||
|
||||
Only fields included in the JSON body (non-null) are modified. All other
|
||||
bytes are round-tripped verbatim from the device.
|
||||
Only non-null fields in the JSON body are modified. All other config bytes
|
||||
are round-tripped verbatim from the device.
|
||||
|
||||
Supply either *port* (serial) or *host* (TCP/modem).
|
||||
|
||||
Example body:
|
||||
Example body (all fields optional — include only what you want to change):
|
||||
{
|
||||
"project": "Bridge Inspection 2026",
|
||||
"client_name": "City of Portland",
|
||||
"operator": "Brian Harrison",
|
||||
"seis_loc": "South Abutment",
|
||||
"notes": "Pre-blast baseline"
|
||||
"sample_rate": 1024,
|
||||
"record_time": 3.0,
|
||||
"trigger_level_geo": 0.5,
|
||||
"alarm_level_geo": 1.0,
|
||||
"project": "Bridge Inspection 2026",
|
||||
"client_name": "City of Portland",
|
||||
"operator": "Brian Harrison",
|
||||
"seis_loc": "South Abutment",
|
||||
"notes": "Pre-blast baseline"
|
||||
}
|
||||
|
||||
Returns:
|
||||
{"status": "ok", "message": "..."} on success.
|
||||
{"status": "ok", "updated_fields": {...}} on success.
|
||||
|
||||
Raises:
|
||||
502 on protocol errors (timeout, bad ack, etc.).
|
||||
422 if neither port nor host is provided.
|
||||
"""
|
||||
log.info(
|
||||
"POST /device/config/project port=%s host=%s body=%s",
|
||||
port, host, body.model_dump(exclude_none=True),
|
||||
)
|
||||
changed = body.model_dump(exclude_none=True)
|
||||
log.info("POST /device/config port=%s host=%s fields=%s", port, host, list(changed.keys()))
|
||||
|
||||
try:
|
||||
def _do():
|
||||
with _build_client(port, baud, host, tcp_port) as client:
|
||||
client.connect()
|
||||
client.set_project_info(
|
||||
client.apply_config(
|
||||
sample_rate=body.sample_rate,
|
||||
record_time=body.record_time,
|
||||
trigger_level_geo=body.trigger_level_geo,
|
||||
alarm_level_geo=body.alarm_level_geo,
|
||||
max_range_geo=body.max_range_geo,
|
||||
project=body.project,
|
||||
client_name=body.client_name,
|
||||
operator=body.operator,
|
||||
@@ -543,12 +583,28 @@ def device_config_project(
|
||||
raise HTTPException(status_code=502, detail=f"Protocol error: {exc}") from exc
|
||||
except OSError as exc:
|
||||
raise HTTPException(status_code=502, detail=f"Connection error: {exc}") from exc
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=f"Device error: {exc}") from exc
|
||||
|
||||
changed = body.model_dump(exclude_none=True)
|
||||
msg = "Config written. Fields updated: " + (", ".join(changed.keys()) or "none")
|
||||
return {"status": "ok", "message": msg, "updated_fields": changed}
|
||||
return {
|
||||
"status": "ok",
|
||||
"updated_fields": changed,
|
||||
}
|
||||
|
||||
|
||||
# Keep the old endpoint alive under its old URL for anything already calling it
|
||||
@app.post("/device/config/project")
|
||||
def device_config_project(
|
||||
body: DeviceConfigBody,
|
||||
port: Optional[str] = Query(None, description="Serial port (e.g. COM5)"),
|
||||
baud: int = Query(38400, description="Serial baud rate"),
|
||||
host: Optional[str] = Query(None, description="TCP host — modem IP or ACH relay"),
|
||||
tcp_port: int = Query(DEFAULT_TCP_PORT, description=f"TCP port (default {DEFAULT_TCP_PORT})"),
|
||||
) -> dict:
|
||||
"""Deprecated alias for POST /device/config — use that instead."""
|
||||
return device_config(body=body, port=port, baud=baud, host=host, tcp_port=tcp_port)
|
||||
|
||||
|
||||
# ── Entry point ────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user