Core communication abstraction. Replaces both CCMC and CRP1210Wrapper from the C++ application.
File: src/DIF.Transport/IEcmTransport.cs
public interface IEcmTransport : IDisposable
{
Task<bool> ConnectAsync(TransportConfig config, CancellationToken ct = default);
Task DisconnectAsync();
bool IsConnected { get; }
Task<ReadResult> ReadMessageAsync(CancellationToken ct = default);
Task<bool> WriteMessageAsync(byte ecmMid, int pid, bool ddecUnique);
Task<bool> SendRawMessageAsync(byte[] message);
event EventHandler<MessageReceivedEventArgs>? MessageReceived;
event EventHandler<ConnectionStateEventArgs>? ConnectionStateChanged;
}
Implementations:
SimulatorTransport (DIF.Simulator) - Software ECM simulationMessagePlayer (DIF.Simulator) - Recorded session replayFile: src/DIF.Transport/TransportConfig.cs
public record TransportConfig(
TransportType Type, // Rp1210, Cmc, Simulator
string DriverName = "", // e.g., "MCOM32NT", "DD121032"
string Protocol = "J1708",
int ComPort = 1,
int BaudRate = 9600,
int ReadTimeoutMs = 20
);
File: src/DIF.Transport/ReadResult.cs
public record ReadResult(
bool Success,
byte[] Data,
int Mid,
int RetryCount
)
{
public static ReadResult Failed { get; }
}
File: src/DIF.Transport/TransportType.cs
public enum TransportType { Rp1210, Cmc, Simulator }
File: src/DIF.Transport/J1587Constants.cs
Protocol constants sourced from CMC.h and the J1587 specification.
| Category | Constant | Value | Description |
|---|---|---|---|
| App | AppMid |
180 | DIF application MID |
| ECM MIDs | MidMaster |
128 | Master ECM |
MidReceiver1 |
175 | Receiver 1 | |
MidReceiver2 |
183 | Receiver 2 | |
| Special PIDs | PidDataRequest |
0 | Request data from ECM |
PidDdecUnique |
254 | DDEC custom sub-protocol | |
PidPage2Selector |
255 | Extended PID range | |
| PID Ranges | PidSingleByteMax |
127 | 1 data byte |
PidDoubleByteMax |
191 | 2 data bytes | |
PidMultiByteMax |
253 | Variable length | |
| Communication | MaxRetries |
3 | Retry limit |
ReadSleepTimeMs |
20 | Polling interval | |
MaxMessageSize |
40 | Buffer size | |
MaxJ1708FrameSize |
26 | Wire frame limit | |
| Timeouts | TaskTimeoutDefault |
12000 | Standard (ms) |
TaskTimeoutCco |
2000 | Cylinder cutout (ms) | |
| Task Bases | ReturnBaseSlew |
1000 | Slew task |
ReturnBaseHp |
2000 | HP adjust | |
ReturnBaseInjCal |
3000 | Injector calibration | |
ReturnBaseCco |
4000 | Cylinder cutout | |
ReturnBaseGovGain |
5000 | Governor gain | |
ReturnBaseVsg |
6000 | VSG | |
ReturnBaseRating |
7000 | Rating | |
ReturnBaseClearCodes |
8000 | Clear codes | |
ReturnBaseGetCodes |
9000 | Get codes | |
| Password | EcmPassword |
0x30 x12 |
12 ASCII '0' chars |
| Limits | MaxEcms |
3 | ECMs per engine |
MaxCylindersPerEcm |
8 | Cylinders per ECM |
public class MessageReceivedEventArgs : EventArgs
{
public byte[] Data { get; }
public int Mid { get; }
}
public class ConnectionStateEventArgs : EventArgs
{
public bool IsConnected { get; }
public string Message { get; }
}
Static parameter definition loaded from CSV. Does not change at runtime.
File: src/DIF.Core/Models/PidInfo.cs
| Property | Type | Description |
|---|---|---|
Pid |
int | J1587 PID (0-1511, Page 2 = 256+) |
FullDescription |
string | Human-readable description |
EdmsName |
string | PowerTek database name (e.g., "EC1_RPM") |
Units |
string | Engineering units (e.g., "RPM", "DEG F") |
BitSize |
uint | Data size in bits |
BitOffset |
uint | Bit offset in payload |
ScaleFactor |
float | ScaledValue = RawValue * ScaleFactor |
OnRequest |
bool | Must be explicitly requested (PID 0) |
RequestOnce |
bool | Request only once (identification data) |
DataType |
PidDataType | Extraction type |
MinValue |
float | Display minimum |
MaxValue |
float | Display maximum |
SlewableParam |
int | Non-zero if slewable |
ConvMult |
float | DisplayValue = ScaledValue * ConvMult + ConvAdd |
ConvAdd |
float | Conversion offset |
DecimalPlaces |
int | Display precision |
ByteSize |
int | Raw data byte count |
Runtime value for a single PID. Updated as messages arrive.
File: src/DIF.Core/Models/PidData.cs
| Property | Type | Description |
|---|---|---|
RawValue |
long | Raw integer from message bytes |
ScaledValue |
float | RawValue * ScaleFactor |
DisplayValue |
float | (ScaledValue * ConvMult) + ConvAdd |
Pid |
int | J1587 PID number |
PidInfoIndex |
int | Index into PidInfo array (-1 if unlinked) |
RdbHandle |
int | PowerTek RDB handle (-1 if disconnected) |
EdmsName |
string | EDMS parameter name |
SlewableParam |
int | Slew index |
LastTimeUpdated |
DateTime | Last bus message timestamp |
HasData |
bool | True if updated at least once |
Methods:
Reset() - Clear to "no data" stateApplyScaling(PidInfo info) - Apply full conversion pipelinepublic enum PidDataType { Uns8, Int8, Uns16, Int16, Uns32, Int32, Bit }
Container for one ECM's parameter database.
File: src/DIF.Core/Models/EcmData.cs
| Property | Type | Description |
|---|---|---|
EcmMid |
ushort | MID on J1708 bus |
NumJ1587Pids |
int | Count of PidInfos |
NumEdmsPids |
int | EDMS parameter count |
PidInfos |
List<PidInfo> | Parameter definitions |
PidDatas |
List<PidData> | Runtime values |
Methods:
GetPidDataByName(string edmsName) - Lookup by EDMS nameGetPidDataByPid(int pid) - Lookup by PID numberGetPidInfoByName(string edmsName) - Definition by EDMS nameGetPidInfoByPid(int pid) - Definition by PID numberResetAllData() - Clear all runtime valuesFile: src/DIF.Core/Models/FaultCode.cs
| Property | Type | Description |
|---|---|---|
Fmi |
int | Failure Mode Identifier |
SidOrPid |
int | SID or PID that faulted |
IsSid |
bool | True if SID, false if PID |
IsActive |
bool | True if currently active |
Description |
string | Human-readable description |
OccurrenceCount |
int | Number of occurrences |
File: src/DIF.Core/Models/J1708Message.cs
| Property | Type | Description |
|---|---|---|
Mid |
byte | Source module ID |
Data |
byte[] | Message payload |
Timestamp |
DateTime | Receive timestamp |
Priority |
byte | Bus priority |
Parses Ddec3ec1.csv (15-column format).
File: src/DIF.Core/Config/PidDefinitionLoader.cs
public static class PidDefinitionLoader
{
public static List<PidInfo> Load(string csvPath);
}
CSV columns: PID, description, EDMS name, units, bit size, bit offset, scale factor, data type, on-request flag, request-once flag, min value, max value, slewable param, conversion multiplier, conversion add.
Parses DDECParm.txt (83 EDMS parameter names).
File: src/DIF.Core/Config/EdmsParameterLoader.cs
public static class EdmsParameterLoader
{
public static string[] Load(string txtPath);
}
Parses Codes.csv (fault code definitions).
File: src/DIF.Core/Config/FaultCodeLoader.cs
public static class FaultCodeLoader
{
public static List<FaultCode> Load(string csvPath);
}
Parses Channel.csv (I/O channel definitions).
File: src/DIF.Core/Config/ChannelLoader.cs
public static class ChannelLoader
{
public static Dictionary<int, string> Load(string csvPath);
}
Parses J1587 messages and extracts PID values.
File: src/DIF.Core/Protocol/J1587Parser.cs
public class J1587Parser
{
public J1587Parser(EcmData[] ecmData);
// Parse a complete J1587 message payload
public List<PidUpdate> ProcessJ1587(ReadOnlySpan<byte> msg, int len, int fromMid);
// Map MID to ECM index (0=Master, 1=Receiver1, 2=Receiver2)
public int LookupDeviceMid(int mid);
}
public record PidUpdate(
int Pid, // J1587 PID number
long RawValue, // Extracted raw value
float DisplayValue, // After full conversion
int Mid, // Source ECM MID
string EdmsName = "", // EDMS parameter name
bool IsTaskResponse = false, // Route to TaskManager
int SubPid = 0, // DDEC Unique sub-PID
byte[]? RawBytes = null // Original message bytes
);
Interface for all ECM task operations.
File: src/DIF.Core/Tasks/IEcmTask.cs
public interface IEcmTask
{
Task<bool> SendRequestAsync(IEcmTransport transport, byte toMid);
bool ProcessResponse(int pid, int fromMid, byte[] data);
bool IsComplete { get; }
TaskResult? Result { get; }
}
File: src/DIF.Core/Tasks/TaskResult.cs
public class TaskResult
{
public bool Success { get; init; }
public int StatusCode { get; init; }
public int FromMid { get; init; }
public string StatusMessage { get; init; }
public static TaskResult Succeeded(int fromMid, string message = "Acknowledged.");
public static TaskResult TimedOut(int toMid);
public static TaskResult Cancelled(int toMid);
public static TaskResult Failed(int statusCode, int fromMid, string message);
}
| Constant | Value | Description |
|---|---|---|
Success |
0x000 | Task completed successfully |
Timeout |
0x00F | ECM did not respond |
Cancelled |
0x00D | Task was cancelled |
NoResponse |
0x00B | No response received |
| Code | Message |
|---|---|
| 0 | Acknowledged |
| 1 | Failed - initialization message not received |
| 2 | Failed - engineering bit not set |
| 3 | Failed - invalid data |
| 4 | Failed - EEPROM busy |
| 5 | Failed - invalid condition |
| 6 | Failed - EEPROM failure |
| 7 | Failed - Wrong engine type |
| 8 | Failed - Reset lockout set |
| Code | Message |
|---|---|
| 0 | Acknowledged |
| 1 | Failed - engine hours exceed maximum allowed |
| 2 | Failed - invalid data |
| 3 | Failed - invalid password |
| 4 | Failed - invalid condition |
| 5 | Failed - EEPROM failure |
| 6 | Failed - EEPROM busy |
File: src/DIF.Core/Tasks/TaskManager.cs
public class TaskManager : IDisposable
{
public TaskManager(IEcmTransport transport);
public bool IsActive { get; }
public event EventHandler<TaskCompletedEventArgs>? TaskCompleted;
// Core execution
Task<TaskResult> ExecuteTaskAsync(IEcmTask task, byte toMid,
int timeoutMs = 12000, int maxRetries = 3, CancellationToken ct = default);
Task<TaskResult> ExecuteSequenceAsync(IEcmTask[] steps, byte toMid,
int timeoutMs = 12000, int maxRetries = 3, CancellationToken ct = default);
// Route incoming PID to active task
bool ProcessIncomingPid(int pid, int fromMid, byte[] data);
// Convenience methods
Task<TaskResult> GetDiagnosticCodesAsync(byte toMid, CancellationToken ct);
Task<TaskResult> ClearDiagnosticCodesAsync(int codeType, byte toMid, CancellationToken ct);
Task<TaskResult> ReadGovernorGainAsync(int gainId, byte toMid, CancellationToken ct);
Task<TaskResult> SetGovernorGainAsync(int gainId, float value, byte toMid, CancellationToken ct);
Task<TaskResult> GetVsgAsync(byte toMid, CancellationToken ct);
Task<TaskResult> SetVsgAsync(int min, int alt, int max, byte toMid, CancellationToken ct);
Task<TaskResult> GetRatingAsync(byte toMid, CancellationToken ct);
Task<TaskResult> SetRatingAsync(int ratingValue, byte toMid, CancellationToken ct);
Task<TaskResult> GetInjectorCodesAsync(byte toMid, CancellationToken ct);
Task<TaskResult> SetInjectorCodesAsync(int[] codes, byte toMid, CancellationToken ct);
Task<TaskResult> SlewParameterAsync(int paramId, float targetValue, byte toMid, CancellationToken ct);
Task ResetAsync(byte toMid, CancellationToken ct);
}
| Task | File | Operations | Key PIDs |
|---|---|---|---|
DiagnosticCodesTask |
DiagnosticCodesTask.cs | Get, Clear | 194 (get), 195/196 (clear) |
GovernorGainTask |
GovernorGainTask.cs | Read, Set | 128-135 via PID 166 |
HpAdjustTask |
HpAdjustTask.cs | Set | PID 254 sub-PID |
InjectorCalibrationTask |
InjectorCalibrationTask.cs | Password, Get, Set, Complete | 196/197/198 |
RatingTask |
RatingTask.cs | InitPassword, RatingPassword, Get, Set, Complete | 208/209 via PID 254 |
ResetTask |
ResetTask.cs | Reset | 21/22/23 |
SetChannelTask |
SetChannelTask.cs | Set | 141 (output), 393 (input) |
SlewTask |
SlewTask.cs | Slew | 202 via PID 254 |
VsgTask |
VsgTask.cs | Password, Get, Set, Complete | 207/208 via PID 254 |
Background communication loop replacing MFC worker thread.
File: src/DIF.Core/Services/EcmCommunicationService.cs
public class EcmCommunicationService : IDisposable
{
public EcmCommunicationService(
IEcmTransport transport, J1587Parser parser,
TaskManager taskManager, EcmData[] ecmData);
public bool IsRunning { get; }
public long MessagesReceived { get; }
public long ParseErrors { get; }
public event EventHandler<PidUpdateEventArgs>? PidUpdated;
public event EventHandler<CommunicationErrorEventArgs>? CommunicationError;
Task<bool> StartAsync(TransportConfig config, CancellationToken ct);
Task StopAsync();
Task RequestPidsAsync(int ecmIndex, int[] pids);
Task RequestAllOnRequestPidsAsync();
}
Implements IEcmTransport using software simulation.
File: src/DIF.Simulator/SimulatorTransport.cs
public class SimulatorTransport : IEcmTransport
{
public bool IsConnected { get; }
public EcmSimulator? Simulator { get; }
Task<bool> ConnectAsync(TransportConfig config, CancellationToken ct);
Task<bool> ConnectWithSimulatorAsync(EcmSimulator simulator, CancellationToken ct);
Task DisconnectAsync();
Task<ReadResult> ReadMessageAsync(CancellationToken ct);
Task<bool> WriteMessageAsync(byte ecmMid, int pid, bool ddecUnique);
Task<bool> SendRawMessageAsync(byte[] message);
}
Software ECM that responds to J1587 requests.
File: src/DIF.Simulator/EcmSimulator.cs
public class EcmSimulator
{
public EcmSimulator(EcmState? initialState = null);
public byte SimulatedMid { get; set; } // Default: 128 (Master)
public int BroadcastIntervalMs { get; set; } // Default: 100ms
public EcmState State { get; }
public ChannelReader<byte[]> Responses { get; }
void StartBroadcast();
Task StopBroadcastAsync();
Task ProcessIncomingMessageAsync(byte[] message);
}
Configurable simulator state with 45+ parameters.
File: src/DIF.Simulator/EcmState.cs
| Category | Properties |
|---|---|
| Identification | EngineSerial, EcmSerial, EngineModel, SoftwareVersion, Engine6N4C/D/M, CertificationNumber |
| Operating | Rpm, BatteryVoltage, CoolantTempOut, OilPressure, OilTemperature, FuelRate, IntakeManifoldPressure, IntakeManifoldTemp, AmbientAirTemp, AirTempInlet, BarometricPressure, ThrottlePosition, PercentEngineLoad, PulseWidth, FuelTempInlet, CoolantLevel, EngineHours, TotalFuel, TurboSpeed |
| Calibration | ActiveRating, AlternateRatings[], InjectorCodes[8], VsgMin/Max/Alt, GovernorOverall/Proportional/Integral/Differential, PulseWidthMultiplier, Pwm1-4 |
| Diagnostics | ActiveCodes, HistoricalCodes |
| Communication | IsRunning, AcceptPassword, ResponseDelayMs, SimulateTimeouts |
Methods:
GetValueByEdmsName(string edmsName) - Lookup by EDMS name (e.g., "EC1_RPM")Records J1708 traffic to binary files.
File: src/DIF.Simulator/MessageRecorder.cs
public class MessageRecorder : IDisposable
{
public MessageRecorder(string outputPath);
void RecordMessage(byte[] data, bool isOutbound);
// Binary format: [timestamp_ms:uint32][direction:byte][length:uint16][data:byte[]]
}
Replays recorded sessions, implementing IEcmTransport.
File: src/DIF.Simulator/MessagePlayer.cs
public class MessagePlayer : IEcmTransport
{
public MessagePlayer(string recordingPath);
// Implements full IEcmTransport interface
// Replays messages with original timing
}