diff --git a/.vscode/launch.json b/.vscode/launch.json
index a8c1260..4f33f50 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -2,15 +2,12 @@
"version": "0.2.0",
"configurations": [
{
- // Use IntelliSense to find out which attributes exist for C# debugging
- // Use hover for the description of the existing attributes
- // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
- "name": ".NET Core Launch (console)",
+ "name": "Grap from serial 1",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
- "program": "${workspaceFolder}/bin/Debug/net6.0/RS485 Monitor.dll",
+ "program": "${workspaceFolder}/RS485 Monitor/bin/Debug/net8.0/RS485 Monitor.dll",
"args": [""],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
@@ -18,41 +15,20 @@
"stopAtEntry": false
},
{
- // Use IntelliSense to find out which attributes exist for C# debugging
- // Use hover for the description of the existing attributes
- // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
- "name": ".NET Core Launch with args",
+ "name": "Read from file",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
- "program": "${workspaceFolder}/bin/Debug/net6.0/RS485 Monitor.dll",
- "args": ["${workspaceFolder}\\..\\Traces\\raw_20230501_103841.bin", "-g"],
+ "program": "${workspaceFolder}/RS485 Monitor/bin/Debug/net8.0/RS485 Monitor.dll",
+ "args": [
+ "${workspaceFolder}\\..\\Traces\\raw_20230501_103841.bin",
+ "-g"
+ ],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
- {
- // Use IntelliSense to find out which attributes exist for C# debugging
- // Use hover for the description of the existing attributes
- // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
- "name": ".NET Core Launch Helptext",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "build",
- // If you have changed target frameworks, make sure to update the program path.
- "program": "${workspaceFolder}/bin/Debug/net6.0/RS485 Monitor.dll",
- "args": ["--version"],
- "cwd": "${workspaceFolder}",
- // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
- "console": "internalConsole",
- "stopAtEntry": false
- },
- {
- "name": ".NET Core Attach",
- "type": "coreclr",
- "request": "attach"
- }
]
-}
\ No newline at end of file
+}
diff --git a/README.md b/README.md
index 988702d..63516cd 100644
--- a/README.md
+++ b/README.md
@@ -307,10 +307,10 @@ Current fed into or taken from the battery. Values < 0 describe discharge of the
| 6 | 3 | 96 | Ctrl | Ctrl (6,0) | |
| 6 | 4 | 95 | Ctrl | Ctrl (6,6) | |
| 6 | 5 | 94 | Battery | Disconnect | |
-| 6 | 6 | 93 | Battery | Battery (8,1) | |
-| 6 | 7 | 92 | Battery | Battery (8,0) | |
+| 6 | 6 | 93 | Battery | Battery (8,1) | charge current to high |
+| 6 | 7 | 92 | Battery | Battery (8,0) | charging stopped |
| 7 | 0 | 91 | Battery | Battery[Temp] >= 3B(60°C) | |
-| 7 | 1 | 90 | Battery | Battery (8,2) | |
+| 7 | 1 | 90 | Battery | Battery (8,2) | discharge current too high |
| 7 | 2 | 89 | Battery | Battery (8,5) | |
| 7 | 3 | 88 | Battery | Battery (8,7) | |
| 7 | 4 | 87 | X | | |
diff --git a/RS485 Monitor/src/TelegramParser.cs b/RS485 Monitor/src/TelegramParser.cs
index ac4ea5d..88de773 100644
--- a/RS485 Monitor/src/TelegramParser.cs
+++ b/RS485 Monitor/src/TelegramParser.cs
@@ -132,7 +132,7 @@ public void ParseChunk(byte[] rawData)
///
/// Parse the given file
///
- ///
+ ///
public void ParseFile(string filePath)
{
FileInfo info = new(filePath);
@@ -199,28 +199,23 @@ private void FinishBlock()
return null;
}
- // Check if we can convert
- if ( tg.Type == BaseTelegram.TelegramType.READ_RESPONSE)
+ // Define known telegrams
+ Dictionary knownTelegrams = new()
{
- if (tg.Source == 0xAA && tg.Destination == 0x5A && tg.PDU.Length == 10)
- {
- tg = new BatteryStatus(tg);
- }
- else if (tg.Source == 0xAA && tg.Destination == 0xDA && tg.PDU.Length == 10)
- {
- tg = new ECUStatus(tg);
- }
- }
-
- else if (tg.Type == BaseTelegram.TelegramType.READ_REQUEST)
+ {ControllerRequest.TELEGRAM_ID, typeof(ControllerRequest) },
+ {ControllerResponse.TELEGRAM_ID, typeof(ControllerResponse) },
+ {BatteryRequest.TELEGRAM_ID, typeof(BatteryRequest) },
+ {BatteryResponse.TELEGRAM_ID, typeof(BatteryResponse) },
+ {SpeedometerRequest.TELEGRAM_ID, typeof(SpeedometerRequest) },
+ {SpeedometerResponse.TELEGRAM_ID, typeof(SpeedometerResponse) },
+ };
+
+ // try to fetch the special telegram type
+ if (knownTelegrams.TryGetValue(tg.Id, out Type? specialType))
{
- if (tg.Source == 0xBA && tg.Destination == 0xAA && tg.PDU.Length == GSMStatus.RAW_DATA_LEN)
- {
- tg = new GSMStatus(tg);
- }
+ tg = (BaseTelegram?)Activator.CreateInstance(specialType, [tg]);
}
return tg;
}
-
-}
\ No newline at end of file
+}
diff --git a/RS485 Monitor/src/Telegrams/BaseTelegram.cs b/RS485 Monitor/src/Telegrams/BaseTelegram.cs
index 6b5beb5..ddc3f4d 100644
--- a/RS485 Monitor/src/Telegrams/BaseTelegram.cs
+++ b/RS485 Monitor/src/Telegrams/BaseTelegram.cs
@@ -1,7 +1,7 @@
using NLog;
///
-/// This class defines a basic telegram with data. No special parsing of the
+/// This class defines a basic telegram with data. No special parsing of the
/// content is done here. Check the specialized classes for more detail
///
public class BaseTelegram : IEquatable
@@ -11,86 +11,141 @@ public class BaseTelegram : IEquatable
///
static readonly Logger log = LogManager.GetCurrentClassLogger();
- #region Properties
+
+ #region Constants
///
- /// Start Sequence of the telegram
+ /// Maximum supported data length
///
- public UInt16 Start { get => (UInt16)((Raw[0] << 8) + Raw[1]); }
+ private const byte MAX_DATA_LEN = 32;
///
- /// Source of the telegram
+ /// Minimum length of a telegram. This contains the following data:
+ /// - Type (2 bytes)
+ /// - Destination (1 byte)
+ /// - Source (1 byte)
+ /// - Data length (1 byte)
+ /// - Checksum (1 byte)
+ /// - End byte (1 byte)
///
- public byte Source { get => Raw[POS_SRC]; }
+ private const byte MIN_DATA_LEN = 7;
///
- /// Destination of the telegram
+ /// End byte of the telegram
///
- public byte Destination { get => Raw[POS_DES]; }
+ private const byte END_TELEGRAM = 0x0D;
///
- /// User Data
+ /// Offset of the high Byte of the the telegram type
///
- public byte[] PDU { get; }
+ private const byte POS_TYPE_H = 0;
+ ///
+ /// Offset of the low Byte of the the telegram type
+ ///
+ private const byte POS_TYPE_L = 1;
+ ///
+ /// Position of the destination id in the raw data
+ ///
+ private const byte POS_DES = 2;
+ ///
+ /// Position of the source id in the raw data
+ ///
+ private const byte POS_SRC = 3;
+ ///
+ /// Position of the data length in the raw data
+ ///
+ private const byte POS_LEN = 4;
+ #endregion
+ #region Properties
///
- /// Received checksum
+ /// Type of the telegram as raw value
///
- public byte Checksum { get => Raw[POS_LEN + PDU.Length + 1]; }
+ public UInt16 RawType
+ {
+ get => (UInt16)((Raw[POS_TYPE_H] << 8) + Raw[POS_TYPE_L]);
+ }
///
- /// Copy of the received raw data of the telegram
+ /// Destination / recipient of the telegram
///
- public byte[] Raw { get; }
+ public byte Destination { get => Raw[POS_DES]; }
///
- /// Are the raw data valid against the Checksum
+ /// Source / sender of the telegram
///
- public bool Valid { get; }
+ public byte Source { get => Raw[POS_SRC]; }
- public enum TelegramType
+ ///
+ /// Identifier for the telegram type. Is is a mixture of the telegram type,
+ /// the destination and the source.
+ ///
+ public UInt16 Id
{
- READ_REQUEST = 0xC55C,
- READ_RESPONSE = 0xB66B
+ get => (UInt16)(Destination << 8 | Source);
}
+
///
- /// Type of the telegram
+ /// User Data / specific to the telegram
///
- public TelegramType Type { get => (TelegramType)Start; }
-
- #endregion
+ public byte[] PDU { get; }
- #region Constants
///
- /// Maximum supported data length
+ /// Received checksum
///
- private const byte MAX_DATA_LEN = 32;
+ public byte Checksum { get => Raw[POS_LEN + PDU.Length + 1]; }
///
- /// End byte of the telegram
+ /// Complete telegram as raw data
///
- private const byte END_TELEGRAM = 0x0D;
+ public byte[] Raw { get; }
///
- /// Position of the source id in the raw data
+ /// The raw PDU is valid against the received checksum
///
- private const byte POS_SRC = 2;
+ public bool Valid
+ {
+ get
+ {
+ byte calcCheck = Raw[POS_LEN];
+ foreach (byte b in PDU)
+ {
+ calcCheck ^= b;
+ }
+
+ log.Trace($"Checksum: read={Checksum.ToString("X2")}, calc={calcCheck.ToString("X2")}");
+ return calcCheck == Checksum;
+ }
+ }
+
///
- /// Position of the destination id in the raw data
+ /// Possible types of the telegram
///
- private const byte POS_DES = 3;
+ public enum TelegramType
+ {
+ ///
+ /// Request telegram triggered by bus master to the unit
+ ///
+ REQUEST = 0xC55C,
+ ///
+ /// Telegram sent as a response to a request from the unit to the bus master
+ ///
+ RESPONSE = 0xB66B
+ }
+
///
- /// Position of the data length in the raw data
+ /// Type of the telegram
///
- private const byte POS_LEN = 4;
+ public TelegramType Type { get => (TelegramType)RawType; }
+
#endregion
///
- /// Internal constructor for specialized classes
+ /// Internal constructor for specialized classes. Create empty arrays
///
protected BaseTelegram()
{
- PDU = new byte[1];
- Raw = new byte[1];
+ PDU = Array.Empty();
+ Raw = Array.Empty();
}
///
@@ -104,56 +159,52 @@ protected BaseTelegram(BaseTelegram c)
this.PDU = new byte[c.PDU.Length];
Array.Copy(c.PDU, this.PDU, this.PDU.Length);
-
- this.Valid = c.Valid;
}
///
/// Create a new base telegram based on the given raw data
///
/// raw data of one telegram
- /// Given data length in the raw data is invalid.
+ /// Raw data is null.
+ /// Raw data is too short.
+ /// Invalid data length in the raw data.
+ /// Raw data does not contain End tag.
public BaseTelegram(byte[] rawData)
{
+ // Basic validation
+ ArgumentNullException.ThrowIfNull(rawData);
+ if (rawData.Length < MIN_DATA_LEN)
+ {
+ throw new ArgumentException("Raw data is too short");
+ }
+
// Copy raw data
Raw = new byte[rawData.Length];
Array.Copy(rawData, Raw, Raw.Length);
// fetch user data
- byte dataLen = rawData[POS_LEN];
- if (dataLen > MAX_DATA_LEN)
+ byte pduLen = rawData[POS_LEN];
+ if (pduLen > MAX_DATA_LEN)
{
- throw new ArgumentException($"Invalid data len {dataLen}. Max supported: {MAX_DATA_LEN}");
+ throw new ArgumentException($"Invalid data len {pduLen}. Max supported: {MAX_DATA_LEN}");
}
- PDU = new byte[dataLen];
+
+ // copy user data to PDU array
+ PDU = new byte[pduLen];
Array.Copy(rawData, POS_LEN + 1, PDU, 0, PDU.Length);
- /* Verify checksum
- * Checksum is calculated as XOR of the user data, including the length byte
- */
- byte calcCheck = dataLen;
- foreach (byte b in PDU)
- {
- calcCheck ^= b;
- }
- log.Trace($"Checksum: read={Checksum.ToString("X2")}, calc={calcCheck.ToString("X2")}");
- Valid = (calcCheck == Checksum);
+ // Verify checksum
if (Valid == false)
{
- log.Warn($"Telegram has an invalid checksum read:{Checksum.ToString("X2")}, calc:{calcCheck.ToString("X2")}");
+ log.Warn("Telegram has an invalid checksum");
}
- // Check end telegram
- try
+ // Check end telegram value
+ int pos_end_tag = MIN_DATA_LEN - 1 + pduLen;
+ if (rawData.Length < (pos_end_tag + 1) || rawData[pos_end_tag] != END_TELEGRAM)
{
- if (rawData.Length < (5 + 1 + dataLen) || rawData[5 + 1 + dataLen] != END_TELEGRAM)
- {
- log.Warn("RawData does not hold Endtag");
- }
- }
- catch (IndexOutOfRangeException)
- {
- log.Warn("Rawdata does not contain End tag");
+ log.Error("RawData does not hold Endtag");
+ throw new ArgumentException("Raw data does not contain End tag");
}
}
@@ -192,8 +243,18 @@ public virtual string ToStringDetailed()
return ToString();
}
+ ///
+ /// Compare two telegrams based on the raw data
+ ///
+ /// Second object to compare
+ ///
public bool Equals(BaseTelegram? other)
{
- return Array.Equals(this.Raw, other?.Raw);
+ if (other == null)
+ {
+ return false;
+ }
+ return this.Raw.SequenceEqual(other.Raw);
}
-}
\ No newline at end of file
+
+}
diff --git a/RS485 Monitor/src/Telegrams/BatteryRequest.cs b/RS485 Monitor/src/Telegrams/BatteryRequest.cs
new file mode 100644
index 0000000..5da716d
--- /dev/null
+++ b/RS485 Monitor/src/Telegrams/BatteryRequest.cs
@@ -0,0 +1,53 @@
+using NLog;
+
+///
+/// Specialization of the BaseTelegram class that extracts the information
+/// of BatteryStatus
+///
+public class BatteryRequest : BaseTelegram
+{
+ ///
+ /// Internal logging object
+ ///
+ private static readonly Logger log = LogManager.GetCurrentClassLogger();
+
+ #region Constants
+ ///
+ /// Required size of PDU data
+ ///
+ private const byte PDU_LENGTH = 1;
+
+
+ private const byte SOURCE = (byte)Units.ECU;
+ private const byte DESTINATION = (byte)Units.BATTERY;
+ public const UInt16 TELEGRAM_ID = DESTINATION << 8 | SOURCE;
+ #endregion
+
+ ///
+ /// Create a new Battery Status object based on a received telegram
+ ///
+ /// Raw telegram
+ /// Raw data has an unexpected length
+ public BatteryRequest(BaseTelegram t)
+ : base(t)
+ {
+ if (t.PDU.Length != PDU_LENGTH)
+ {
+ throw new ArgumentException($"Unexpected size of {t.PDU.Length}");
+ }
+ if (t.Source != SOURCE || t.Destination != DESTINATION)
+ {
+ throw new ArgumentException("Not a BatteryResponse telegram");
+ }
+ }
+
+ ///
+ /// Get a string representation of the telegram
+ ///
+ /// String representation
+ public override string ToString()
+ {
+ log.Trace(base.ToString());
+ return "Battery Request";
+ }
+}
diff --git a/RS485 Monitor/src/Telegrams/BatteryStatus.cs b/RS485 Monitor/src/Telegrams/BatteryResponse.cs
similarity index 85%
rename from RS485 Monitor/src/Telegrams/BatteryStatus.cs
rename to RS485 Monitor/src/Telegrams/BatteryResponse.cs
index c3867f9..2153fae 100644
--- a/RS485 Monitor/src/Telegrams/BatteryStatus.cs
+++ b/RS485 Monitor/src/Telegrams/BatteryResponse.cs
@@ -2,10 +2,10 @@
using NLog;
///
-/// Specialization of the BaseTelegram class that extracts the information
+/// Specialization of the BaseTelegram class that extracts the information
/// of BatteryStatus
///
-public class BatteryStatus : BaseTelegram
+public class BatteryResponse : BaseTelegram
{
///
/// Internal logging object
@@ -73,7 +73,7 @@ public enum VBreakerStatus
///
/// Position of charge / discharge current in PDU
///
- private const byte POS_CHARGE = 3;
+ private const byte POS_CURRENT = 3;
///
/// Positiion of high byte of number of charging cycles in PDU
///
@@ -91,13 +91,17 @@ public enum VBreakerStatus
///
private const byte POS_DISCYCLE_L = 7;
///
- /// Position of VBreaker information
+ /// Position of error information
///
- private const byte POS_VBREAKER= 8;
+ private const byte POS_ERROR_CODE= 8;
///
/// Position of charging information in PDU
///
private const byte POS_CHARGING = 9;
+
+ private const byte SOURCE = (byte)Units.BATTERY;
+ private const byte DESTINATION = (byte)Units.ECU;
+ public const UInt16 TELEGRAM_ID = DESTINATION << 8 | SOURCE;
#endregion
#region Properties
@@ -114,13 +118,13 @@ public enum VBreakerStatus
///
public sbyte Temperature { get => (sbyte)PDU[POS_TEMP]; }
///
- /// Current VBreaker status
+ /// error code
///
- public VBreakerStatus VBreaker{ get => (VBreakerStatus)PDU[POS_VBREAKER]; }
+ public VBreakerStatus VBreaker{ get => (VBreakerStatus)PDU[POS_ERROR_CODE]; }
///
- /// Current Charge or Discharge in Amps
+ /// Charge or discharge current in Amps
///
- public sbyte Charge { get => (sbyte)PDU[POS_CHARGE]; }
+ public sbyte Current { get => (sbyte)PDU[POS_CURRENT]; }
///
/// Total number of charging cycles
///
@@ -151,13 +155,17 @@ public bool Charging
///
/// Raw telegram
/// Raw data has an unexpected length
- public BatteryStatus(BaseTelegram t)
+ public BatteryResponse(BaseTelegram t)
: base(t)
{
if (t.PDU.Length != TELEGRAM_SIZE)
{
throw new ArgumentException($"Unexpected size of {t.PDU.Length}");
}
+ if (t.Source != SOURCE || t.Destination != DESTINATION)
+ {
+ throw new ArgumentException("Not a BatteryResponse telegram");
+ }
}
///
@@ -167,8 +175,8 @@ public BatteryStatus(BaseTelegram t)
public override string ToString()
{
log.Trace(base.ToString());
- return $"Battery Status: {Voltage}V, {SoC}%, {Temperature}°C, {Charge} Amp, " +
+ return $"Battery Response: {Voltage}V, {SoC}%, {Temperature}°C, {Current} Amp, " +
$"Charged: {Cycles}x, Discharged: {DischargeCycles}x, VBreaker: {VBreaker}, " +
$"Activity: {Activity}, Charging: {Charging}";
}
-}
\ No newline at end of file
+}
diff --git a/RS485 Monitor/src/Telegrams/ControllerRequest.cs b/RS485 Monitor/src/Telegrams/ControllerRequest.cs
new file mode 100644
index 0000000..c9bd15f
--- /dev/null
+++ b/RS485 Monitor/src/Telegrams/ControllerRequest.cs
@@ -0,0 +1,84 @@
+using NLog;
+
+///
+/// Specialization of the BaseTelegram class that extracts the information
+/// of ControllerResponse
+///
+public class ControllerRequest : BaseTelegram
+{
+ private static readonly Logger log = LogManager.GetCurrentClassLogger();
+
+
+ #region Constants
+ private const byte POS_CHARGE_STATE = 1;
+ private const byte RAW_DATA_LEN = 2;
+
+ public const byte SOURCE = (byte)Units.ECU;
+ public const byte DESTINATION = (byte)Units.ENGINE_CONTROLLER;
+ public const UInt16 TELEGRAM_ID = DESTINATION << 8 | SOURCE;
+ #endregion
+
+ ///
+ /// Possible parking states
+ ///
+ public enum ChargingState
+ {
+ CHARGING_ON = 0x01,
+ CHARGING_OFF = 0x00,
+ ///
+ /// Invalid / unknown parking state
+ ///
+ CHARGING_UNKNOWN = 0xFF
+ }
+
+ #region Properties
+ public ChargingState Charge
+ {
+ get
+ {
+ if (Enum.IsDefined(typeof(ChargingState), (int)PDU[POS_CHARGE_STATE]))
+ {
+ return (ChargingState)PDU[POS_CHARGE_STATE];
+ }
+ else
+ {
+ return ChargingState.CHARGING_UNKNOWN;
+ }
+ }
+ }
+ ///
+ /// Simple property if vehicle is parking
+ ///
+ public bool IsCharging { get => Charge == ChargingState.CHARGING_ON; }
+ #endregion
+
+ ///
+ /// Create a new ControllerResponse Telegram with the information of BaseTelegram
+ ///
+ /// BaseTelegram that holds the raw data
+ /// Thrown when the size of the PDU is
+ /// unexpected or the telegram is not a ControllerResponse telegram
+ public ControllerRequest(BaseTelegram t)
+ : base(t)
+ {
+ if (t.PDU.Length != RAW_DATA_LEN)
+ {
+ throw new ArgumentException("Unexpected size");
+ }
+
+ if (t.Source != SOURCE || t.Destination != DESTINATION)
+ {
+ throw new ArgumentException("Not a ControllerRequest telegram");
+ }
+ }
+
+ ///
+ /// Get a string representation of the telegram
+ ///
+ /// String representation
+ public override string ToString()
+ {
+ log.Trace(base.ToString());
+ return $"Controller Request: {Charge}";
+ }
+}
diff --git a/RS485 Monitor/src/Telegrams/ControllerResponse.cs b/RS485 Monitor/src/Telegrams/ControllerResponse.cs
new file mode 100644
index 0000000..423c681
--- /dev/null
+++ b/RS485 Monitor/src/Telegrams/ControllerResponse.cs
@@ -0,0 +1,112 @@
+using System.Reflection.Metadata;
+using NLog;
+using NLog.Config;
+
+///
+/// Specialization of the BaseTelegram class that extracts the information
+/// of ControllerResponse
+///
+public class ControllerResponse : BaseTelegram
+{
+ private static readonly Logger log = LogManager.GetCurrentClassLogger();
+
+
+ #region Constants
+ private const byte POS_GEAR = 0;
+ private const byte POS_CURRENT_H = 1;
+ private const byte POS_CURRENT_L = 2;
+ private const byte POS_SPEED_H = 3;
+ private const byte POS_SPEED_L = 4;
+ private const byte POS_TEMP = 5;
+ private const byte POS_ERROR_CODE = 6;
+ private const byte POS_PARKING = 8;
+
+ public const byte RAW_DATA_LEN = 10;
+
+ public const byte SOURCE = (byte) Units.ENGINE_CONTROLLER;
+ public const byte DESTINATION = (byte) Units.ECU;
+ public const UInt16 TELEGRAM_ID = DESTINATION << 8 | SOURCE;
+ #endregion
+
+ ///
+ /// Possible parking states
+ ///
+ public enum ParkStatus
+ {
+ PARKING_ON = 0x02,
+ PARKING_OFF = 0x01,
+ ///
+ /// Invalid / unknown parking state
+ ///
+ PARKING_UNKNOWN = 0xFF
+ }
+
+ #region Properties
+ public byte Gear { get => PDU[POS_GEAR]; }
+ ///
+ /// Current in Ampere
+ ///
+ public float Current { get => (float)((ushort)((PDU[POS_CURRENT_H] << 8) + PDU[POS_CURRENT_L]) * 0.1); }
+ ///
+ /// Current speed in km/h
+ ///
+ public float Speed { get => (float) ((ushort)((PDU[POS_SPEED_H] << 8) + PDU[POS_SPEED_L]) * 0.028); }
+ ///
+ /// Simple error code extraction
+ ///
+ /// Define error codes
+ public byte ErrorCode { get => PDU[POS_ERROR_CODE]; }
+ ///
+ /// Temperature in °C
+ ///
+ public sbyte Temperature { get => (sbyte)PDU[POS_TEMP]; }
+ ///
+ /// Status of parking
+ ///
+ public ParkStatus Parking {
+ get {
+ ParkStatus status = ParkStatus.PARKING_UNKNOWN;
+ if (Enum.IsDefined(typeof(ParkStatus), (int)PDU[POS_PARKING]))
+ {
+ status = (ParkStatus)PDU[POS_PARKING];
+ }
+ return status;
+ }
+ }
+ ///
+ /// Simple property if vehicle is parking
+ ///
+ public bool IsParking { get => Parking == ParkStatus.PARKING_ON; }
+ #endregion
+
+ ///
+ /// Create a new ControllerResponse Telegram with the information of BaseTelegram
+ ///
+ /// BaseTelegram that holds the raw data
+ /// Thrown when the size of the PDU is
+ /// unexpected or the telegram is not a ControllerResponse telegram
+ public ControllerResponse(BaseTelegram t)
+ : base(t)
+ {
+ if (t.PDU.Length != RAW_DATA_LEN)
+ {
+ throw new ArgumentException("Unexpected size");
+ }
+
+ if (t.Source != SOURCE || t.Destination != DESTINATION)
+ {
+ throw new ArgumentException("Not a ControllerResponse telegram");
+ }
+ }
+
+ ///
+ /// Get a string representation of the telegram
+ ///
+ /// String representation
+ public override string ToString()
+ {
+ log.Trace(base.ToString());
+ string current_invariant = string.Create(System.Globalization.CultureInfo.InvariantCulture, $"{Current}");
+ return $"Controller Response: Mode {Gear}, {current_invariant}A, {Speed}km/h, {Temperature}°C, Parking: {Parking}";
+ }
+}
diff --git a/RS485 Monitor/src/Telegrams/ECUStatus.cs b/RS485 Monitor/src/Telegrams/ECUStatus.cs
deleted file mode 100644
index 23e53b6..0000000
--- a/RS485 Monitor/src/Telegrams/ECUStatus.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using NLog;
-
-///
-/// Specialization of the BaseTelegram class that extracts the information
-/// of ECUStatus
-///
-public class ECUStatus : BaseTelegram
-{
- private static readonly Logger log = LogManager.GetCurrentClassLogger();
-
- #region Constants
- private const byte POS_PDU = 0;
- private const byte POS_CURRENT_H = 1;
- private const byte POS_CURRENT_L = 2;
- private const byte POS_SPEED_H = 3;
- private const byte POS_SPEED_L = 4;
- private const byte POS_TEMP = 5;
- private const byte POS_PARKING = 8;
-
- public const byte RAW_DATA_LEN = 10;
- #endregion
-
- ///
- /// Possible parking states
- ///
- public enum ParkStatus
- {
- PARKING_ON = 0x02,
- PARKING_OFF = 0x01,
- }
-
- #region Properties
- public byte Mode { get => PDU[POS_PDU]; }
- ///
- /// Current in Ampere
- ///
- public float Current { get => (float)((ushort)((PDU[POS_CURRENT_H] << 8) + PDU[POS_CURRENT_L]) * 0.1); }
- ///
- /// Current speed in km/h
- ///
- public float Speed { get => (float) ((ushort)((PDU[POS_SPEED_H] << 8) + PDU[POS_SPEED_L]) * 0.028); }
- ///
- /// Temperature in °C
- ///
- public sbyte Temperature { get => (sbyte)PDU[POS_TEMP]; }
- public ParkStatus Parking { get => (ParkStatus)PDU[POS_PARKING]; }
- public bool IsParking { get => Parking == ParkStatus.PARKING_ON; }
-
- #endregion
-
- ///
- /// Create a new ECUStatus Telegram with the information of BaseTelegram
- ///
- /// BaseTelegram that holds the raw data
- /// Unexpected size
- public ECUStatus(BaseTelegram t)
- : base(t)
- {
- if (t.PDU.Length != RAW_DATA_LEN)
- {
- throw new ArgumentException("Unexpected size");
- }
- }
-
- ///
- /// Get a string representation of the telegram
- ///
- /// String representation
- public override string ToString()
- {
- log.Trace(base.ToString());
- return $"ECU Status: Mode {Mode}, {Current}A, {Speed}km/h, {Temperature}°C, Parking: {IsParking}";
- }
-}
\ No newline at end of file
diff --git a/RS485 Monitor/src/Telegrams/ErrorCode.cs b/RS485 Monitor/src/Telegrams/ErrorCode.cs
new file mode 100644
index 0000000..4ffc52f
--- /dev/null
+++ b/RS485 Monitor/src/Telegrams/ErrorCode.cs
@@ -0,0 +1,41 @@
+public class ErrorCode{
+ private UInt16 raw;
+
+ [Flags]
+ public enum ErrorBits {
+ CTRL_DISCONNECT_99 = 0x0001,
+ CTRL_ERROR_98 = 0x0002,
+ CTRL_ERROR_97 = 0x0004,
+ CTRL_ERROR_96 = 0x0008,
+ CTRL_ERROR_95 = 0x0010,
+ ///
+ /// Battery is disconnected
+ ///
+ BATTERY_DISCONNECT_94 = 0x0020,
+ ///
+ /// Charging current too high
+ ///
+ BATTERY_CHARGE_CURRENT_93 = 0x0040,
+ ///
+ /// Charging has stopped
+ ///
+ BATTERY_CHARGE_STOPPED_92 = 0x0080,
+ ///
+ /// Battery over temperature
+ ///
+ BATTERY_OVERTEMP_91 = 0x0100,
+ ///
+ /// Discharge current too high
+ ///
+ BATTERY_DISCHARGE_CURRENT_90 = 0x0200,
+ BATTERY_ERROR_89 = 0x0400,
+ BATTERY_ERROR_88 = 0x0800,
+ }
+
+ public ErrorCode(int raw) {
+ this.raw = (UInt16)raw;
+ }
+
+ public ErrorBits State { get => (ErrorBits)this.raw; }
+
+}
diff --git a/RS485 Monitor/src/Telegrams/SpeedometerRequest.cs b/RS485 Monitor/src/Telegrams/SpeedometerRequest.cs
new file mode 100644
index 0000000..2e8f6a5
--- /dev/null
+++ b/RS485 Monitor/src/Telegrams/SpeedometerRequest.cs
@@ -0,0 +1,201 @@
+using CommandLine;
+using NLog;
+
+///
+/// Specialization of the BaseTelegram class that extracts the information
+/// of the SpeedometerRequest
+///
+public class SpeedometerRequest : BaseTelegram
+{
+ ///
+ /// Internal class logger
+ ///
+ private static readonly Logger log = LogManager.GetCurrentClassLogger();
+
+ #region Constants
+ ///
+ /// Position of SOC in the PDU
+ ///
+ private const byte POS_SOC = 0;
+ ///
+ /// Position of current in the PDU
+ ///
+ private const byte POS_CTRL_CURRENT = 1;
+ ///
+ /// Position of speed in the PDU
+ ///
+ private const byte POS_SPEED = 2;
+ ///
+ /// Position of temperature level in the PDU
+ ///
+ private const byte POS_TEMP_LEVEL = 3;
+ ///
+ /// Position of hour (Localtime) in the PDU
+ ///
+ private const byte POS_HOUR = 4;
+ ///
+ /// Position of minute in the PDU
+ ///
+ private const byte POS_MINUTE = 5;
+ ///
+ /// Position of error code high Byte in the PDU
+ ///
+ private const byte POS_ERR_1 = 6;
+ ///
+ /// Position of error code low Byte in the PDU
+ ///
+ private const byte POS_ERR_2 = 7;
+ ///
+ /// Position of vehicle state in the PDU
+ ///
+ private const byte POS_VEHICLE_STATE = 8;
+ ///
+ /// Position of gear in the PDU
+ ///
+ private const byte POS_GEAR = 9;
+ ///
+ /// Position of speed high Byte in the PDU
+ ///
+ private const byte POS_SPEED_H = 10;
+ ///
+ /// Position of speed low Byte in the PDU
+ ///
+ private const byte POS_SPEED_L = 11;
+ ///
+ /// Position of range in the PDU
+ ///
+ private const byte POS_RANGE = 13;
+
+ ///
+ /// Destination of the telegram
+ ///
+ private const byte DESTINATION = (byte)Units.SPEEDOMETER;
+ ///
+ /// Source of the telegram
+ ///
+ private const byte SOURCE = (byte)Units.ECU;
+
+ ///
+ /// Calculated telegram ID
+ ///
+ public const UInt16 TELEGRAM_ID = DESTINATION << 8 | SOURCE;
+
+ ///
+ /// Length of the required raw data
+ ///
+ public const byte RAW_DATA_LEN = 14;
+ #endregion
+
+ #region Properties
+
+ ///
+ /// State of Charge in %
+ ///
+ public byte Soc { get => PDU[POS_SOC]; }
+ ///
+ /// Current received from the engine controller in ampere
+ ///
+ public double CtrlCurrent { get => PDU[POS_CTRL_CURRENT] * 2.5; }
+ ///
+ /// Speed in km/h
+ ///
+ public byte Speed { get => PDU[POS_SPEED]; }
+ ///
+ /// Temperature level
+ ///
+ public byte TempLevel { get => PDU[POS_TEMP_LEVEL]; }
+ ///
+ /// Current hour (localtime)
+ ///
+ public byte Hour { get => PDU[POS_HOUR]; }
+ ///
+ /// Current minute (localtime)
+ ///
+ public byte Minutes { get => PDU[POS_MINUTE]; }
+ public ErrorCode ErrorValue {
+ get => new ErrorCode(PDU[POS_ERR_1] << 8 | PDU[POS_ERR_2]);
+ }
+
+ ///
+ /// Current state of the vehicle
+ ///
+ public VehicleState State
+ {
+ get
+ {
+ if (Enum.IsDefined(typeof(VehicleState), (Int32)PDU[POS_VEHICLE_STATE]))
+ {
+ return (VehicleState)PDU[POS_VEHICLE_STATE];
+ }
+ return VehicleState.UNKNOWN;
+ }
+ }
+ ///
+ /// Currently selected gear
+ ///
+ public byte Gear { get => PDU[POS_GEAR]; }
+ ///
+ /// Current speed
+ ///
+ public UInt16 SpeedCtrlValue { get => (UInt16)(PDU[POS_SPEED_H] << 8 | PDU[POS_SPEED_L]); }
+ ///
+ /// Remaining range in km
+ ///
+ public byte Range { get => PDU[POS_RANGE]; }
+
+ #endregion
+
+ ///
+ /// Different states of the vehicle
+ ///
+ public enum VehicleState
+ {
+ ///
+ /// Vehicle is charging
+ ///
+ CHARGING = 4,
+ ///
+ /// Vehicle is parking
+ ///
+ PARKING = 1,
+ ///
+ /// Vehicle is active
+ ///
+ ACTIVE = 0,
+ ///
+ /// Vehicle has an unknown state
+ ///
+ UNKNOWN = 0xFF
+ }
+
+ ///
+ /// Create a new GSMStatus telegram with the information of BaseTelegram
+ ///
+ /// BaseTelegram that holds the raw data
+ /// Unexpected size
+ public SpeedometerRequest(BaseTelegram t)
+ : base(t)
+ {
+ if (t.PDU.Length != RAW_DATA_LEN)
+ {
+ throw new ArgumentException("Unexpected size");
+ }
+ if (t.Source != SOURCE || t.Destination != DESTINATION)
+ {
+ throw new ArgumentException("Not a SpeedometerRequest telegram");
+ }
+ }
+
+ ///
+ /// Get a string representation of the telegram
+ ///
+ /// String representation
+ public override string ToString()
+ {
+ log.Trace(base.ToString());
+ return $"Speedometer Request: Soc {Soc}%, Current {CtrlCurrent}A, " +
+ $"Speed {Speed}km/h, TempLvl {TempLevel}, Time {Hour:d2}:{Minutes:d2}, " +
+ $"Error {ErrorValue.State}, Vehicle State {State}, Gear {Gear}, " +
+ $"Speed Ctrl {SpeedCtrlValue}, Range {Range}km";
+ }
+}
diff --git a/RS485 Monitor/src/Telegrams/GSMStatus.cs b/RS485 Monitor/src/Telegrams/SpeedometerResponse.cs
similarity index 56%
rename from RS485 Monitor/src/Telegrams/GSMStatus.cs
rename to RS485 Monitor/src/Telegrams/SpeedometerResponse.cs
index 05f0cd4..11b33aa 100644
--- a/RS485 Monitor/src/Telegrams/GSMStatus.cs
+++ b/RS485 Monitor/src/Telegrams/SpeedometerResponse.cs
@@ -1,56 +1,56 @@
+using CommandLine;
using NLog;
///
-/// Specialization of the BaseTelegram class that extracts the information
-/// of GSM / GPS Status
+/// Specialization of the BaseTelegram class that extracts the information
+/// of the SpeedometerRequest
///
-public class GSMStatus : BaseTelegram
+public class SpeedometerResponse : BaseTelegram
{
-///
-/// Internal class logger{
-///
+ ///
+ /// Internal class logger
+ ///
private static readonly Logger log = LogManager.GetCurrentClassLogger();
#region Constants
+
///
- /// Position of hour (Localtime)
+ /// Destination of the telegram
///
- private const byte POS_HOUR = 4;
+ private const byte DESTINATION = (byte)Units.ECU;
///
- /// Position of minute
+ /// Source of the telegram
///
- private const byte POS_MINUTE = 5;
-
-///
-/// LEngth of the required raw data
-///
- public const byte RAW_DATA_LEN = 14;
- #endregion
+ private const byte SOURCE = (byte)Units.SPEEDOMETER;
- #region Properties
///
- /// Current hour (localtime)
+ /// Calculated telegram ID
///
- public byte Hour { get => PDU[POS_HOUR]; }
+ public const UInt16 TELEGRAM_ID = DESTINATION << 8 | SOURCE;
+
///
- /// Current minute (localtime)
+ /// Length of the required raw data
///
- public byte Minutes { get => PDU[POS_MINUTE]; }
-
+ public const byte RAW_DATA_LEN = 1;
#endregion
+
///
/// Create a new GSMStatus telegram with the information of BaseTelegram
///
/// BaseTelegram that holds the raw data
/// Unexpected size
- public GSMStatus(BaseTelegram t)
+ public SpeedometerResponse(BaseTelegram t)
: base(t)
{
if (t.PDU.Length != RAW_DATA_LEN)
{
throw new ArgumentException("Unexpected size");
}
+ if (t.Source != SOURCE || t.Destination != DESTINATION)
+ {
+ throw new ArgumentException("Not a SpeedometerResponse telegram");
+ }
}
///
@@ -60,6 +60,6 @@ public GSMStatus(BaseTelegram t)
public override string ToString()
{
log.Trace(base.ToString());
- return $"GSM Status: Time {Hour:d2}:{Minutes:d2}";
+ return $"Speedometer Response";
}
-}
\ No newline at end of file
+}
diff --git a/RS485 Monitor/src/Telegrams/Units.cs b/RS485 Monitor/src/Telegrams/Units.cs
new file mode 100644
index 0000000..017256e
--- /dev/null
+++ b/RS485 Monitor/src/Telegrams/Units.cs
@@ -0,0 +1,22 @@
+///
+/// List of available units in the system
+///
+public enum Units
+{
+ ///
+ /// Main ECU / bus master
+ ///
+ ECU = 0xAA,
+ ///
+ /// Engine controller
+ ///
+ ENGINE_CONTROLLER = 0xDA,
+ ///
+ /// Battery management system
+ ///
+ BATTERY = 0x5A,
+ ///
+ /// Speedometer
+ ///
+ SPEEDOMETER = 0xBA
+}
diff --git a/tests/BaseTelegramTest.cs b/tests/BaseTelegramTest.cs
index 86f8791..2a5a2cd 100644
--- a/tests/BaseTelegramTest.cs
+++ b/tests/BaseTelegramTest.cs
@@ -6,40 +6,112 @@ public class BaseTelegramTest
[Test]
public void ParseTelegramSuccessful()
{
- byte[] raw = new byte[] { 0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D };
+ byte[] raw = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D];
BaseTelegram telegram = new(raw);
- Assert.That(telegram.Type, Is.EqualTo(BaseTelegram.TelegramType.READ_RESPONSE));
- Assert.That(telegram.Destination, Is.EqualTo(0xDA));
- Assert.That(telegram.Source, Is.EqualTo(0xAA));
+ Assert.That(telegram.Type, Is.EqualTo(BaseTelegram.TelegramType.RESPONSE));
+ Assert.That(telegram.Destination, Is.EqualTo(0xAA));
+ Assert.That(telegram.Source, Is.EqualTo(0xDA));
Assert.That(telegram.Valid, Is.EqualTo(true));
}
[Test]
public void ParseTelegramRequestSuccessful()
{
- byte[] raw = new byte[] { 0xC5, 0x5C, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D };
+ byte[] raw = [0xC5, 0x5C, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D];
BaseTelegram telegram = new(raw);
- Assert.That(telegram.Type, Is.EqualTo(BaseTelegram.TelegramType.READ_REQUEST));
- Assert.That(telegram.Destination, Is.EqualTo(0xDA));
- Assert.That(telegram.Source, Is.EqualTo(0xAA));
+ Assert.That(telegram.Type, Is.EqualTo(BaseTelegram.TelegramType.REQUEST));
+ Assert.That(telegram.Destination, Is.EqualTo(0xAA));
+ Assert.That(telegram.Source, Is.EqualTo(0xDA));
Assert.That(telegram.Valid, Is.EqualTo(true));
}
[Test]
public void ParseTelegramWrongChecksum()
{
- byte[] raw = new byte[] { 0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x10, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D };
+ byte[] raw = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x10, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D];
BaseTelegram telegram = new(raw);
- Assert.That(telegram.Type, Is.EqualTo(BaseTelegram.TelegramType.READ_RESPONSE));
- Assert.That(telegram.Destination, Is.EqualTo(0xDA));
- Assert.That(telegram.Source, Is.EqualTo(0xAA));
+ Assert.That(telegram.Type, Is.EqualTo(BaseTelegram.TelegramType.RESPONSE));
+ Assert.That(telegram.Destination, Is.EqualTo(0xAA));
+ Assert.That(telegram.Source, Is.EqualTo(0xDA));
Assert.That(telegram.Valid, Is.EqualTo(false));
}
+ [Test]
+ public void ParseTelegramTooShort()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xAA];
+
+ var ex = Assert.Throws(() => new BaseTelegram(raw));
+ Assert.That(ex.Message, Is.EqualTo("Raw data is too short"));
+ }
+
+ [Test]
+ public void ParseTelegramInvalidDataLength()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xAA, 0xDA, 0x21, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D];
+
+ var ex = Assert.Throws(() => new BaseTelegram(raw));
+ Assert.That(ex.Message, Is.EqualTo("Invalid data len 33. Max supported: 32"));
+ }
+
+ [Test]
+ public void ParseTelegramMissingEndTag()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C];
+
+ var ex = Assert.Throws(() => new BaseTelegram(raw));
+ Assert.That(ex.Message, Is.EqualTo("Raw data does not contain End tag"));
+ }
+
+ [Test]
+ public void ToStringTest()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D];
+
+ BaseTelegram telegram = new(raw);
+
+ string expected = "B6 6B AA DA 0A 02 00 04 00 00 13 00 00 02 01 1C 0D ";
+ Assert.That(telegram.ToString(), Is.EqualTo(expected));
+ }
+
+ [Test]
+ public void ToStringDetailedTest()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D];
+
+ BaseTelegram telegram = new(raw);
+
+ string expected = "B6 6B AA DA 0A 02 00 04 00 00 13 00 00 02 01 1C 0D ";
+ Assert.That(telegram.ToStringDetailed(), Is.EqualTo(expected));
+ }
+
+ [Test]
+ public void EqualsTest()
+ {
+ byte[] raw1 = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D];
+ byte[] raw2 = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D];
+
+ BaseTelegram telegram1 = new(raw1);
+ BaseTelegram telegram2 = new(raw2);
+
+ Assert.That(telegram1.Equals(telegram2), Is.EqualTo(true));
+ }
+
+ [Test]
+ public void NotEqualsTest()
+ {
+ byte[] raw1 = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D];
+ byte[] raw2 = [0xC5, 0x5C, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D];
+
+ BaseTelegram telegram1 = new(raw1);
+ BaseTelegram telegram2 = new(raw2);
+
+ Assert.That(telegram1.Equals(telegram2), Is.EqualTo(false));
+ }
}
diff --git a/tests/BatteryRequestTest.cs b/tests/BatteryRequestTest.cs
new file mode 100644
index 0000000..b2594a3
--- /dev/null
+++ b/tests/BatteryRequestTest.cs
@@ -0,0 +1,54 @@
+namespace RS485_Monitor.tests;
+using NUnit.Framework;
+
+public class BatteryRequestTest
+{
+ [Test]
+ public void BatteryRequestSuccessful()
+ {
+ byte[] raw = [0xC5, 0x5C, 0x5A, 0xAA, 0x01, 0x00, 0x01, 0x0D];
+
+ BaseTelegram baseTelegram = new(raw);
+ BatteryRequest batteryRequest = new(baseTelegram);
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(batteryRequest.Type, Is.EqualTo(BaseTelegram.TelegramType.REQUEST));
+ Assert.That(batteryRequest.Destination, Is.EqualTo((byte)Units.BATTERY));
+ Assert.That(batteryRequest.Source, Is.EqualTo((byte)Units.ECU));
+ Assert.That(batteryRequest.Valid, Is.EqualTo(true));
+ }
+ }
+
+ [Test]
+ public void BatteryRequestInvalidPDUSize()
+ {
+ byte[] raw = [0xC5, 0x5C, 0x5A, 0xAA, 0x02, 0x00, 0x01, 0x02, 0x0D];
+
+ BaseTelegram baseTelegram = new(raw);
+ var ex = Assert.Throws(() => new BatteryRequest(baseTelegram));
+ Assert.That(ex.Message, Is.EqualTo("Unexpected size of 2"));
+ }
+
+ [Test]
+ public void BatteryRequestInvalidSourceOrDestination()
+ {
+ byte[] raw = [0xC5, 0x5C, 0xBB, 0xCC, 0x01, 0x00, 0x01, 0x0D];
+
+ BaseTelegram baseTelegram = new(raw);
+ var ex = Assert.Throws(() => new BatteryRequest(baseTelegram));
+ Assert.That(ex.Message, Is.EqualTo("Not a BatteryResponse telegram"));
+ }
+
+ [Test]
+ public void BatteryRequestToString()
+ {
+ byte[] raw = [0xC5, 0x5C, 0x5A, 0xAA, 0x01, 0x00, 0x01, 0x0D];
+
+ BaseTelegram baseTelegram = new(raw);
+ BatteryRequest batteryRequest = new(baseTelegram);
+
+ string expected = "Battery Request";
+ Assert.That(batteryRequest.ToString(), Is.EqualTo(expected));
+ }
+}
diff --git a/tests/BatteryResponseTest.cs b/tests/BatteryResponseTest.cs
new file mode 100644
index 0000000..321c5c2
--- /dev/null
+++ b/tests/BatteryResponseTest.cs
@@ -0,0 +1,76 @@
+namespace RS485_Monitor.tests;
+using NUnit.Framework;
+
+public class BatteryResponseTest
+{
+ private BaseTelegram? batteryBase = null;
+
+ [SetUp]
+ public void Setup()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xAA, 0x5A, 0x0A, 0x4D, 0x48, 0x17, 0x00, 0x00, 0x23, 0x00, 0x0B, 0x00, 0x00, 0x30, 0x0D];
+ batteryBase = new BaseTelegram(raw);
+ }
+
+ [Test]
+ public void ConvertToBatteryReponseSuccess()
+ {
+ BatteryResponse status = new(batteryBase!);
+ Assert.That(status, Is.Not.Null);
+ }
+
+ [Test]
+ public void ConvertToBatteryResponseFail()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xAA, 0x5A, 0x09, 0x4D, 0x48, 0x17, 0x00, 0x00, 0x23, 0x00, 0x0B, 0x00, 0x30, 0x0D];
+ BaseTelegram invalidTelegram = new(raw);
+
+ Assert.Throws(() => new BatteryResponse(invalidTelegram));
+ }
+
+ [Test]
+ public void CheckContent()
+ {
+ BatteryResponse status = new(batteryBase!);
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(status.Voltage, Is.EqualTo(77));
+ Assert.That(status.SoC, Is.EqualTo(72));
+ Assert.That(status.Temperature, Is.EqualTo(23));
+ Assert.That(status.Current, Is.EqualTo(0x0));
+ Assert.That(status.Cycles, Is.EqualTo(35));
+ Assert.That(status.DischargeCycles, Is.EqualTo(11));
+ Assert.That(status.VBreaker, Is.EqualTo(BatteryResponse.VBreakerStatus.OK));
+ Assert.That(status.Activity, Is.EqualTo(BatteryResponse.BatteryActivity.NO_ACTIVITY));
+ Assert.That(status.Charging, Is.False);
+ }
+ }
+
+ [Test]
+ public void ToStringTest()
+ {
+ BatteryResponse status = new(batteryBase!);
+ string expected = "Battery Response: 77V, 72%, 23°C, 0 Amp, Charged: 35x, Discharged: 11x, VBreaker: OK, Activity: NO_ACTIVITY, Charging: False";
+ Assert.That(status.ToString(), Is.EqualTo(expected));
+ }
+
+ [Test]
+ public void BatteryResponseInvalidSourceOrDestination()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xBB, 0xCC, 0x0A, 0x4D, 0x48, 0x17, 0x00, 0x00, 0x23, 0x00, 0x0B, 0x00, 0x00, 0x30, 0x0D];
+ BaseTelegram invalidTelegram = new(raw);
+
+ var ex = Assert.Throws(() => new BatteryResponse(invalidTelegram));
+ Assert.That(ex.Message, Is.EqualTo("Not a BatteryResponse telegram"));
+ }
+
+ [Test]
+ public void BatteryResponseInvalidPDUSize()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xAA, 0x5A, 0x0B, 0x4D, 0x48, 0x17, 0x00, 0x00, 0x23, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x30, 0x0D];
+ BaseTelegram invalidTelegram = new(raw);
+
+ var ex = Assert.Throws(() => new BatteryResponse(invalidTelegram));
+ Assert.That(ex.Message, Is.EqualTo("Unexpected size of 11"));
+ }
+}
diff --git a/tests/BatteryStatusTest.cs b/tests/BatteryStatusTest.cs
deleted file mode 100644
index de8da92..0000000
--- a/tests/BatteryStatusTest.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-namespace RS485_Monitor.tests;
-using NUnit.Framework;
-
-public class BatteryStatusTest
-{
- private BaseTelegram? batteryBase = null;
-
- [SetUp]
- public void Setup()
- {
- byte[] raw = new byte[] { 0xB6, 0x6B, 0xAA, 0x5A, 0x0A, 0x4D, 0x48, 0x17, 0x00, 0x00, 0x23, 0x00, 0x0B, 0x00, 0x00, 0x30, 0x0D };
- batteryBase = new BaseTelegram(raw);
- }
-
- [Test]
- public void ConvertToBatteryStatusSuccess()
- {
- BatteryStatus status = new(batteryBase!);
- Assert.That(status, Is.Not.Null);
- }
-
- [Test]
- public void ConvertToBatteryStatusFail()
- {
- byte[] raw = new byte[] { 0xB6, 0x6B, 0xAA, 0x5A, 0x09, 0x4D, 0x48, 0x17, 0x00, 0x00, 0x23, 0x00, 0x0B, 0x00, 0x30, 0x0D };
- BaseTelegram invalidTelegram = new(raw);
-
- Assert.Throws(() => new BatteryStatus(invalidTelegram));
- }
-
- [Test]
- public void CheckContent()
- {
- BatteryStatus status = new(batteryBase!);
- using (Assert.EnterMultipleScope())
- {
- Assert.That(status.Voltage, Is.EqualTo(77));
- Assert.That(status.SoC, Is.EqualTo(72));
- Assert.That(status.Temperature, Is.EqualTo(23));
- Assert.That(status.Charge, Is.EqualTo(0x0));
- Assert.That(status.Cycles, Is.EqualTo(35));
- Assert.That(status.DischargeCycles, Is.EqualTo(11));
- Assert.That(status.VBreaker, Is.EqualTo(BatteryStatus.VBreakerStatus.OK));
- Assert.That(status.Activity, Is.EqualTo(BatteryStatus.BatteryActivity.NO_ACTIVITY));
- Assert.That(status.Charging, Is.False);
- }
- }
-}
diff --git a/tests/ControllerRequestTest.cs b/tests/ControllerRequestTest.cs
new file mode 100644
index 0000000..3ade6d3
--- /dev/null
+++ b/tests/ControllerRequestTest.cs
@@ -0,0 +1,85 @@
+namespace RS485_Monitor.tests;
+using NUnit.Framework;
+
+public class ControllerRequestTest
+{
+ private BaseTelegram? controllerBase = null;
+
+ [SetUp]
+ public void Setup()
+ {
+ byte[] raw = [0xC5, 0x5C, 0xDA, 0xAA, 0x02, 0x00, 0x00, 0x01, 0x0D];
+ controllerBase = new BaseTelegram(raw);
+ }
+
+ [Test]
+ public void ConvertToControllerRequestSuccess()
+ {
+ ControllerRequest request = new(controllerBase!);
+ Assert.That(request, Is.Not.Null);
+ }
+
+ [Test]
+ public void ConvertToControllerRequestFail_InvalidPDUSize()
+ {
+ byte[] raw = [0xC5, 0x5C, 0xDA, 0xAA, 0x03, 0x00, 0x01, 0x02, 0x03, 0x0D];
+ BaseTelegram invalidTelegram = new(raw);
+
+ var ex = Assert.Throws(() => new ControllerRequest(invalidTelegram));
+ Assert.That(ex.Message, Is.EqualTo("Unexpected size"));
+ }
+
+ [Test]
+ public void ConvertToControllerRequestFail_InvalidSourceOrDestination()
+ {
+ byte[] raw = [0xC5, 0x5C, 0xBB, 0xCC, 0x02, 0x00, 0x00, 0x01, 0x0D];
+ BaseTelegram invalidTelegram = new(raw);
+
+ var ex = Assert.Throws(() => new ControllerRequest(invalidTelegram));
+ Assert.That(ex.Message, Is.EqualTo("Not a ControllerRequest telegram"));
+ }
+
+ [Test]
+ public void CheckChargingState_On()
+ {
+ byte[] raw = [0xC5, 0x5C, 0xDA, 0xAA, 0x02, 0x00, 0x01, 0x01, 0x0D];
+ BaseTelegram baseTelegram = new(raw);
+ ControllerRequest request = new(baseTelegram);
+
+ Assert.That(request.Charge, Is.EqualTo(ControllerRequest.ChargingState.CHARGING_ON));
+ Assert.That(request.IsCharging, Is.True);
+ }
+
+ [Test]
+ public void CheckChargingState_Off()
+ {
+ byte[] raw = [0xC5, 0x5C, 0xDA, 0xAA, 0x02, 0x00, 0x00, 0x00, 0x0D];
+ BaseTelegram baseTelegram = new(raw);
+ ControllerRequest request = new(baseTelegram);
+
+ Assert.That(request.Charge, Is.EqualTo(ControllerRequest.ChargingState.CHARGING_OFF));
+ Assert.That(request.IsCharging, Is.False);
+ }
+
+ [Test]
+ public void CheckChargingState_Unknown()
+ {
+ byte[] raw = [0xC5, 0x5C, 0xDA, 0xAA, 0x02, 0x00, 0xFF, 0xFF, 0x0D];
+ BaseTelegram baseTelegram = new(raw);
+ ControllerRequest request = new(baseTelegram);
+
+ Assert.That(request.Charge, Is.EqualTo(ControllerRequest.ChargingState.CHARGING_UNKNOWN));
+ Assert.That(request.IsCharging, Is.False);
+ }
+
+ [Test]
+ public void ToStringTest()
+ {
+ byte[] raw = [0xC5, 0x5C, 0xDA, 0xAA, 0x02, 0x00, 0x01, 0x0F, 0x0D];
+ BaseTelegram baseTelegram = new(raw);
+ ControllerRequest request = new(baseTelegram);
+
+ string expected = "Controller Request: CHARGING_ON";
+ Assert.That(request.ToString(), Is.EqualTo(expected));
+ }
+}
diff --git a/tests/ControllerResponseTest.cs b/tests/ControllerResponseTest.cs
new file mode 100644
index 0000000..15a118d
--- /dev/null
+++ b/tests/ControllerResponseTest.cs
@@ -0,0 +1,96 @@
+namespace RS485_Monitor.tests;
+using NUnit.Framework;
+
+public class ControllerResponseTest
+{
+ private BaseTelegram? ecuBase;
+
+ [SetUp]
+ public void Setup()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D];
+ ecuBase = new BaseTelegram(raw);
+ }
+
+ [Test]
+ public void ConvertToEcuStatusSuccess()
+ {
+ ControllerResponse status = new(ecuBase!);
+ Assert.That(status, Is.Not.Null);
+ }
+
+ [Test]
+ public void CheckContent()
+ {
+ ControllerResponse status = new(ecuBase!);
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(status.Gear, Is.EqualTo(2));
+ Assert.That(status.Current, Is.EqualTo(0.4).Within(0.1));
+ Assert.That(status.Speed, Is.EqualTo(0));
+ Assert.That(status.Temperature, Is.EqualTo(0x13));
+ Assert.That(status.IsParking, Is.EqualTo(true));
+ }
+ }
+
+ [Test]
+ public void CheckParkingStatus_On()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x02, 0x1C, 0x0D];
+ BaseTelegram baseTelegram = new(raw);
+ ControllerResponse response = new(baseTelegram);
+
+ Assert.That(response.Parking, Is.EqualTo(ControllerResponse.ParkStatus.PARKING_ON));
+ Assert.That(response.IsParking, Is.True);
+ }
+
+ [Test]
+ public void CheckParkingStatus_Off()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x01, 0x01, 0x1C, 0x0D];
+ BaseTelegram baseTelegram = new(raw);
+ ControllerResponse response = new(baseTelegram);
+
+ Assert.That(response.Parking, Is.EqualTo(ControllerResponse.ParkStatus.PARKING_OFF));
+ Assert.That(response.IsParking, Is.False);
+ }
+
+ [Test]
+ public void CheckParkingStatus_Unknown()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x03, 0xFF, 0x1C, 0x0D];
+ BaseTelegram baseTelegram = new(raw);
+ ControllerResponse response = new(baseTelegram);
+
+ Assert.That(response.Parking, Is.EqualTo(ControllerResponse.ParkStatus.PARKING_UNKNOWN));
+ Assert.That(response.IsParking, Is.False);
+ }
+
+ [Test]
+ public void ToStringTest()
+ {
+ ControllerResponse status = new(ecuBase!);
+ string expected = "Controller Response: Mode 2, 0.4A, 0km/h, 19°C, Parking: PARKING_ON";
+ Assert.That(status.ToString(), Is.EqualTo(expected));
+ }
+
+ [Test]
+ public void InvalidPDUSize()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xAA, 0xDA, 0x0B, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0xFF, 0x1C, 0x0D];
+ BaseTelegram invalidTelegram = new(raw);
+
+ var ex = Assert.Throws(() => new ControllerResponse(invalidTelegram));
+ Assert.That(ex.Message, Is.EqualTo("Unexpected size"));
+ }
+
+ [Test]
+ public void InvalidSourceOrDestination()
+ {
+ byte[] raw = [0xB6, 0x6B, 0xBB, 0xCC, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D];
+ BaseTelegram invalidTelegram = new(raw);
+
+ var ex = Assert.Throws(() => new ControllerResponse(invalidTelegram));
+ Assert.That(ex.Message, Is.EqualTo("Not a ControllerResponse telegram"));
+ }
+}
diff --git a/tests/ECUStatusTest.cs b/tests/ECUStatusTest.cs
deleted file mode 100644
index 978fbc1..0000000
--- a/tests/ECUStatusTest.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-namespace RS485_Monitor.tests;
-using NUnit.Framework;
-
-public class ECUStatusTest
-{
- private BaseTelegram? ecuBase;
-
- [SetUp]
- public void Setup()
- {
- byte[] raw = new byte[] { 0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D };
- ecuBase = new BaseTelegram(raw);
- }
-
- [Test]
- public void ConvertToEcuStatusSuccess()
- {
- ECUStatus status = new(ecuBase!);
- Assert.That(status, Is.Not.Null);
- }
-
- [Test]
- public void ConvertToEcuStatusFail()
- {
- byte[] raw = new byte[] { 0xC5, 0x5C, 0xBA, 0xAA, 0x0E, 0x48, 0x00, 0x00, 0x00, 0x16, 0x19, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x40, 0x0A, 0x0D };
- BaseTelegram gsmTelegram = new(raw);
-
- Assert.Throws(() => new ECUStatus(gsmTelegram));
- }
-
- [Test]
- public void CheckContent()
- {
- ECUStatus status = new(ecuBase!);
- using (Assert.EnterMultipleScope())
- {
- Assert.That(status.Mode, Is.EqualTo(2));
- Assert.That(status.Current, Is.EqualTo(0.4).Within(0.1));
- Assert.That(status.Speed, Is.EqualTo(0));
- Assert.That(status.Temperature, Is.EqualTo(0x13));
- Assert.That(status.IsParking, Is.EqualTo(true));
- }
-
- }
-}
diff --git a/tests/SpeedometerRequestTest.cs b/tests/SpeedometerRequestTest.cs
new file mode 100644
index 0000000..b22b030
--- /dev/null
+++ b/tests/SpeedometerRequestTest.cs
@@ -0,0 +1,70 @@
+namespace RS485_Monitor.tests;
+using NUnit.Framework;
+
+public class SpeedometerRequestTest
+{
+ private BaseTelegram? speedometerBase;
+
+ [SetUp]
+ public void Setup()
+ {
+ byte[] raw = [0xC5, 0x5C, 0xBA, 0xAA, 0x0E, 0x34, 0x00, 0x00, 0x01, 0x0A, 0x26, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x48, 0x5E, 0x0D];
+ speedometerBase = new BaseTelegram(raw);
+ }
+
+ [Test]
+ public void ConvertToSpeedometerRequestSuccess()
+ {
+ SpeedometerRequest request = new(speedometerBase!);
+ Assert.That(request, Is.Not.Null);
+ }
+
+ [Test]
+ public void ConvertToSpeedometerRequestFail_InvalidPDUSize()
+ {
+ byte[] raw = [0xC5, 0x5C, 0xBA, 0xAA, 0x0F, 0x34, 0x00, 0x00, 0x01, 0x0A, 0x26, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x48, 0x00, 0x5E, 0x0D];
+ BaseTelegram invalidTelegram = new(raw);
+
+ var ex = Assert.Throws(() => new SpeedometerRequest(invalidTelegram));
+ Assert.That(ex.Message, Is.EqualTo("Unexpected size"));
+ }
+
+ [Test]
+ public void ConvertToSpeedometerRequestFail_InvalidSourceOrDestination()
+ {
+ byte[] raw = [0xC5, 0x5C, 0xBC, 0xAA, 0x0E, 0x34, 0x00, 0x04, 0x01, 0x0A, 0x26, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x48, 0x5E, 0x0D];
+ BaseTelegram invalidTelegram = new(raw);
+
+ var ex = Assert.Throws(() => new SpeedometerRequest(invalidTelegram));
+ Assert.That(ex.Message, Is.EqualTo("Not a SpeedometerRequest telegram"));
+ }
+
+ [Test]
+ public void CheckContent()
+ {
+ // 0x34, 0x00, 0x00, 0x01, 0x0A, 0x26, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x48, 0x5E, 0x0D
+ SpeedometerRequest request = new(speedometerBase!);
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(request.Soc, Is.EqualTo(0x34));
+ Assert.That(request.CtrlCurrent, Is.EqualTo(0.0).Within(0.1));
+ Assert.That(request.Speed, Is.EqualTo(0));
+ Assert.That(request.TempLevel, Is.EqualTo(0x01));
+ Assert.That(request.Hour, Is.EqualTo(0x0A));
+ Assert.That(request.Minutes, Is.EqualTo(0x26));
+ Assert.That(request.ErrorValue.State, Is.EqualTo((ErrorCode.ErrorBits)0));
+ Assert.That(request.State, Is.EqualTo(SpeedometerRequest.VehicleState.PARKING));
+ Assert.That(request.Gear, Is.EqualTo(0x01));
+ Assert.That(request.SpeedCtrlValue, Is.EqualTo(0x0001));
+ Assert.That(request.Range, Is.EqualTo(0x48));
+ }
+ }
+
+ [Test]
+ public void ToStringTest()
+ {
+ SpeedometerRequest request = new(speedometerBase!);
+ string expected = "Speedometer Request: Soc 52%, Current 0A, Speed 0km/h, TempLvl 1, Time 10:38, Error 0, Vehicle State PARKING, Gear 1, Speed Ctrl 1, Range 72km";
+ Assert.That(request.ToString(), Is.EqualTo(expected));
+ }
+}
diff --git a/tests/SpeedometerResponseTest.cs b/tests/SpeedometerResponseTest.cs
new file mode 100644
index 0000000..bb36122
--- /dev/null
+++ b/tests/SpeedometerResponseTest.cs
@@ -0,0 +1,49 @@
+namespace RS485_Monitor.tests;
+using NUnit.Framework;
+
+public class SpeedometerResponseTest
+{
+ private BaseTelegram? speedometerBase;
+
+ [SetUp]
+ public void Setup()
+ {
+ byte[] raw = [0xC5, 0x5C, 0xAA, 0xBA, 0x01, 0x00, 0x00, 0x0D];
+ speedometerBase = new BaseTelegram(raw);
+ }
+
+ [Test]
+ public void ConvertToSpeedometerResponseSuccess()
+ {
+ SpeedometerResponse response = new(speedometerBase!);
+ Assert.That(response, Is.Not.Null);
+ }
+
+ [Test]
+ public void ConvertToSpeedometerResponseFail_InvalidPDUSize()
+ {
+ byte[] raw = [0xC5, 0x5C, 0xAA, 0xBA, 0x02, 0x00, 0x00, 0x00, 0x0D];
+ BaseTelegram invalidTelegram = new(raw);
+
+ var ex = Assert.Throws(() => new SpeedometerResponse(invalidTelegram));
+ Assert.That(ex.Message, Is.EqualTo("Unexpected size"));
+ }
+
+ [Test]
+ public void ConvertToSpeedometerResponseFail_InvalidSourceOrDestination()
+ {
+ byte[] raw = [0xC5, 0x5C, 0xBB, 0xCC, 0x01, 0x00, 0x00, 0x0D];
+ BaseTelegram invalidTelegram = new(raw);
+
+ var ex = Assert.Throws(() => new SpeedometerResponse(invalidTelegram));
+ Assert.That(ex.Message, Is.EqualTo("Not a SpeedometerResponse telegram"));
+ }
+
+ [Test]
+ public void ToStringTest()
+ {
+ SpeedometerResponse response = new(speedometerBase!);
+ string expected = "Speedometer Response";
+ Assert.That(response.ToString(), Is.EqualTo(expected));
+ }
+}