feat(vesc): integrate native calibration diagnostics and configuration archives
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
"""Native FW 5.02 Hall measurement used by VESC Tool (COMM_DETECT_HALL_FOC).
|
||||
|
||||
This is a non-interruptible ~12 s firmware procedure: it locks mc_interface,
|
||||
overrides phase, sweeps three electrical turns in each direction and restores
|
||||
its prior RAM configuration. A host stop/current-zero cannot cancel the sweep.
|
||||
No table is applied here; measurements are archived in the operation receipt.
|
||||
"""
|
||||
from .configuration import decode
|
||||
from .protocol import values, ppm
|
||||
|
||||
|
||||
def parse_result(raw):
|
||||
if len(raw) != 10 or raw[0] != 28 or raw[9] not in (0, 1):
|
||||
raise ValueError("Invalid Hall result")
|
||||
table = list(raw[1:9])
|
||||
if any(v > 200 and v != 255 for v in table): raise ValueError("Invalid Hall angle")
|
||||
observed = [i for i, v in enumerate(table) if v != 255]
|
||||
return {"firmware_success": raw[9] == 0, "hall_table": table,
|
||||
"observed_states": observed, "valid_six_states": raw[9] == 0 and len(observed) == 6}
|
||||
|
||||
|
||||
def measure(owner, devices, target, original, backups, unchanged, preflight):
|
||||
motor = decode(original, "motor")
|
||||
if motor["m_sensor_port_mode"] != 0:
|
||||
from .motor_test import Rejected
|
||||
raise Rejected("Вход датчиков выбран не в режиме Холла.")
|
||||
pending = owner.service.root / "hall-pending.json"
|
||||
owner.atomic(pending, {"device_id": target.id, "identity": target.identity,
|
||||
"started_at": owner.utc(), "backups": backups})
|
||||
owner.state("calibrating")
|
||||
owner.active, owner.mode = True, "hall"
|
||||
started = owner.monotonic()
|
||||
samples, issues, result, after = [], [], None, {}
|
||||
completed, restored, attempted = False, False, False
|
||||
try:
|
||||
unchanged()
|
||||
owner.neutral(devices)
|
||||
# Do not enter the native procedure if Stop arrived during preflight.
|
||||
if owner.stop.is_set(): raise ValueError("Measurement cancelled before start")
|
||||
for device in devices: device.link.test_command("claim")
|
||||
attempted = True
|
||||
target.link.detect_hall()
|
||||
while owner.monotonic() - started < 30:
|
||||
cycle = owner.monotonic()
|
||||
try:
|
||||
unchanged()
|
||||
for device in devices:
|
||||
incoming = ppm(device.link.query(31, timeout=0.06))
|
||||
if owner.receiver_active(device.id, incoming["level"]):
|
||||
owner.state("rc")
|
||||
if "rc_during_native_cycle" not in issues: issues.append("rc_during_native_cycle")
|
||||
sample = values(target.link.query(4, timeout=0.06))
|
||||
samples.append({"at": owner.monotonic() - started, "values": sample})
|
||||
for device in devices: device.link.test_command("claim")
|
||||
for device in devices:
|
||||
if device is not target: device.link.test_command("release")
|
||||
except (OSError, ValueError, TimeoutError):
|
||||
if "communication_unconfirmed" not in issues: issues.append("communication_unconfirmed")
|
||||
if owner.stop.is_set() and "stop_requested_during_native_cycle" not in issues:
|
||||
issues.append("stop_requested_during_native_cycle")
|
||||
reply = target.link.hall_result
|
||||
if reply is not None:
|
||||
result = parse_result(reply)
|
||||
completed = True
|
||||
break
|
||||
owner.sleep(max(0, 0.1 - (owner.monotonic() - cycle)))
|
||||
except (OSError, ValueError, TimeoutError):
|
||||
issues.append("native_completion_unconfirmed")
|
||||
finally:
|
||||
# Zero is a release AFTER completion, never a claim that native detect
|
||||
# is interruptible. Peers also receive release if the target disappears.
|
||||
for device in devices:
|
||||
try: device.link.test_command("release")
|
||||
except (OSError, ValueError, TimeoutError): issues.append("release_unconfirmed")
|
||||
owner.sleep(0.3)
|
||||
for device in devices:
|
||||
try: after[device.id] = values(device.link.query(4, timeout=0.1))
|
||||
except (OSError, ValueError, TimeoutError): after[device.id] = None
|
||||
if completed or not attempted:
|
||||
target.link.hall_pending = False
|
||||
try: restored = target.link.query(14) == original
|
||||
except (OSError, ValueError, TimeoutError): pass
|
||||
released = all(v is not None and abs(v["motor_current_a"]) <= 1 for v in after.values())
|
||||
if (completed or not attempted) and restored and released:
|
||||
pending.unlink()
|
||||
import os
|
||||
fd = os.open(pending.parent, os.O_RDONLY)
|
||||
try: os.fsync(fd)
|
||||
finally: os.close(fd)
|
||||
if not owner.latched: owner.state("ready")
|
||||
else:
|
||||
owner.state("rc")
|
||||
owner.active, owner.mode = False, None
|
||||
return {"observed_at": owner.utc(), "device_id": target.id, "procedure": "native_foc_hall",
|
||||
"preflight": preflight,
|
||||
"current_a": 5, "elapsed_s": owner.monotonic() - started,
|
||||
"completed": completed, "measurement": result, "issues": issues,
|
||||
"configuration_restored": restored, "configuration_written": False,
|
||||
"release_confirmed": released, "after": after, "samples": samples, "backups": backups}
|
||||
Reference in New Issue
Block a user