Skip to content

Instantly share code, notes, and snippets.

@bwedding
Created February 21, 2023 15:31
Show Gist options
  • Save bwedding/09ff9d825057f98dd6c5d4d9395bcc89 to your computer and use it in GitHub Desktop.
Save bwedding/09ff9d825057f98dd6c5d4d9395bcc89 to your computer and use it in GitHub Desktop.
CanHeader
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SRHStressTester
{
public enum HeartCommand
{
StartHoming = 0,
StartPumping = 1,
GoToAuto = 2,
GoToManual = 3,
Stop = 4,
SendAllValues = 100
}
public enum StrokeForm
{
Linear,
Sine,
FastSine
}
public enum MessagePriority { Default = 5 };
public enum MessageType { Undefined = 0, ByteValue = 1, BooleanValue = 2, TextMessage = 3, PhasePortions = 4, StrokeTiming = 5, Int32Value = 6 };
public enum DataSource { Right = 0, Left = 1, ControlUnit = 2, PCApplication = 3 }
public enum StatusMessageLevel { None = 0, Error = 64, Warning = 65, Info = 66, InvalidCommand = 67 }
public enum OperationState
{
Undefined = 0,
Init = 1,
Startup = 3,
Homing = 4,
HomingDone = 5,
Manual = 7,
Auto = 8,
HomingFailed = 9,
InitFailed = 10,
Stopped = 11
}
public enum AlivenessState
{
Dead = 0,
Alive = 1
}
public enum Value
{
// Common values
NoMessage = 0,
Command = 1, // ByteValue
PhasePortions = 4, // CanMessagePhasePortions
OperationMode = 6, // ByteValue
LoggingMode = 7, // BooleanValue
StrokeCount = 8, // Int32Value
CpuLoad = 9, // ByteValue
StatusCode = 10, // ByteValue StatusCodeValue
HeartRunning = 11, // ByteValue
LeftMCUStatus = 12, // ByteValue
RightMCUStatus = 13, // ByteValue
UseMedicalSensor = 14, // BooleanValue
StopI2CCommunication = 15, // BooleanValue
ExternalRelease = 16, // Int32 Value
HCBRelease = 17, // Int32 Value
PumpID = 18, // Int32 Value
BeatsPerformed = 19, // Int32Value
StrokeTiming = 20, // CanMessageStrokeTiming
StrokeForm = 21, // ByteValue (Linear, Sine) enum StrokeForm
ActualMechanicalVersion = 22, // ByteValue (V11B or V11C)
CurrentConsumption = 23, // Int32Value ??
StrokeLengthActual = 24, // Int32Value
HeartRateActual = 25, // ByteValue
PowerConsumption = 26, // Int32Value
BatteryPower = 28, // Int32Value
InstantaneousPower = 29, // Int32Value
AVPlanePosition = 30, // Int32Value
DesiredCurrent = 31, // Int32Value
Time = 32,
GraphOutputMode = 33, // BooleanValue
MaxPowerConsumption = 43, // Int32Value
AverageDesiredCurrent = 44, // Int32Value
MaxDesiredCurrent = 45, // Int32Value
InstantaneousCurrent = 46, // Int32Value
DesiredPosition = 50, // Int32Value
InstantaneousVoltage = 51, // Int32Value
Vbus = 52,
NewInstantaneousPower = 53, // Int32Value
NewMaxPower = 54, // Int32Value
NewAveragePower = 55, // Int32Value
QCurrent = 56, // Int32Value
QCurrentSystolicAvg = 57, // Int32Value
QCurrentMax = 58, // Int32Value
EstimatedPressure = 59, // Int32Value
PowerLosses = 60, // Int32Value
InvalidCommand = 67, // Received when the heart has received an invalid command
FlowLimit = 68, // Int32Value
FlowLimitState = 69, // ByteValue "Decrease", 1 "Allow increasing", 2 "Keep constant", 3
StartCalibration = 72, // BooleanValue
Check_Serializer_Errors = 73, // BooleanValue
PhaseCurrentMax = 74, // Int32Value
PhaseCurrentAvg = 75, // Int32Value
TriggerResetLeft = 76, // BooleanValue
TriggerResetRight = 77, // BooleanValue
UseManualValue = 78, // BooleanValue
AtmosphericPressure = 79, // Int32Value
// Left side values
LeftStrokeLengthTarget = 100, // Int32Value
LeftStrokeVolumeTarget = 103, // Int32Value
LeftDesiredAtriumPressure = 104, // Int32Value
LeftOutput = 105, // Int32Value
LeftAtriumPressure = 106, // Int32Value
LeftTemperature = 107, // Int32Value
LeftCalPressure = 108, // Int32Value
LeftMedicalPressure = 109, // Int32Value
LeftIntPressure = 110, // Int32Value
LeftOffset = 111, // Int32Value
LeftSensorTemperature = 112, // Int32Value
LeftGain = 113, // Int32Value
// Right side values
RightStrokeLengthTarget = 200, // Int32Value
RightStrokeVolumeTarget = 203, // Int32Value
RightDesiredAtriumPressure = 204, // Int32Value
RightOutput = 205, // Int32Value
RightAtriumPressure = 206, // Int32Value
RightTemperature = 207, // Int32Value
RightCalPressure = 208, // Int32Value
RightMedicalPressure = 209, // Int32Value
RightIntPressure = 210, // Int32Value
RightOffset = 211, // Int32Value
RightSensorTemperature = 212, // Int32Value
RightGain = 213 // Int32Value
}
public enum StatusCodeValue
{
Unknown = 0,
SystemStarted = 1,
RecoveryAfterReset = 2,
WatchdogReset = 3,
HallSensorFault = 4,
RuntimeException = 5,
DriverCircuitFault = 6,
TopEndpointNotFound = 7,
BottomEndpointNotFound = 8,
SoftwareReset = 9,
HeartSyncFault = 10,
DriverCircuitWarning = 11,
InsufficientVoltage = 12,
TruncatedPIDOutput = 13
}
}
using System;
using System.Collections.Generic;
using System.Runtime.Remoting.Messaging;
namespace SRHStressTester
{
public class CanHeader
{
private const int PRIO_BITS = 0x700;
private const int PRIO_OFFSET = 8;
private const int MESSAGE_TYPE_BITS = 0x38;
private const int MESSAGE_TYPE_OFFSET = 3;
private const int DATA_SOURCE_BITS = 0x3;
private const int DATA_SOURCE_OFFSET = 0;
public CanHeader(int id)
{
ParsePrio(id);
ParseMessageType(id);
ParseSource(id);
}
private void ParsePrio(int id)
{
int tmp = (id & PRIO_BITS) >> PRIO_OFFSET;
if (Enum.IsDefined(typeof(MessagePriority), tmp))
{
Prio = (MessagePriority)tmp;
}
else
{
Prio = MessagePriority.Default;
}
}
private void ParseMessageType(int id)
{
int tmp = (id & MESSAGE_TYPE_BITS) >> MESSAGE_TYPE_OFFSET;
if (Enum.IsDefined(typeof(MessageType), tmp))
{
MessageType = (MessageType)tmp;
}
else
{
MessageType = MessageType.Undefined;
IsInvalid = true;
//ErrorHandler.Log(ErrorType.Error, $"Unkown message type {tmp} received!");
}
}
private void ParseSource(int id)
{
int tmp = (id & DATA_SOURCE_BITS) >> DATA_SOURCE_OFFSET;
if (Enum.IsDefined(typeof(DataSource), tmp))
{
Source = (DataSource)tmp;
}
else
{
Source = DataSource.Left;
IsInvalid = true;
//ErrorHandler.Log(ErrorType.Error, $"Unkown data source {tmp} received!");
}
}
public CanHeader(MessagePriority prio, MessageType messageType, DataSource dataSource)
{
Prio = prio;
MessageType = messageType;
Source = dataSource;
}
public int RawHeader
{
get
{
int result = 0;
result = result | ((int)Prio << PRIO_OFFSET);
result = result | ((int)Source << DATA_SOURCE_OFFSET);
result = result | ((int)MessageType << MESSAGE_TYPE_OFFSET);
return result;
}
}
public MessagePriority Prio { get; private set; }
public DataSource Source { get; private set; }
public MessageType MessageType { get; private set; }
public bool IsInvalid { get; private set; }
}
public abstract class CanMessage : IMessage
{
protected CanMessage(CanHeader header, byte[] data)
{
Header = header;
RawData = data;
}
public CanHeader Header { get; private set; }
public byte[] RawData { get; protected set; }
public abstract byte ValueId { get; }
public MessagePriority Prio => Header.Prio;
public DataSource Source => Header.Source;
public MessageType MessageType => Header.MessageType;
public bool IsInvalid => Header.IsInvalid;
}
public class CanMessageBool : CanMessage, IBoolMessage
{
private readonly byte valueId;
public CanMessageBool(CanHeader header, byte[] data) : base(header, data)
{
if (RawData.Length >= 2)
{
valueId = RawData[0];
Value = RawData[1] > 0;
}
}
public override byte ValueId => valueId;
public bool Value { get; private set; }
}
public class CanMessageByte : CanMessage, IByteMessage
{
private byte valueId;
public CanMessageByte(CanHeader header, byte[] data) : base(header, data)
{
if (RawData.Length >= 2)
{
valueId = RawData[0];
Value = RawData[1];
}
else
{
valueId = 0;
Value = 0;
}
}
public override byte ValueId => valueId;
public byte Value { get; private set; }
}
public class CanMessageInt32 : CanMessage, IInt32Message
{
private readonly byte _valueId;
public CanMessageInt32(CanHeader header, byte[] data) : base(header, data)
{
if (RawData.Length >= 8)
{
_valueId = RawData[0];
Value = BitConverter.ToInt32(RawData, 1);
}
else
{
_valueId = 0;
Value = 0;
}
}
public CanMessageInt32(CanHeader header, byte valueId, int value) : base(header, new byte[8])
{
List<byte> rawData = new List<byte>();
rawData.Add(valueId);
rawData.AddRange(BitConverter.GetBytes(value));
RawData = rawData.ToArray();
_valueId = RawData[0];
Value = BitConverter.ToInt32(RawData, 1);
}
public override byte ValueId => _valueId;
public int Value { get; private set; }
}
public class CanMessageString : CanMessage, IStringMessage
{
public CanMessageString(CanHeader header, byte[] data) : base(header, data)
{
if (Enum.IsDefined(typeof(StatusMessageLevel), (int)RawData[0]))
{
Type = (StatusMessageLevel)RawData[0];
}
else
{
Type = StatusMessageLevel.InvalidCommand;
}
EndOfMessage = false;
int endOfStringIndex = RawData.Length;
for (int i = 1; i < RawData.Length; i++)
{
if (RawData[i] == 0)
{
EndOfMessage = true;
endOfStringIndex = i;
break;
}
}
Value = System.Text.Encoding.Default.GetString(RawData, 1, endOfStringIndex - 1);
}
public override byte ValueId => 0;
public StatusMessageLevel Type { get; private set; }
public string Value { get; private set; }
public bool EndOfMessage { get; private set; }
}
public class CanMessageStrokeTiming : CanMessage, IStrokeTimingMessage
{
private readonly byte valueId;
public CanMessageStrokeTiming(CanHeader header, byte[] data) : base(header, data)
{
valueId = RawData[0];
Systole = BitConverter.ToUInt16(RawData, 1);
SystolePause = RawData[3];
Diastole = BitConverter.ToUInt16(RawData, 4);
DiastolePause = RawData[6];
}
public CanMessageStrokeTiming(CanHeader header, byte valueId, ushort systole, byte systolePause,
ushort diastole, byte diastolePause) : base(header, new byte[8])
{
this.valueId = RawData[0] = valueId;
Array.Copy(BitConverter.GetBytes(systole), 0, RawData, 1, 2);
SystolePause = RawData[3] = systolePause;
Array.Copy(BitConverter.GetBytes(diastole), 0, RawData, 4, 2);
DiastolePause = RawData[6] = diastolePause;
Systole = BitConverter.ToUInt16(RawData, 1);
Diastole = BitConverter.ToUInt16(RawData, 4);
}
public ushort Systole { get; protected set; }
public byte SystolePause { get; protected set; }
public ushort Diastole { get; protected set; }
public byte DiastolePause { get; protected set; }
public override byte ValueId => valueId;
}
public class TextMessage : ITextMessage
{
public TextMessage(StatusMessageLevel type, string text, DataSource source)
{
TimeStamp = DateTime.Now;
MessageType = type;
Message = text;
Source = source;
}
public DataSource Source
{
get;
private set;
}
public DateTime TimeStamp
{
get;
private set;
}
public string Message
{
get;
private set;
}
public StatusMessageLevel MessageType
{
get;
private set;
}
}
public class MessageReceivedEventArgs<T> : EventArgs
{
public MessageReceivedEventArgs(T msg)
{
Message = msg;
}
public T Message { get; private set; }
}
public class CanTextMessageBuilder
{
private string currentString = String.Empty;
public CanTextMessageBuilder(DataSource dataSource)
{
Source = dataSource;
}
public DataSource Source { get; private set; }
public event EventHandler<MessageReceivedEventArgs<ITextMessage>> MessageReceived = delegate { };
}
public class CanMessagePhasePortions : CanMessage, IPhasePortionsMessage
{
private readonly byte valueId;
public CanMessagePhasePortions(CanHeader header, byte[] data) : base(header, data)
{
if (RawData.Length >= 3)
{
valueId = RawData[0];
Systole = RawData[1];
Diastole = RawData[2];
}
}
public override byte ValueId => valueId;
public byte Diastole { get; private set; }
public byte Systole { get; private set; }
}
public class CanMessageParser
{
public CanHeader ReadHeader(int header)
{
return new CanHeader(header);
}
public CanMessageByte ParseByteMessage(CanHeader header, byte[] data)
{
var value = new CanMessageByte(header, data);
return value;
}
public CanMessageInt32 ParseInt32Message(CanHeader header, byte[] data)
{
var value = new CanMessageInt32(header, data);
return value;
}
public CanMessageBool ParseBoolMessage(CanHeader header, byte[] data)
{
var value = new CanMessageBool(header, data);
return value;
}
public CanMessageString ParseTextMessage(CanHeader header, byte[] data)
{
var value = new CanMessageString(header, data);
return value;
}
public CanMessagePhasePortions ParsePhasePortionsMessage(CanHeader header, byte[] data)
{
var value = new CanMessagePhasePortions(header, data);
return value;
}
public CanMessageStrokeTiming ParseStrokeTimingMessage(CanHeader header, byte[] data)
{
var value = new CanMessageStrokeTiming(header, data);
return value;
}
}
}
using System;
namespace SRHStressTester
{
public interface IAlivenessHandler
{
AlivenessState CurrentAlivenessState(DataSource source);
event EventHandler<AlivenessState> AlivenessChangedEvent;
event EventHandler<ITextMessage> MessageReceived;
}
public interface IMessage
{
MessagePriority Prio { get; }
DataSource Source { get; }
MessageType MessageType { get; }
bool IsInvalid { get; }
}
public interface IValueMessage : IMessage
{
byte ValueId { get; }
}
public interface IBoolMessage : IValueMessage
{
bool Value { get; }
}
public interface IByteMessage : IValueMessage
{
byte Value { get; }
}
public interface IInt32Message : IValueMessage
{
int Value { get; }
}
public interface IStringMessage : IMessage
{
StatusMessageLevel Type { get; }
string Value { get; }
bool EndOfMessage { get; }
}
public interface IStrokeTiming
{
ushort Systole { get; }
byte SystolePause { get; }
ushort Diastole { get; }
byte DiastolePause { get; }
}
public interface IStrokeTimingMessage : IValueMessage, IStrokeTiming
{ }
public interface ITextMessage
{
DataSource Source { get; }
DateTime TimeStamp { get; }
string Message { get; }
StatusMessageLevel MessageType { get; }
}
public interface IPhasePortionsMessage : IValueMessage
{
byte Diastole { get; }
byte Systole { get; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment