0%

C# Open Protocol

基于Open Protocol的扳手程序设计
基于TcpIP, 扳手为Server端口一般为4545

数据组成

通讯数据内容均为ASCII格式
数据包含三部分: Header, DataField和MessageEnd

Byte parameter Value
1-4 Length Header+DataField的长度
5-8 MID MessageID
9-11 Revision The revision of the MID
12 No ack lag
13-14 Station ID
15-16 Spindle ID
17-20 Spare

DataField

Byte parameter Value
21-22 01 Parameter ID
23-n Parameter 01 value Parameter 01的内容
n+1-n+2 02 Parameter ID
n+3 Parameter 02 value Parameter 02的内容

MessageEnd

以空字符NUL结尾, ‘\0’或0x00

程序部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
public class Header
{
public int Length { get; set; }
public int Mid { get; set; }
public int Revision { get; set; }
public bool NoAckFlag { get; set; }
public int? StationId { get; set; }
public int? SpindleId { get; set; }
public const int DefaultSize = 20;

public Header()
{
Length = DefaultSize;
}
public override string ToString()
{
var sb = new StringBuilder(Length.ToString("D4"));
//var sb = new StringBuilder();
sb.Append(Mid.ToString("D4"));
sb.Append(Revision.ToString("D3"));
sb.Append(NoAckFlag ? "1" : "0");
sb.Append(" ");
return sb.ToString();
}
}

