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>
This commit is contained in:
150
tests/IfLoc.Sim/Protocol.cs
Normal file
150
tests/IfLoc.Sim/Protocol.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user