Files
ulapp/tests/IfLoc.Sim/Protocol.cs
Brent Perteet 0b4fb83546 S1-b: IF-LOC v1.0 locator simulator + round-trip test
Reference implementation of the frozen IF-LOC v1.0 contract
(meta/contracts/ble-device-interface.md): byte-accurate codec for the
telemetry, capture-trigger, and capture-result frames, plus a
LocatorSimulator that replays a canned locate session over a
transport-agnostic loopback link. Unblocks App development without
firmware (Risk R-3, SRS S3.1).

Plain net9.0 (not a MAUI head) so it builds/runs headless under the QA
gate without the Android/iOS workloads; qa-gate.sh auto-discovers
*.Tests.csproj under app/tests/. Round-trip test asserts field-level
fidelity (every SRS S3.1 telemetry + capture-result field), sentinels,
endianness (incl. RFC 4122 big-endian pointId), the REJECTED path, and
exactly-once capture correlation (no silent drop, SRS-LOG-7). 8/8 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-20 13:10:24 -05:00

151 lines
4.6 KiB
C#

namespace IfLoc.Sim;
// Reference constants for the frozen IF-LOC v1.0 contract.
// Source of truth: meta/contracts/ble-device-interface.md. Keep the two in lockstep;
// enum values and message-type ids are append-only once frozen.
/// <summary>Wire message-type ids (frame header byte 1).</summary>
public enum MessageType : byte
{
Telemetry = 0x01,
CaptureTrigger = 0x02,
CaptureResult = 0x03,
Alert = 0x04,
Ack = 0x05,
CmdRequestSnapshot = 0x10,
CmdSetFrequency = 0x11,
CmdSetMode = 0x12,
CmdSetTelemetryRate = 0x13,
CmdTimeSync = 0x14,
CmdSetUtility = 0x15,
OtaBegin = 0x20,
OtaData = 0x21,
OtaEnd = 0x22,
UsageLogRequest = 0x30,
UsageLogRecord = 0x31,
UsageLogEnd = 0x32,
ClaimChallenge = 0x40,
ClaimAssertion = 0x41,
ClaimCertReq = 0x42,
ClaimCert = 0x43,
}
/// <summary>§4.1 locateMode.</summary>
public enum LocateMode : byte
{
Single = 0, Twin = 1, Null = 2, Sweep = 3, TwinSweep = 4, Omni = 5, TwinOmni = 6,
NotAvailable = 0xFF,
}
/// <summary>§4.2 signalType.</summary>
public enum SignalType : byte
{
Active = 0, LineDropActive = 1, Power = 2, GroupedPower = 3, Cathodic = 4,
Sonde = 5, Radio = 6, FaultFind = 7, NotAvailable = 0xFF,
}
/// <summary>§4.4 utilityType (APWA-aligned).</summary>
public enum UtilityType : byte
{
None = 0, Gas = 1, Power = 2, Communications = 3, Water = 4, Sewer = 5, Fiber = 6,
Other = 7, NotAvailable = 0xFF,
}
/// <summary>§4.3 warningFlags bitfield.</summary>
[Flags]
public enum WarningFlags : ushort
{
None = 0,
Shallow = 1 << 0,
Overload = 1 << 1,
SwingTilt = 1 << 2,
DepthInvalid = 1 << 3,
CurrentInvalid = 1 << 4,
OutOfRange = 1 << 5,
DistortionHigh = 1 << 6,
LowBattery = 1 << 7,
}
/// <summary>§4.5 statusFlags bitfield.</summary>
[Flags]
public enum StatusFlags : byte
{
None = 0,
Locating = 1 << 0,
MenuActive = 1 << 1,
TimeSynced = 1 << 2,
DepthModeAuto = 1 << 3,
}
/// <summary>§5.1 triggerType.</summary>
public enum TriggerType : byte
{
ButtonSingle = 0, ButtonHold = 1, OffsetRequest = 2, AppInitiated = 3,
}
/// <summary>§5.2 outcome — the deterministic capture outcome (SRS-LOG-7).</summary>
public enum CaptureOutcome : byte
{
Stored = 0, StoredFlagged = 1, Rejected = 2,
}
/// <summary>§5.4 reasonCode / gateStatus.</summary>
public enum ReasonCode : byte
{
Ok = 0,
FixTypeTooLow = 1,
HrmsExceeded = 2,
VrmsExceeded = 3,
CorrectionAgeExceeded = 4,
NoActiveTicket = 5,
ImuCalibrationInvalid = 6,
HeadingConfidenceLow = 7,
BufferFull = 8,
NoPosition = 9,
WaiverRequired = 10,
Other = 255,
}
/// <summary>§5.2 fixType (GGA-quality mapping; 3 reserved).</summary>
public enum FixType : byte
{
NoFix = 0, Autonomous = 1, Dgps = 2, RtkFixed = 4, RtkFloat = 5,
}
/// <summary>Frozen wire constants and sentinels.</summary>
public static class IfLoc
{
public const byte FrameVersion = 0x01;
// Fixed frame lengths (Appendix A).
public const int HeaderLen = 4;
public const int TelemetryFrameLen = 32;
public const int TelemetryPayloadLen = 28; // offsets 4..31
public const int CaptureTriggerFrameLen = 36;
public const int CaptureResultFrameLen = 52;
// Sentinels (§1).
public const short DepthNoValue = unchecked((short)0x8000);
public const ushort CurrentInvalid = 0xFFFF;
public const uint FrequencyNa = 0xFFFFFFFF;
public const short GuidanceNa = unchecked((short)0x8000);
public const int PositionNoValue = unchecked((int)0x80000000);
public const ushort RmsUnknown = 0xFFFF;
// GATT (§2) — recommended UUIDs the App builds against.
public const string BaseUuidFormat = "A9E1{0:X4}-1B4C-4F9A-9B7E-2D6F0C3A5E11";
public static string LocateServiceUuid => string.Format(BaseUuidFormat, 0x0001);
public static string ProtocolInfoUuid => string.Format(BaseUuidFormat, 0x0002);
public static string TelemetryUuid => string.Format(BaseUuidFormat, 0x0003);
public static string EventUuid => string.Format(BaseUuidFormat, 0x0004);
public static string CommandUuid => string.Format(BaseUuidFormat, 0x0005);
public static string CaptureResultUuid => string.Format(BaseUuidFormat, 0x0006);
public static string LinkStateUuid => string.Format(BaseUuidFormat, 0x0007);
public static string BulkOtaUuid => string.Format(BaseUuidFormat, 0x0008);
public static string ClaimUuid => string.Format(BaseUuidFormat, 0x0009);
}