public class Mid
{
public Header Header { get; set; }
public string? DataFieldsString { get; set; }
public bool PositiveResponse { get => Header.Mid == 5; }
public Mid()
{
Header = new Header();
}

public Mid(Header header)
{
Header = header;
}

public Mid(int mid, int revision, bool noAckFlag = false) : this(new Header()
{
Mid = mid,
Revision = revision,
NoAckFlag = noAckFlag
})
{
}

public virtual Mid AppendDataFields(string dataFields)
{
DataFieldsString = dataFields;
return this;
}

public virtual string Pack()
{
if (DataFieldsString != null)
Header.Length += DataFieldsString.Length;
var builder = new StringBuilder(Header.ToString());
builder.Append(DataFieldsString);
builder.Append('\0');

return builder.ToString();
}

//解析Header
protected virtual Header ProcessHeader(string package)
{
if (package.Length < 20)
{
package = package.PadRight(20, ' ');
}

var header = new Header
{
Length = int.Parse(package.Substring(0, 4)),
Mid = int.Parse(package.Substring(4, 4)),
Revision = int.TryParse(package.Substring(8, 3), out var revision) ? revision : 1,
NoAckFlag = !string.IsNullOrWhiteSpace(package.Substring(11, 1)),
StationId = int.TryParse(package.Substring(12, 2), out var stationId) ? stationId : 1,
SpindleId = int.TryParse(package.Substring(14, 2), out var spindleId) ? spindleId : 1,
};

return header;
}

//截取返回数据段
protected string SubDataFieldsString(string package)
{
return package.Substring(20, Header.Length - 20);
}

//根据给定数据字段解析, 要求加入所有字段或者ID从1开始连续的字段
public virtual void ProcessDataFields(List<DataField> dataFields)
{
if (DataFieldsString == null)
throw new Exception("数据段为空");

dataFields = dataFields.OrderBy(df => df.id).ToList();
int startIndex = 0;
int idCount = 1;
dataFields.ForEach(df =>
{
if (idCount++ == Convert.ToInt32(DataFieldsString.Substring(startIndex, 2)))
{
//throw new Exception("数据解析ID与实际不匹配");
if (startIndex > DataFieldsString.Length)
throw new Exception("接收到的数据长度不正确: 长度应大于" + startIndex + ";实际长度=" + DataFieldsString.Length);
df.data = DataFieldsString.Substring(startIndex + 2, df.length).Trim().Trim('\0');
startIndex += df.length + 2;
}
else
df.data = "List Order Error";
});
}

//根据字段的索引值取值, 数据段起始为0
public virtual void ProcessDataFieldsWithIndex(List<DataField> dataFields, int dataIndexOffset = 0)
{
if (DataFieldsString == null)
throw new Exception("数据段为空");
dataFields.ForEach(df =>
{
df.index -= dataIndexOffset;
if (df.id == Convert.ToInt32(DataFieldsString.Substring(df.index, 2)))
{
if (df.index + df.length > DataFieldsString.Length)
throw new Exception("数据长度不正确");
df.data = DataFieldsString.Substring(df.index + 2, df.length).Trim().Trim('\0');
}
else
df.data = "Index Error";
});
}

//解析出Header和DateField
public virtual Mid Parse(string package)
{
Header = ProcessHeader(package);
DataFieldsString = SubDataFieldsString(package);
return this;
}
public virtual Mid Parse(byte[] package)
{
var pack = ToAscii(package);
return Parse(pack);
}

protected static string ToAscii(byte[] bytes) => Encoding.ASCII.GetString(bytes);

protected static byte[] ToBytes(string value) => Encoding.ASCII.GetBytes(value);

public class DataField
{
public int id;
public int length;
public string data;
public int index;//数据段起始为0(不计算Header长度)

public DataField(List<DataField> dfList, int id, int length)
{
this.id = id;
this.length = length;
this.data = "";
this.index = -1;
//
if(dfList.Find(df => df.id == id) == null)
dfList.Add(this);
}

public DataField(List<DataField> dfList, int id, int length, int index) : this(dfList, id, length)
{
this.index = index;
}
}
}

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
public class OpenProtocolClient : xStringEx
{
private static readonly object locker = new object();
private xString Connected = new();
private static TcpClient tcpClient = new();
public Mid recvMid;
public Mid lastRecvMid;

private bool EnableLog = false;
private string LogFile = "DesoutterOpenProtocol";
private string remoteIP = string.Empty;
private int remotePort = 502;
private int recvTimeout = 100;

bool online = false;

public OpenProtocolClient()
{
Connected = TCreate<xString>(new xString(), "Connected");
}

public xState Init(string sPara)
{
try
{
online = false;
Dictionary<string, object> PortCif = new Dictionary<string, object>
{
{ "IPAddr", "127.0.0.1" },
{ "PortNo", 502 },
{ "RecvTimeout", 1000 },
{ "EnableLog", true },
{ "LogFileName", "TCPClientOpra" }
};
XmlFO.Configs.Get("TCPClient", sPara, PortCif);

remoteIP = Convert.ToString(PortCif["IPAddr"]);
remotePort = Convert.ToInt32(PortCif["PortNo"]);
recvTimeout = Convert.ToInt32(PortCif["RecvTimeout"]);
EnableLog = Convert.ToBoolean(PortCif["EnableLog"]);
LogFile = Convert.ToString(PortCif["LogFileName"]);

ThreadEx.New(TcpRecvThread, "TcpRecvThread", 50);
ThreadEx.New(Online, "Online", 1000);
return xState.xTrue;
}
catch (Exception e)
{
xErrorOutException(e);
return xState.xFalse;
}
}

public xState StartCommunication(string sParam)
{
try
{
online = true;
int i = 0;
Mid mid;
do
{
mid = SendAndRecv(new Mid(1, 1));
if (mid == null)
break;
}
while (mid.Header.Mid != 2 && ++i < 5);
if (lastRecvMid != null && recvMid.Header.Mid == 2)
{
Connected.xValue = "True";
return xState.xTrue;
}
else
{
Connected.xValue = "False";
return xState.xFalse;
}
}
catch (Exception e)
{
Connected.xValue = "False";
xErrorOutException(e);
return xState.xFalse;
}
}

public xState StopCommunication(string sParam)
{
try
{
online = false;
Mid mid = SendAndRecv(new Mid(3,1));
Connected.xValue = "False";
return mid.PositiveResponse ? xState.xTrue : xState.xFalse;
}
catch (Exception e)
{
xErrorOutException(e);
return xState.xFalse;
}
}

public xState PostVehicleCode(string sParam)
{
try
{
string code = XmlFO.StringAnalysis(sParam);
if (code.Length < 19)
return xState.xFalse;
StartCommunication("");
Mid mid = SendAndRecv(new Mid(50, 1).AppendDataFields(code));
return mid.PositiveResponse ? xState.xTrue : xState.xFalse;
}
catch (Exception e)
{
xErrorOutException(e);
return xState.xFalse;
}
}
public xState SubscribeLastResult(string sParam)
{
try
{
Mid mid = SendAndRecv(new Mid(60, 4));
return mid.PositiveResponse ? xState.xTrue : xState.xFalse;
}
catch (Exception e)
{
xErrorOutException(e);
return xState.xFalse;
}
}
public xState RecvLastResult(string sParam)
{
try
{
if (lastRecvMid.Header.Mid != 61)
return xState.xFalse;
if (lastRecvMid.DataFieldsString == null)
return xState.xFalse;
Mid61DataResult mid61Result = new Mid61DataResult();
lastRecvMid.ProcessDataFields(Mid61DataResult.dfList);
double torque = Convert.ToInt32(mid61Result.torque.data) / 100;
return xState.xTrue;
}
catch (Exception e)
{
xErrorOutException(e);
return xState.xFalse;
}
}
//Online
void Online()
{
if (!online)
return;
Mid mid = SendAndRecv(new Mid(9999, 0));
}
private void TcpRecvThread()
{
byte[] tcpRecvByts;
//while (true)
{
if (!IsOnline())
{
Connect();
return;
}
tcpRecvByts = new byte[2048];
int recvLength = 20;
try
{
recvLength = tcpClient.Client.Receive(tcpRecvByts);
}
catch (Exception e)
{
recvMid = null;
return;
}
byte[] tmpRecvData = new byte[recvLength];
Array.Copy(tcpRecvByts, tmpRecvData, recvLength);
string tcpRecvString = Encoding.ASCII.GetString(tmpRecvData);
Logout("Recv:<-" + tcpRecvString);
recvMid = new Mid().Parse(tcpRecvString);
lastRecvMid = recvMid;
}
}
public Mid SendAndRecv(Mid sendMid)
{
SendMsg(sendMid.Pack());
int t = Environment.TickCount;
while ((Environment.TickCount - t) < recvTimeout)
{
Thread.Sleep(10);
if(recvMid != null)
return recvMid;
}
Logout("Recv Timeout");
throw new Exception("Recv Timeout");
}
public void SendMsg(string data)
{
if (!IsOnline())
Connect();
lock (locker)
{
tcpClient.Client.Send(Encoding.ASCII.GetBytes(data));
recvMid = null;//考虑发出去后置空, recv中好判断
Logout("Send:->" + data);
}
}

private void Connect()
{
if (tcpClient == null)
tcpClient = new TcpClient();
if (tcpClient.Connected)
return;
tcpClient.Close();
tcpClient = new TcpClient();
tcpClient.Connect(IPAddress.Parse(remoteIP), remotePort);
tcpClient.Client.ReceiveTimeout = recvTimeout;
}

public bool IsOnline()
{
if (tcpClient == null)
return false;
bool onLine = !((tcpClient.Client.Poll(100, SelectMode.SelectRead) && (tcpClient.Client.Available == 0)) || !tcpClient.Client.Connected);
if (!onLine)
tcpClient = null;
return onLine;
}

private void Logout(string msg)
{
string path = Environment.CurrentDirectory + "\\Log";
string fullPath = path + "\\log" + DateTime.Now.ToString("yyyyMMdd") + ".log";
if (!File.Exists(path))
System.IO.Directory.CreateDirectory(path);
if (EnableLog)
{
using (StreamWriter sw = new StreamWriter(fullPath, true)) { sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ffff ") + msg); }
}
}

//Just for test
public class Mid61DataResult
{
public static List<Mid.DataField> dfList = new List<Mid.DataField>();
public DataField sensorID = new DataField(dfList, 1, 4, 0);
public DataField channelID = new DataField(dfList, 2, 2, 6);
public DataField controlerName = new DataField(dfList, 3, 25, 10);
public DataField torque = new DataField(dfList, 4, 40,37);

public double TorqueResult { get => Convert.ToDouble(torque.data) ; }
}
}