What to build
Discover whether a Board's firmware supports the VESC PIN write-lock, and persist the PIN alongside the rest of the Board Link so later sessions can use it.
Protocol reference: docs/pin-lock.md. The feature only exists on surfdado's fork; stock firmware silently drops the packet, so detection is a probe plus timeout.
Board Probe sends COMM_LOCK_STATUS (155) once, on the confirmed transport only (each blind probe with a wrong PIN bumps the firmware's bruteforce cooldown). A reply carrying magic 169 means pinlock firmware; the reply also states whether a PIN is currently set.
Two facts flow through to the Board Link: supportsPinLock and pinCode. A Board Link for a board that has a PIN set but no saved pinCode is incomplete — it reports outdated link integrity, which keeps telemetry working while blocking Firmware-Dependent Commands (CONTEXT.md:325). That is the correct semantic: re-link is the recovery path, and outdated already latches and already means re-link.
This requires bumping linkVersion 3 -> 4, so every existing link re-probes once. Accepted.
outdated now has a third cause, and the existing rider copy ("board link needs update", CONTEXT.md:436) is wrong for it — a rider who sees that will re-link, skip the PIN step, and loop. Carry a reason alongside the state so the surface can say the PIN was rejected.
Likely files
modules/vescape-core/ios/protocol/VescProtocol.swift - packet framing; REFLOAT_MAGIC lives here
modules/vescape-core/ios/connection/BoardTransportDetector.swift - Board Probe; sendProbeBurst / finalizeCurrentObservation
modules/vescape-core/ios/connection/TransportDetection.swift - Probe / Candidate shapes carrying probe facts
modules/vescape-core/ios/telemetry/BoardLinkPersistence.swift - compose / settings, linkVersion
modules/vescape-core/ios/runtime/BoardSession.swift - LinkIdentity, isComplete, LinkIntegrity
modules/vescape-core/src/index.ts - LinkIntegrity type, bridge payload types
CONTEXT.md - Board Link / Link Integrity Check definitions need the PIN fact
Implementation hints
hasBms is the precedent for a probe-discovered capability persisted on the link. Its full path:
BoardTransportDetector -> TransportDetection.Probe/.Candidate -> VescapeCoreModule bridge dict
-> BoardLinkPersistence.compose/settings -> LinkIdentity -> BoardSessionController
Follow it exactly; every one of those sites needs the new fields.
COMM_LOCK_STATUS request is a magic byte then PIN as big-endian uint16:
[155][magic=169][pin_hi][pin_lo]
Reply:
[155][169][writelock][pin_matches][pin_is_set][lock_on_boot][pin_hi][pin_lo]
Do not read the trailing two bytes. They are the stored PIN in cleartext, marked in-source as development-only, and will be removed upstream.
pin_matches is unreliable during the firmware's cooldown — it returns 0 even for a correct PIN. pin_is_set and the magic byte are returned regardless, so the capability probe must read only those and ignore pin_matches.
isComplete at BoardSession.swift currently pins linkVersion == 3 with exact equality. Bump to 4 and add the PIN condition; a board that needs a PIN and has none saved is not complete.
Both native platforms plus index.ts need this — see the @parity tags on BoardLinkPersistence.swift and BoardSession.swift.
Acceptance criteria
Blocked by
None - can start immediately.
Related
What to build
Discover whether a Board's firmware supports the VESC PIN write-lock, and persist the PIN alongside the rest of the Board Link so later sessions can use it.
Protocol reference: docs/pin-lock.md. The feature only exists on surfdado's fork; stock firmware silently drops the packet, so detection is a probe plus timeout.
Board Probe sends
COMM_LOCK_STATUS(155) once, on the confirmed transport only (each blind probe with a wrong PIN bumps the firmware's bruteforce cooldown). A reply carrying magic169means pinlock firmware; the reply also states whether a PIN is currently set.Two facts flow through to the Board Link:
supportsPinLockandpinCode. A Board Link for a board that has a PIN set but no savedpinCodeis incomplete — it reportsoutdatedlink integrity, which keeps telemetry working while blocking Firmware-Dependent Commands (CONTEXT.md:325). That is the correct semantic: re-link is the recovery path, andoutdatedalready latches and already means re-link.This requires bumping
linkVersion3 -> 4, so every existing link re-probes once. Accepted.outdatednow has a third cause, and the existing rider copy ("board link needs update", CONTEXT.md:436) is wrong for it — a rider who sees that will re-link, skip the PIN step, and loop. Carry a reason alongside the state so the surface can say the PIN was rejected.Likely files
modules/vescape-core/ios/protocol/VescProtocol.swift- packet framing;REFLOAT_MAGIClives heremodules/vescape-core/ios/connection/BoardTransportDetector.swift- Board Probe;sendProbeBurst/finalizeCurrentObservationmodules/vescape-core/ios/connection/TransportDetection.swift-Probe/Candidateshapes carrying probe factsmodules/vescape-core/ios/telemetry/BoardLinkPersistence.swift-compose/settings,linkVersionmodules/vescape-core/ios/runtime/BoardSession.swift-LinkIdentity,isComplete,LinkIntegritymodules/vescape-core/src/index.ts-LinkIntegritytype, bridge payload typesCONTEXT.md- Board Link / Link Integrity Check definitions need the PIN factImplementation hints
hasBmsis the precedent for a probe-discovered capability persisted on the link. Its full path:Follow it exactly; every one of those sites needs the new fields.
COMM_LOCK_STATUSrequest is a magic byte then PIN as big-endianuint16:Reply:
Do not read the trailing two bytes. They are the stored PIN in cleartext, marked in-source as development-only, and will be removed upstream.
pin_matchesis unreliable during the firmware's cooldown — it returns 0 even for a correct PIN.pin_is_setand the magic byte are returned regardless, so the capability probe must read only those and ignorepin_matches.isCompleteatBoardSession.swiftcurrently pinslinkVersion == 3with exact equality. Bump to 4 and add the PIN condition; a board that needs a PIN and has none saved is not complete.Both native platforms plus
index.tsneed this — see the@paritytags onBoardLinkPersistence.swiftandBoardSession.swift.Acceptance criteria
COMM_LOCK_STATUSonce, on the confirmed transport onlysupportsPinLock = falsewithout stalling the probesupportsPinLockandpinCodepersist on the Board Link and survive app restartlinkVersionbumped to 4; existing links re-probe once instead of breakingpinCodereportsoutdatedlink integrityoutdatedcarries a distinguishable reason for PIN vs stale-versionindex.tsstay in@parity; CONTEXT.md updatedBlocked by
None - can start immediately.
Related