99 lines
5.0 KiB
Python
99 lines
5.0 KiB
Python
"""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):
|
|
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 abs(incoming["level"]) > 0.02:
|
|
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",
|
|
"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}
|