"""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}