Last active
August 11, 2016 21:40
-
-
Save ChrisMoney/0ac23b428ac976d30ddf5a4477d7899c to your computer and use it in GitHub Desktop.
C# : Send low level hexadecimal, binary command to K100 debit card dispenser listening on a serial port.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Linq; | |
using IntercardLogging; | |
using System.IO; | |
using System.Net; | |
using System.IO.Ports; | |
using System.Reflection; | |
using System.Text; | |
using IntercardLogging; | |
namespace Dispenser | |
{ | |
public interface K100A | |
{ | |
void Process(K100ADriver.Command cmd); | |
//void Open(); | |
//void Close(); | |
} | |
public interface IDispenserCom : IDisposable | |
{ | |
void Open(); | |
void Close(); | |
bool IsOpen { get; } | |
void Write(byte[] buffer); | |
void WriteByte(byte value); | |
int ReadByte(); | |
} | |
public class K100ADriver : K100A, IDispenser | |
{ | |
private const byte STX = 0x02; | |
private const byte ETX = 0x03; | |
private const byte ACK = 0x06; | |
private const byte NAK = 0x15; | |
private const byte ENQ = 0x05; | |
private const byte EOT = 0x04; | |
public CommandErrorCode LastError { get; private set; } | |
IDispenserCom _com; | |
private static readonly K100ADriver k100ADriver = new K100ADriver(); | |
private static SerialPort _currentPort; | |
public static SerialPort K100Port { | |
get { return _currentPort; } | |
} | |
public static K100ADriver MakeFromSerialPort(string portName) | |
{ | |
var thisPort = new SerialPort(portName, 9600, Parity.None, 8, StopBits.One); | |
thisPort.Open(); | |
// get something back from machine | |
string version = string.Empty; | |
thisPort.ReadTimeout = 500; | |
thisPort.WriteTimeout = 500; | |
SerialHelper helper = new SerialHelper(thisPort); | |
_currentPort = thisPort; | |
K100ADriver dispenser = new K100ADriver(); | |
dispenser._com = helper; | |
// check connection | |
K100A k100a = new K100ADriver(); | |
if (!CheckConnection(k100a, out version)) | |
{ | |
thisPort.Close(); | |
} | |
return dispenser; | |
} | |
public static K100ADriver MakeFromUSB() | |
{ | |
CH375 driver = new CH375(); | |
driver.Open(); | |
K100ADriver dispenser = new K100ADriver(); | |
return dispenser; | |
} | |
public K100ADriver() | |
{ | |
LastError = CommandErrorCode.OK; | |
} | |
public void Open() | |
{ | |
if (!_currentPort.IsOpen) | |
_currentPort.Open(); | |
} | |
public void Close() | |
{ | |
if (_currentPort.IsOpen) | |
_currentPort.Close(); | |
} | |
public bool ResetAndGetVersion(out string version) | |
{ | |
version = string.Empty; | |
var cmd = new Command(0x30, 0x34); | |
Process(cmd); | |
if (!cmd.IsSuccessful) | |
return false; | |
version = Encoding.ASCII.GetString(cmd.DataOut); | |
return true; | |
} | |
public static bool CheckConnection(K100A handler, out string version) | |
{ | |
version = string.Empty; | |
var cmd = new Command(0x30, 0x34); | |
handler.Process(cmd); | |
if (!cmd.IsSuccessful) | |
return false; | |
version = Encoding.ASCII.GetString(cmd.DataOut); | |
return true; | |
} | |
public bool InitializeAndEject(K100A handler) | |
{ | |
var cmd = new Command(0x33, 0x34); | |
handler.Process(cmd); | |
return cmd.IsSuccessful; | |
} | |
public bool Reset(K100A handler, ResetAction position) | |
{ | |
var cmd = new K100ADriver.Command(0x30, position); | |
handler.Process(cmd); | |
return cmd.IsSuccessful; | |
} | |
public bool GetPosition(K100A handler, out CardPositionStatus cardPosition, out StackerStatus stacker) | |
{ | |
cardPosition = 0; | |
stacker = 0; | |
var cmd = new K100ADriver.Command(0x31, 0x30); | |
handler.Process(cmd); | |
if (!cmd.IsSuccessful) | |
return false; | |
cardPosition = (CardPositionStatus)cmd.DataOut[0]; | |
stacker = (StackerStatus)cmd.DataOut[1]; | |
return true; | |
} | |
public bool GetSensors(K100A handler, out SensorStates states) | |
{ | |
states = new SensorStates(); | |
var cmd = new K100ADriver.Command(0x31, 0x31); | |
handler.Process(cmd); | |
if (!cmd.IsSuccessful) | |
return false; | |
byte[] d = cmd.DataOut; | |
states.C_S1 = d[0] == 0x31; | |
states.C_S2 = d[1] == 0x31; | |
states.C_S3 = d[2] == 0x31; | |
states.C_S4 = d[3] == 0x31; | |
states.C_S5 = d[4] == 0x31; | |
states.C_S6 = d[5] == 0x31; | |
states.B_S1 = d[6] == 0x31; | |
states.B_S2 = d[7] == 0x31; | |
states.LowCardSensor = d[8] == 0x31; | |
return true; | |
} | |
public static string GetEnumDescription(Enum enumValue) | |
{ | |
string enumValueAsString = enumValue.ToString(); | |
var type = enumValue.GetType(); | |
FieldInfo fieldInfo = type.GetField(enumValueAsString); | |
object[] attributes = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); | |
if (attributes.Length > 0) | |
{ | |
var attribute = (DescriptionAttribute)attributes[0]; | |
return attribute.Description; | |
} | |
return enumValueAsString; | |
} | |
public bool GetVoltages(K100A handler, System.IO.Ports.SerialPort sp, out SensorVoltages voltages) | |
{ | |
voltages = new SensorVoltages(); | |
var cmd = new K100ADriver.Command(0x31, 0x32); | |
handler.Process(cmd); | |
if (!cmd.IsSuccessful) | |
return false; | |
byte[] d = cmd.DataOut; | |
voltages.C_S1 = 5f / 1024 * BitConverter.ToUInt16(d, 0); | |
voltages.C_S2 = 5f / 1024 * BitConverter.ToUInt16(d, 2); | |
voltages.C_S3 = 5f / 1024 * BitConverter.ToUInt16(d, 4); | |
voltages.C_S4 = 5f / 1024 * BitConverter.ToUInt16(d, 6); | |
voltages.C_S5 = 5f / 1024 * BitConverter.ToUInt16(d, 8); | |
voltages.C_S6 = 5f / 1024 * BitConverter.ToUInt16(d, 10); | |
return true; | |
} | |
public bool SetCardEntry(K100A handler, CardEntryMode mode) | |
{ | |
var cmd = new K100ADriver.Command(0x32, mode); | |
handler.Process(cmd); | |
return cmd.IsSuccessful; | |
} | |
public bool MoveCard(K100A handler, MoveCardPosition position) | |
{ | |
var cmd = new K100ADriver.Command(0x33, position); | |
handler.Process(cmd); | |
return cmd.IsSuccessful; | |
} | |
public bool DetectICCardType(K100A handler, out IC_CardType cardType) | |
{ | |
cardType = IC_CardType.Unknown; | |
var cmd = new K100ADriver.Command(0x34, 0x30); | |
handler.Process(cmd); | |
if (cmd.IsSuccessful) | |
cardType = (IC_CardType)cmd.DataOut[0]; | |
return cmd.IsSuccessful; | |
} | |
public bool ReadCard(K100A handler, out byte[] acct) | |
{ | |
// Read card on track 2 | |
var cmd = new K100ADriver.Command(0x37, 0x31); | |
handler.Process(cmd); | |
byte[] data = cmd.DataOut; | |
// out entire command and do not convert to string until after it is returned; | |
acct = cmd.DataOut; | |
return cmd.IsSuccessful; | |
} | |
public void Process(Command cmd) | |
{ | |
try | |
{ | |
if (!_currentPort.IsOpen) | |
_currentPort.Open(); | |
LastError = CommandErrorCode.OK; | |
byte[] cmdPacket = cmd.CommandPacket; | |
const int maxRetry = 2; | |
int respCode = 0; | |
int retryCount = 0; | |
CommandState state = CommandState.TryCommand; | |
while (retryCount < maxRetry && state != CommandState.Complete) | |
{ | |
switch (state) | |
{ | |
case CommandState.TryCommand: | |
_currentPort.Write(cmdPacket, 0, cmdPacket.Length); | |
// get response from dispenser | |
try { respCode = _currentPort.ReadByte(); } | |
catch (TimeoutException) { respCode = -1; } | |
// if command fails, try again | |
if (respCode == -1 || respCode == NAK) | |
retryCount++; | |
else if (respCode != ACK) | |
throw new Exception("Unexpected response value: " + respCode); | |
else | |
{ | |
state = CommandState.TryENQ; | |
retryCount = 0; | |
} | |
continue; | |
case CommandState.TryENQ: | |
// try enquiry | |
_currentPort.BaseStream.WriteByte(ENQ); | |
try { respCode = _currentPort.ReadByte(); } | |
catch (TimeoutException) { respCode = -1; } | |
if (respCode == -1) | |
retryCount++; | |
else if (respCode != STX) | |
throw new Exception("Unexpected response value: " + respCode); | |
else | |
{ | |
state = CommandState.ReadResponse; | |
retryCount = 0; | |
} | |
continue; | |
// read response | |
case CommandState.ReadResponse: | |
try | |
{ | |
byte lenHi = (byte)_currentPort.ReadByte(); | |
byte lenLo = (byte)_currentPort.ReadByte(); | |
// command expected back | |
byte expectedBcc = (byte)(STX ^ lenLo ^ lenHi ^ ETX); | |
byte[] arr = new byte[lenHi << 8 | lenLo]; | |
for (int i = 0; i < arr.Length; i++) | |
{ | |
byte b = (byte)_currentPort.ReadByte(); | |
arr[i] = b; | |
expectedBcc ^= b; | |
} | |
byte etx = (byte)_currentPort.ReadByte(); | |
if (etx != ETX) | |
throw new Exception("Unexpected value at ETX: " + etx); | |
// Block Check Character: XOR operation for every byte from STX to ETX | |
byte bcc = (byte)_currentPort.ReadByte(); | |
if (bcc != expectedBcc || arr[1] != cmd.CM || arr[2] != cmd.PM) | |
{ | |
state = CommandState.TryENQ; | |
retryCount++; | |
continue; | |
} | |
if (arr[0] != 'P' && arr[0] != 'N') | |
throw new Exception("Unexpected Command success byte: " + arr[0]); | |
cmd.IsSuccessful = (arr[0] == 'P'); | |
if (cmd.IsSuccessful) | |
{ | |
cmd.DataOut = new byte[arr.Length - 3]; | |
Array.Copy(arr, 3, cmd.DataOut, 0, cmd.DataOut.Length); | |
} | |
else | |
{ | |
LastError = (CommandErrorCode)arr[6]; | |
cmd.ErrorCode = LastError; | |
} | |
state = CommandState.Complete; | |
continue; | |
} | |
catch (TimeoutException) | |
{ | |
state = CommandState.TryENQ; | |
retryCount++; | |
continue; | |
} | |
} | |
} | |
if (retryCount >= maxRetry) | |
{ | |
_currentPort.BaseStream.WriteByte(ENQ); | |
try | |
{ | |
respCode = _currentPort.ReadByte(); | |
throw new TimeoutException(); | |
} | |
catch (TimeoutException) | |
{ | |
throw; | |
} | |
} | |
} | |
finally | |
{ | |
} | |
} | |
public void Dispose() | |
{ | |
_currentPort.Dispose(); | |
_currentPort = null; | |
} | |
private enum CommandState | |
{ | |
TryCommand, | |
TryENQ, | |
ReadResponse, | |
TryEOT, | |
Complete | |
} | |
public class SerialHelper : IDispenserCom | |
{ | |
SerialPort SerialPort { get; set; } | |
public SerialHelper(SerialPort serialPort) | |
{ | |
SerialPort = serialPort; | |
} | |
public void Open() { SerialPort.Open(); } | |
public void Close() { SerialPort.Close(); } | |
public bool IsOpen { get { return SerialPort.IsOpen; }} | |
public void Write(byte[] buffer) { SerialPort.Write(buffer, 0, buffer.Length); } | |
public void WriteByte(byte value) { SerialPort.BaseStream.WriteByte(value); } | |
public int ReadByte() { return SerialPort.ReadByte(); } | |
public void Dispose() { SerialPort.Dispose(); } | |
} | |
public class Command | |
{ | |
public byte CM { get; set; } | |
public byte PM { get; set; } | |
public byte[] DataIn { get; set; } | |
public byte[] DataOut { get; set; } | |
public Func<object[], byte[]> EncodePayload { get; set; } | |
public Func<byte[], object[]> ParsePayload { get; set; } | |
public bool IsSuccessful { get; set; } | |
public CommandErrorCode? ErrorCode { get; set; } | |
public byte[] CommandPacket | |
{ | |
get | |
{ | |
short cmdLen = 2; | |
if (DataIn != null) | |
cmdLen += (short)DataIn.Length; | |
List<byte> buffer = StandardConcat(STX, cmdLen, CM, PM, DataIn, ETX); | |
byte bcc = 0; | |
for (int i = 0; i < buffer.Count; i++) | |
// XOR check sum for command packet | |
bcc ^= buffer[i]; | |
buffer.Add(bcc); | |
return buffer.ToArray(); | |
} | |
} | |
public Command(byte cm, byte pm, Func<object[], byte[]> encodePayload = null) | |
{ | |
CM = cm; | |
PM = pm; | |
EncodePayload = encodePayload; | |
ParsePayload = ParsePayload; | |
} | |
public Command(byte cm, Enum pm, Func<object[], byte[]> encodePayload = null) | |
{ | |
CM = cm; | |
PM = (byte)Convert.ToInt32(pm); | |
EncodePayload = encodePayload; | |
ParsePayload = ParsePayload; | |
} | |
public void SetData(params object[] values) | |
{ | |
if (values == null || values.Length == 0) | |
DataIn = null; | |
else if (EncodePayload != null) | |
DataIn = EncodePayload(values); | |
else | |
DataIn = StandardConcat(values).ToArray(); | |
} | |
public object[] ReadResponse() | |
{ | |
if (ParsePayload == null) | |
return new object[] { DataOut }; | |
return ParsePayload(DataOut); | |
} | |
private static List<byte> StandardConcat(params object[] values) | |
{ | |
List<byte> buffer = new List<byte>(); | |
foreach (object o in values) | |
buffer.AddRange(StandardEncode(o)); | |
return buffer; | |
} | |
private static byte[] StandardEncode(object o) | |
{ | |
if (o == null) | |
return new byte[0]; | |
if (o is Enum) | |
return new byte[] { (byte)Convert.ToInt32(o) }; | |
if (o is byte || o is sbyte || o is char) | |
return new byte[] { (byte)o }; | |
if (o is int || o is uint) | |
// order bytes by Big Endian | |
return BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)o)); | |
if (o is short || o is ushort) | |
return BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)o)); | |
if (o is long || o is ulong) | |
return BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)o)); | |
if (o is byte[]) | |
return (byte[])o; | |
if (o is IEnumerable<byte>) | |
return ((IEnumerable<byte>)o).ToArray(); | |
throw new InvalidDataException("Unexpected value type: " + o.GetType().Name); | |
} | |
} | |
/// <summary> | |
/// Used to determine if port was written to | |
/// </summary> | |
/// <param name="cmd"></param> | |
/// <param name="sp"></param> | |
/// <param name="bytesToSend"></param> | |
public void WriteToSerial(ushort cmd, SerialPort sp, byte[] bytesToSend = null) | |
{ | |
List<byte> buffer = new List<byte>(); | |
buffer.Add(0x02); | |
int commandLen = 2; | |
if (bytesToSend != null) | |
commandLen += bytesToSend.Length; | |
buffer.Add((byte)(commandLen >> 8)); | |
buffer.Add((byte)(commandLen & 0xFF)); | |
buffer.Add((byte)(cmd >> 8)); | |
buffer.Add((byte)(cmd & 0xFF)); | |
if (bytesToSend != null) | |
buffer.AddRange(bytesToSend); | |
buffer.Add(0x03); | |
byte bcc = 0; | |
for (int i = 0; i < buffer.Count; i++) | |
bcc ^= buffer[i]; | |
buffer.Add(bcc); | |
byte[] arr = buffer.ToArray(); | |
sp.Write(arr, 0, arr.Length); | |
} | |
// Interface method | |
public void PositionCardToRead() | |
{ | |
var cmd = new Command(0x30, ResetAction.Mag_Position); | |
K100A handler = new K100ADriver(); | |
handler.Process(cmd); | |
} | |
// Interface method | |
public void Read() | |
{ | |
var cmd = new Command(0x37, ResetAction.Read); | |
K100A handler = new K100ADriver(); | |
handler.Process(cmd); | |
} | |
// Interface method | |
public void Dispense() | |
{ | |
var cmd = new Command(0x30, ResetAction.Eject_Card); | |
K100A handler = new K100ADriver(); | |
handler.Process(cmd); | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment