├── .gitattributes ├── .gitignore ├── README.md ├── Secs4Frmk4 ├── ConnectionState.cs ├── ExtensionHelper.cs ├── Item.cs ├── Logger.cs ├── MessageHeader.cs ├── MessageType.cs ├── PrimaryMessageWrapper.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── Secs4Frmk4.csproj ├── SecsException.cs ├── SecsFormat.cs ├── SecsGem.cs ├── SecsMessage.cs ├── Sml │ └── SmlExtension.cs ├── StreamDecoder.cs ├── SystemByteGenerator.cs ├── TEventArgs.cs ├── TaskCompletionSourceToken.cs └── app.config ├── Secs4Net4.sln └── TestApp ├── Program.cs ├── Properties └── AssemblyInfo.cs └── TestApp.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | .svn/ 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | *.bak 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opendb 82 | *.opensdf 83 | *.sdf 84 | *.cachefile 85 | *.VC.db 86 | *.VC.VC.opendb 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | *.sap 93 | 94 | # TFS 2012 Local Workspace 95 | $tf/ 96 | 97 | # Guidance Automation Toolkit 98 | *.gpState 99 | 100 | # ReSharper is a .NET coding add-in 101 | _ReSharper*/ 102 | *.[Rr]e[Ss]harper 103 | *.DotSettings.user 104 | 105 | # JustCode is a .NET coding add-in 106 | .JustCode 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # NCrunch 115 | _NCrunch_* 116 | .*crunch*.local.xml 117 | nCrunchTemp_* 118 | 119 | # MightyMoose 120 | *.mm.* 121 | AutoTest.Net/ 122 | 123 | # Web workbench (sass) 124 | .sass-cache/ 125 | 126 | # Installshield output folder 127 | [Ee]xpress/ 128 | 129 | # DocProject is a documentation generator add-in 130 | DocProject/buildhelp/ 131 | DocProject/Help/*.HxT 132 | DocProject/Help/*.HxC 133 | DocProject/Help/*.hhc 134 | DocProject/Help/*.hhk 135 | DocProject/Help/*.hhp 136 | DocProject/Help/Html2 137 | DocProject/Help/html 138 | 139 | # Click-Once directory 140 | publish/ 141 | 142 | # Publish Web Output 143 | *.[Pp]ublish.xml 144 | *.azurePubxml 145 | # TODO: Comment the next line if you want to checkin your web deploy settings 146 | # but database connection strings (with potential passwords) will be unencrypted 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 151 | # checkin your Azure Web App publish settings, but sensitive information contained 152 | # in these scripts will be unencrypted 153 | PublishScripts/ 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | # The packages folder can be ignored because of Package Restore 158 | **/packages/* 159 | # except build/, which is used as an MSBuild target. 160 | !**/packages/build/ 161 | # Uncomment if necessary however generally it will be regenerated when needed 162 | #!**/packages/repositories.config 163 | # NuGet v3's project.json files produces more ignoreable files 164 | *.nuget.props 165 | *.nuget.targets 166 | 167 | # Microsoft Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Microsoft Azure Emulator 172 | ecf/ 173 | rcf/ 174 | 175 | # Windows Store app package directories and files 176 | AppPackages/ 177 | BundleArtifacts/ 178 | Package.StoreAssociation.xml 179 | _pkginfo.txt 180 | 181 | # Visual Studio cache files 182 | # files ending in .cache can be ignored 183 | *.[Cc]ache 184 | # but keep track of directories ending in .cache 185 | !*.[Cc]ache/ 186 | 187 | # Others 188 | ClientBin/ 189 | ~$* 190 | *~ 191 | *.dbmdl 192 | *.dbproj.schemaview 193 | *.jfm 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | # CodeRush 258 | .cr/ 259 | 260 | # Python Tools for Visual Studio (PTVS) 261 | __pycache__/ 262 | *.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Secs4Net4 2 | .net 4.0下的HSMS通讯协议 3 | -------------------------------------------------------------------------------- /Secs4Frmk4/ConnectionState.cs: -------------------------------------------------------------------------------- 1 | namespace Granda.HSMS 2 | { 3 | public enum ConnectionState 4 | { 5 | Connecting, 6 | Connected, 7 | Selected, 8 | Retry 9 | } 10 | } -------------------------------------------------------------------------------- /Secs4Frmk4/ExtensionHelper.cs: -------------------------------------------------------------------------------- 1 | #region 文件说明 2 | /*------------------------------------------------------------------------------ 3 | // Copyright © 2018 Granda. All Rights Reserved. 4 | // 苏州广林达电子科技有限公司 版权所有 5 | //------------------------------------------------------------------------------ 6 | // File Name: ExtensionHelper 7 | // Author: Ivan JL Zhang Date: 2018/4/28 14:44:25 Version: 1.0.0 8 | // Description: 9 | // 10 | // 11 | // Revision History: 12 | // 13 | // 14 | //----------------------------------------------------------------------------*/ 15 | #endregion 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Linq; 19 | using System.Text; 20 | 21 | namespace Granda.HSMS 22 | { 23 | public static class ExtensionHelper 24 | { 25 | 26 | internal static void Reverse(this byte[] bytes, int begin, int end, int offset) 27 | { 28 | if (offset <= 1) return; 29 | for (int index = 0; index < end; index += offset) 30 | { 31 | Array.Reverse(bytes, index, offset); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Secs4Frmk4/Item.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Granda.HSMS 8 | { 9 | public sealed class Item 10 | { 11 | #region Fields/Properties 12 | /// 13 | /// if Format is List RawData is only header bytes. 14 | /// otherwise include header and value bytes. 15 | /// 16 | private readonly Lazy _rawData; 17 | 18 | private readonly IEnumerable _values; 19 | 20 | public SecsFormat Format { get; private set; } 21 | /// 22 | /// List items 23 | /// 24 | public IList Items 25 | { 26 | get 27 | { 28 | return Format != SecsFormat.List 29 | ? throw new InvalidOperationException("The item is not a list") 30 | : (IList)_values; 31 | } 32 | } 33 | public int Count 34 | { 35 | get 36 | { 37 | if (Format == SecsFormat.List) 38 | return ((IList)_values).Count; 39 | else if (Format == SecsFormat.ASCII) 40 | return _values.ToString().Length; 41 | else 42 | return ((Array)_values).Length; 43 | } 44 | } 45 | public IList RawBytes => _rawData.Value; 46 | 47 | private static readonly Encoding Jis8Encoding = Encoding.GetEncoding(50222); 48 | #endregion 49 | 50 | #region 构造方法 51 | /// 52 | /// List 构造方法 53 | /// 54 | /// 55 | private Item(IList items) 56 | { 57 | if (items.Count > byte.MaxValue) 58 | throw new ArgumentOutOfRangeException(nameof(items) + "." + nameof(items.Count), items.Count, 59 | @"List items length out of range, max length: 255"); 60 | 61 | Format = SecsFormat.List; 62 | _values = items; 63 | _rawData = new Lazy(() => new byte[]{ 64 | (byte)SecsFormat.List | 1, 65 | unchecked((byte)((IList)(_values)).Count) 66 | }); 67 | } 68 | /// 69 | /// ASCII, Jis 70 | /// 71 | /// 72 | /// 73 | private Item(SecsFormat secsFormat, string value) 74 | { 75 | Format = secsFormat; 76 | _values = value; 77 | _rawData = new Lazy(() => 78 | { 79 | var str = (string)_values; 80 | var bytelength = str.Length; 81 | var result = EncodeItem(bytelength); 82 | var encoder = Format == SecsFormat.ASCII ? Encoding.ASCII : Jis8Encoding; 83 | encoder.GetBytes(str, 0, str.Length, result.Item1, result.Item2); 84 | return result.Item1; 85 | }); 86 | } 87 | /// 88 | /// U1, U2, U4, U8 89 | /// I1, I2, I4, I8 90 | /// F4, F8 91 | /// Boolean, 92 | /// Binary 93 | /// 94 | /// 95 | /// 96 | private Item(SecsFormat secsFormat, Array value) 97 | { 98 | Format = secsFormat; 99 | _values = value; 100 | 101 | _rawData = new Lazy(() => 102 | { 103 | var arr = (Array)_values; 104 | var byteLength = Buffer.ByteLength(arr); 105 | var ret = EncodeItem(byteLength); 106 | var result = ret.Item1; 107 | var headerLength = ret.Item2; 108 | Buffer.BlockCopy(arr, 0, result, headerLength, byteLength); 109 | result.Reverse(headerLength, headerLength + byteLength, byteLength / arr.Length); 110 | return result; 111 | }); 112 | } 113 | /// 114 | /// Empty Item(none List) 115 | /// 116 | /// 117 | /// 118 | private Item(SecsFormat secsFormat, IEnumerable value) 119 | { 120 | Format = secsFormat; 121 | _values = value; 122 | _rawData = new Lazy(() => new byte[] { (byte)((byte)Format | 1), 0 }); 123 | } 124 | #endregion 125 | 126 | #region helper methods 127 | /// 128 | /// 获取ASCII格式的字串 129 | /// 130 | /// 131 | public string GetString() 132 | { 133 | return Format != SecsFormat.ASCII && Format != SecsFormat.JIS8 134 | ? throw new InvalidOperationException("This type is incompatible") 135 | : (string)_values; 136 | } 137 | #endregion 138 | 139 | #region Factory Methods 140 | public static Item L(IList items) => items.Count > 0 ? new Item(items) : L(); 141 | 142 | public static Item L(IEnumerable items) => L(items.ToList()); 143 | 144 | public static Item L(params Item[] items) => L((IList)items); 145 | 146 | public static Item A(string value) => value != string.Empty ? new Item(SecsFormat.ASCII, value) : A(); 147 | 148 | public static Item B(params byte[] value) => value.Length > 0 ? new Item(SecsFormat.Binary, value) : B(); 149 | public static Item B(IEnumerable value) => B(value.ToArray()); 150 | #endregion 151 | 152 | #region Share Object 153 | public static Item L() => EmptyL; 154 | public static Item A() => EmptyA; 155 | public static Item B() => EmptyB; 156 | private static readonly Item EmptyL = new Item(SecsFormat.List, Enumerable.Empty()); 157 | private static readonly Item EmptyA = new Item(SecsFormat.ASCII, string.Empty); 158 | private static readonly Item EmptyJ = new Item(SecsFormat.JIS8, string.Empty); 159 | private static readonly Item EmptyB = new Item(SecsFormat.Binary, Enumerable.Empty()); 160 | #endregion 161 | 162 | #region internal/private methods 163 | /// 164 | /// Encode item to raw data buffer 165 | /// 166 | /// 167 | /// 168 | internal uint EncodeTo(List> buffer) 169 | { 170 | byte[] bytes = _rawData.Value; 171 | uint length = unchecked((uint)bytes.Length); 172 | buffer.Add(new ArraySegment(bytes)); 173 | 174 | if (Format == SecsFormat.List) 175 | { 176 | foreach (var item in Items) 177 | { 178 | length += item.EncodeTo(buffer); 179 | } 180 | } 181 | return length; 182 | } 183 | /// 184 | /// Item的编码规则为Format|content长度所占byte数 + content长度bytes(最多3byte) + content编码 185 | /// 186 | /// 187 | /// 188 | private Tuple EncodeItem(int valueCount) 189 | { 190 | byte[] valueCountBytes = BitConverter.GetBytes(valueCount); 191 | if (valueCount <= 0xff) 192 | {// 1 byte 193 | var result = new byte[valueCount + 2]; 194 | result[0] = (byte)((byte)Format | 1);// Format 加上 长度byte 195 | result[1] = valueCountBytes[0]; 196 | return new Tuple(result, 2); 197 | } 198 | if (valueCount <= 0xffff) 199 | {// 2 byte 200 | var result = new byte[valueCount + 3]; 201 | result[0] = (byte)((byte)Format | 2);// Format 加上 长度 202 | result[1] = valueCountBytes[1]; 203 | result[2] = valueCountBytes[0]; 204 | return new Tuple(result, 3); 205 | } 206 | if (valueCount <= 0xffffff) 207 | {// 3 byte 208 | var result = new byte[valueCount + 4]; 209 | result[0] = (byte)((byte)Format | 3);// Format 加上 长度 210 | result[1] = valueCountBytes[2]; 211 | result[2] = valueCountBytes[1]; 212 | result[3] = valueCountBytes[0]; 213 | return new Tuple(result, 4); 214 | } 215 | 216 | throw new ArgumentOutOfRangeException(nameof(valueCount), valueCount, $@"Item data length:{valueCount} is overflow"); 217 | } 218 | 219 | internal static Item BytesDecode(ref SecsFormat secsFormat, byte[] data, ref int index, ref int length) 220 | { 221 | switch (secsFormat) 222 | { 223 | case SecsFormat.ASCII: 224 | return length == 0 ? A() : A(Encoding.ASCII.GetString(data, index, length)); 225 | case SecsFormat.Binary: 226 | return length == 0 ? B() : B(Decode(data, ref index, ref length, sizeof(byte))); 227 | default: 228 | throw new ArgumentException(@"Invalid format", nameof(secsFormat)); 229 | } 230 | } 231 | 232 | private static T[] Decode(byte[] data, ref int index, ref int length, int elmSize) 233 | { 234 | data.Reverse(index, index + length, elmSize); 235 | var values = new T[length / elmSize]; 236 | Buffer.BlockCopy(data, index, values, 0, length); 237 | return values; 238 | } 239 | 240 | internal T[] GetValues() where T : struct 241 | { 242 | if (Format == SecsFormat.List) 243 | throw new InvalidOperationException("The item is list."); 244 | if (Format == SecsFormat.ASCII || Format == SecsFormat.JIS8) 245 | throw new InvalidOperationException("The item is a string"); 246 | 247 | if (_values is T[] arr) 248 | return arr; 249 | 250 | throw new InvalidOperationException("The type is incompatible"); 251 | } 252 | #endregion 253 | 254 | } 255 | } -------------------------------------------------------------------------------- /Secs4Frmk4/Logger.cs: -------------------------------------------------------------------------------- 1 | #region 文件说明 2 | /*------------------------------------------------------------------------------ 3 | // Copyright © 2018 Granda. All Rights Reserved. 4 | // 苏州广林达电子科技有限公司 版权所有 5 | //------------------------------------------------------------------------------ 6 | // File Name: Logger 7 | // Author: Ivan JL Zhang Date: 2018/4/25 15:45:59 Version: 1.0.0 8 | // Description: 9 | // 10 | // 11 | // Revision History: 12 | // 13 | // 14 | //----------------------------------------------------------------------------*/ 15 | #endregion 16 | using System; 17 | using System.Diagnostics; 18 | using Granda.AATS.Log; 19 | 20 | namespace Granda.HSMS 21 | { 22 | internal abstract class Logger 23 | { 24 | public static void Info(string message) 25 | { 26 | //LogAdapter.WriteLog(new LogRecord(LogLevel.INFO, message, null)); 27 | Trace.WriteLine(message); 28 | } 29 | public static void Error(string message, Exception exception = null) 30 | { 31 | LogAdapter.WriteLog(new LogRecord(LogLevel.ERROR, message, exception)); 32 | Trace.WriteLine(message + ": " + exception?.Message); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Secs4Frmk4/MessageHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Granda.HSMS 4 | { 5 | public struct MessageHeader 6 | { 7 | public byte S { get; internal set; } 8 | public byte F { get; internal set; } 9 | public bool ReplyExpected { get; internal set; } 10 | public ushort DeviceId { get; internal set; } 11 | public int SystemBytes { get; internal set; } 12 | public MessageType MessageType { get; internal set; } 13 | 14 | public byte[] EncodeTo(byte[] buffer) 15 | { 16 | // Device Id 17 | var values = BitConverter.GetBytes(DeviceId); 18 | buffer[0] = values[1]; 19 | buffer[1] = values[0]; 20 | 21 | // S, ReplyExpected 22 | buffer[2] = (byte)(S | (ReplyExpected ? 0b1000_0000 : 0)); 23 | 24 | // F 25 | buffer[3] = F; 26 | 27 | // PType: 0=> SECS-II Encoding 28 | buffer[4] = 0; 29 | 30 | // MessageType 31 | buffer[5] = (byte)MessageType; 32 | 33 | values = BitConverter.GetBytes(SystemBytes); 34 | buffer[6] = values[3]; 35 | buffer[7] = values[2]; 36 | buffer[8] = values[1]; 37 | buffer[9] = values[0]; 38 | 39 | return buffer; 40 | 41 | } 42 | 43 | internal static MessageHeader Decode(byte[] buffer, int startIndex) 44 | { 45 | ushort deviceId = unchecked(BitConverter.ToUInt16(new byte[] { 46 | buffer[startIndex + 1], 47 | buffer[startIndex], 48 | }, 0)); 49 | 50 | int systemBytes = unchecked(BitConverter.ToInt32(new byte[] { 51 | buffer[startIndex + 9], 52 | buffer[startIndex + 8], 53 | buffer[startIndex + 7], 54 | buffer[startIndex + 6], 55 | }, 0)); 56 | 57 | return new MessageHeader 58 | { 59 | DeviceId = deviceId, 60 | ReplyExpected = (buffer[startIndex + 2] & 0b1000_0000) != 0, 61 | S = (byte)(buffer[startIndex + 2] & 0b0111_111), 62 | F = buffer[startIndex + 3], 63 | MessageType = (MessageType)buffer[startIndex + 5], 64 | SystemBytes = systemBytes, 65 | }; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Secs4Frmk4/MessageType.cs: -------------------------------------------------------------------------------- 1 | namespace Granda.HSMS 2 | { 3 | public enum MessageType : byte 4 | { 5 | DataMessage = 0b0000_0000, 6 | SelectRequest = 0b0000_0001, 7 | SelectResponse = 0b0000_0010, 8 | Deselect_req = 0b0000_0011, 9 | Deselect_rsp = 0b0000_0100, 10 | LinkTestRequest = 0b0000_0101, 11 | LinkTestResponse = 0b0000_0110, 12 | Reject_req = 0b0000_0111, 13 | SeperateRequest = 0b0000_1001 14 | } 15 | } -------------------------------------------------------------------------------- /Secs4Frmk4/PrimaryMessageWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Granda.HSMS 4 | { 5 | public class PrimaryMessageWrapper : EventArgs 6 | { 7 | private SecsGem secsGem; 8 | private MessageHeader header; 9 | public SecsMessage Message { get; } 10 | public int MessageId => header.SystemBytes; 11 | 12 | public MessageHeader Header { get => header; set => header = value; } 13 | 14 | public PrimaryMessageWrapper(SecsGem secsGem, MessageHeader header, SecsMessage secsMessage) 15 | { 16 | this.secsGem = secsGem; 17 | this.header = header; 18 | this.Message = secsMessage; 19 | } 20 | public override string ToString() 21 | { 22 | return Message.ToString(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Secs4Frmk4/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("Granda.HSMS")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Granda.HSMS")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // 将 ComVisible 设置为 false 会使此程序集中的类型 18 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("7e51f1ae-3104-4858-af49-6a81aabe7bcb")] 24 | 25 | // 程序集的版本信息由下列四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | // 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 33 | //通过使用 "*",如下所示: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Secs4Frmk4/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Granda.HSMS.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// 一个强类型的资源类,用于查找本地化的字符串等。 17 | /// 18 | // 此类是由 StronglyTypedResourceBuilder 19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen 21 | // (以 /str 作为命令选项),或重新生成 VS 项目。 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// 返回此类使用的缓存的 ResourceManager 实例。 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Granda.HSMS.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// 使用此强类型资源类,为所有资源查找 51 | /// 重写当前线程的 CurrentUICulture 属性。 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// 查找类似 Unrecognized Device Id 的本地化字符串。 65 | /// 66 | internal static string S9F1 { 67 | get { 68 | return ResourceManager.GetString("S9F1", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// 查找类似 Data Too Long 的本地化字符串。 74 | /// 75 | internal static string S9F11 { 76 | get { 77 | return ResourceManager.GetString("S9F11", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// 查找类似 Conversation Timeout 的本地化字符串。 83 | /// 84 | internal static string S9F13 { 85 | get { 86 | return ResourceManager.GetString("S9F13", resourceCulture); 87 | } 88 | } 89 | 90 | /// 91 | /// 查找类似 Unrecognized Stream Type 的本地化字符串。 92 | /// 93 | internal static string S9F3 { 94 | get { 95 | return ResourceManager.GetString("S9F3", resourceCulture); 96 | } 97 | } 98 | 99 | /// 100 | /// 查找类似 Unrecognized Function Type 的本地化字符串。 101 | /// 102 | internal static string S9F5 { 103 | get { 104 | return ResourceManager.GetString("S9F5", resourceCulture); 105 | } 106 | } 107 | 108 | /// 109 | /// 查找类似 Illegal Data 的本地化字符串。 110 | /// 111 | internal static string S9F7 { 112 | get { 113 | return ResourceManager.GetString("S9F7", resourceCulture); 114 | } 115 | } 116 | 117 | /// 118 | /// 查找类似 Transaction Timer Timeout 的本地化字符串。 119 | /// 120 | internal static string S9F9 { 121 | get { 122 | return ResourceManager.GetString("S9F9", resourceCulture); 123 | } 124 | } 125 | 126 | /// 127 | /// 查找类似 S9Fy message reply. 的本地化字符串。 128 | /// 129 | internal static string S9Fy { 130 | get { 131 | return ResourceManager.GetString("S9Fy", resourceCulture); 132 | } 133 | } 134 | 135 | /// 136 | /// 查找类似 Stream number must be less than 127 的本地化字符串。 137 | /// 138 | internal static string SecsMessageStreamNumberMustLessThan127 { 139 | get { 140 | return ResourceManager.GetString("SecsMessageStreamNumberMustLessThan127", resourceCulture); 141 | } 142 | } 143 | 144 | /// 145 | /// 查找类似 Equipment is not online mode 的本地化字符串。 146 | /// 147 | internal static string SxF0 { 148 | get { 149 | return ResourceManager.GetString("SxF0", resourceCulture); 150 | } 151 | } 152 | 153 | /// 154 | /// 查找类似 T3 Timeout! 的本地化字符串。 155 | /// 156 | internal static string T3Timeout { 157 | get { 158 | return ResourceManager.GetString("T3Timeout", resourceCulture); 159 | } 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Secs4Frmk4/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 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 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Unrecognized Device Id 122 | S9F1 description 123 | 124 | 125 | Data Too Long 126 | S9F11 description 127 | 128 | 129 | Conversation Timeout 130 | S9F13 description 131 | 132 | 133 | Unrecognized Stream Type 134 | S9F3 description 135 | 136 | 137 | Unrecognized Function Type 138 | S9F5 description 139 | 140 | 141 | Illegal Data 142 | S9F7 description 143 | 144 | 145 | Transaction Timer Timeout 146 | S9F9 description 147 | 148 | 149 | S9Fy message reply. 150 | S9Fy description 151 | 152 | 153 | Stream number must be less than 127 154 | 155 | 156 | Equipment is not online mode 157 | EquipmentIsNotOnlineMode description 158 | 159 | 160 | T3 Timeout! 161 | T3 Timeout description 162 | 163 | -------------------------------------------------------------------------------- /Secs4Frmk4/Secs4Frmk4.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {7E51F1AE-3104-4858-AF49-6A81AABE7BCB} 8 | Library 9 | Properties 10 | Granda.HSMS 11 | Granda.HSMS 12 | v4.0 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | True 53 | True 54 | Resources.resx 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | {57e0a100-47fd-43de-bbda-7908b0dc5ddf} 69 | Granda.AATS.Log 70 | 71 | 72 | 73 | 74 | ResXFileCodeGenerator 75 | Resources.Designer.cs 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /Secs4Frmk4/SecsException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Granda.HSMS 5 | { 6 | public class SecsException : Exception 7 | { 8 | public SecsMessage secsMessage { get; } 9 | 10 | public SecsException(string message) : this(null, message) 11 | { 12 | } 13 | 14 | public SecsException(SecsMessage secsMessage, string description) : base(description) 15 | { 16 | this.secsMessage = secsMessage; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Secs4Frmk4/SecsFormat.cs: -------------------------------------------------------------------------------- 1 | namespace Granda.HSMS 2 | { 3 | /// 4 | /// The enumeration of SECS item value format 5 | /// 6 | public enum SecsFormat : byte 7 | { 8 | List = 0b0000_00_00, 9 | Binary = 0b0010_00_00, 10 | Boolean = 0b0010_01_00, 11 | ASCII = 0b0100_00_00, 12 | JIS8 = 0b0100_01_00, 13 | I8 = 0b0110_00_00, 14 | I1 = 0b0110_01_00, 15 | I2 = 0b0110_10_00, 16 | I4 = 0b0111_00_00, 17 | F8 = 0b1000_00_00, 18 | F4 = 0b1001_00_00, 19 | U8 = 0b1010_00_00, 20 | U1 = 0b1010_01_00, 21 | U2 = 0b1010_10_00, 22 | U4 = 0b1011_00_00 23 | } 24 | } -------------------------------------------------------------------------------- /Secs4Frmk4/SecsGem.cs: -------------------------------------------------------------------------------- 1 | #region 文件说明 2 | /*------------------------------------------------------------------------------ 3 | // Copyright © 2018 Granda. All Rights Reserved. 4 | // 苏州广林达电子科技有限公司 版权所有 5 | //------------------------------------------------------------------------------ 6 | // File Name: SecsGem 7 | // Author: Ivan JL Zhang Date: 2018/4/25 15:27:13 Version: 1.0.0 8 | // Description: 9 | // 10 | // 11 | // Revision History: 12 | // 13 | // 14 | //----------------------------------------------------------------------------*/ 15 | #endregion 16 | using System; 17 | using System.Collections.Concurrent; 18 | using System.Collections.Generic; 19 | using System.Diagnostics; 20 | using System.Net; 21 | using System.Net.Sockets; 22 | using System.Threading; 23 | using System.Threading.Tasks; 24 | using Granda.HSMS.Properties; 25 | 26 | namespace Granda.HSMS 27 | { 28 | public class SecsGem : IDisposable 29 | { 30 | #region event 31 | /// 32 | /// HSMS connection sate changed event 33 | /// 34 | public event EventHandler> ConnectionChanged; 35 | 36 | /// 37 | /// Primary message received event 38 | /// 39 | public event EventHandler PrimaryMessageReceived = DefaultPrimaryMessageReceived; 40 | private static void DefaultPrimaryMessageReceived(object sender, PrimaryMessageWrapper _) { } 41 | #endregion 42 | 43 | #region Properties 44 | /// 45 | /// Connection state 46 | /// 47 | public ConnectionState State { get; private set; } 48 | 49 | /// 50 | /// Device Id. 51 | /// 52 | public ushort DeviceId { get; set; } = 0; 53 | 54 | /// 55 | /// T3 timer interval 56 | /// Reply timeout 57 | /// 58 | public int T3 { get; set; } = 40000; 59 | 60 | /// 61 | /// T5 timer interval 62 | /// Connect Separation timeout 63 | /// 64 | public int T5 { get; set; } = 5000; 65 | 66 | /// 67 | /// T6 timer interval 68 | /// Control Timeout 69 | /// 70 | public int T6 { get; set; } = 5000; 71 | 72 | /// 73 | /// T7 timer interval 74 | /// Connection Idle Timeout 75 | /// 76 | public int T7 { get; set; } = 10000; 77 | 78 | /// 79 | /// T8 timer interval 80 | /// network intercharacter timeout 81 | /// 82 | public int T8 { get; set; } = 5000; 83 | 84 | public bool IsActive { get; } 85 | public IPAddress IpAddress { get; } 86 | public int Port { get; } 87 | #endregion 88 | 89 | #region field 90 | private readonly ConcurrentDictionary _replyExpectedMsgs = new ConcurrentDictionary(); 91 | 92 | private readonly Timer _timer7; // between socket connected and received Select.req timer 93 | private readonly Timer _timer8; 94 | private readonly Timer _timerLinkTest; 95 | 96 | private readonly Action _startImpl; 97 | private readonly Action _stopImpl; 98 | 99 | private readonly SystemByteGenerator _systemByte = new SystemByteGenerator(); 100 | internal int NewSystemId => _systemByte.New(); 101 | 102 | private readonly TaskFactory _taskFactory = new TaskFactory(TaskScheduler.Default); 103 | 104 | #endregion 105 | 106 | #region ctor and start/stop methods 107 | public SecsGem(bool isActive, IPAddress ip, int port) 108 | { 109 | IsActive = isActive; 110 | IpAddress = ip; 111 | Port = port; 112 | _secsDecoder = new StreamDecoder(0x4000, HandlerControlMessage, HandleDataMessage); 113 | DecoderBufferSize = 0x4000; 114 | #region Timer Action 115 | _timer7 = new Timer(delegate 116 | { 117 | Logger.Error($"T7 Timeout: {T7 / 1000} sec."); 118 | CommunicationStateChanging(ConnectionState.Retry); 119 | }, null, Timeout.Infinite, Timeout.Infinite); 120 | 121 | _timer8 = new Timer(delegate 122 | { 123 | Logger.Error($"T8 Timeout: {T8 / 1000} sec."); 124 | CommunicationStateChanging(ConnectionState.Retry); 125 | }, null, Timeout.Infinite, Timeout.Infinite); 126 | 127 | _timerLinkTest = new Timer(delegate 128 | { 129 | if (State == ConnectionState.Selected) 130 | SendControlMessage(MessageType.LinkTestRequest, NewSystemId); 131 | }, null, Timeout.Infinite, Timeout.Infinite); 132 | 133 | #endregion 134 | 135 | if (IsActive) 136 | { 137 | _startImpl = () => 138 | { 139 | if (IsDisposed) 140 | return; 141 | CommunicationStateChanging(ConnectionState.Connecting); 142 | try 143 | { 144 | _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 145 | SocketAsyncEventArgs socketAsyncEventArgs = new SocketAsyncEventArgs(); 146 | socketAsyncEventArgs.RemoteEndPoint = new IPEndPoint(ip, port); 147 | socketAsyncEventArgs.Completed += ((sender, e) => 148 | { 149 | if (e.SocketError == SocketError.Success) 150 | { 151 | // hook receive envent first, because no message will received before 'SelectRequest' send to device 152 | StartSocketReceive(); 153 | SendControlMessage(MessageType.SelectRequest, NewSystemId); 154 | } 155 | else 156 | { 157 | Logger.Info($"Start T5 Timer: {T5 / 1000} sec."); 158 | Thread.Sleep(T5); 159 | CommunicationStateChanging(ConnectionState.Retry); 160 | } 161 | }); 162 | _socket.ConnectAsync(socketAsyncEventArgs); 163 | } 164 | catch (Exception ex) 165 | { 166 | if (IsDisposed) 167 | return; 168 | Logger.Error("", ex); 169 | Logger.Info($"Start T5 Timer: {T5 / 1000} sec."); 170 | Thread.Sleep(T5); 171 | } 172 | 173 | }; 174 | } 175 | else 176 | { 177 | var server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 178 | server.Bind(new IPEndPoint(IpAddress, port)); 179 | server.Listen(0); 180 | 181 | _startImpl = () => 182 | { 183 | if (IsDisposed) return; 184 | CommunicationStateChanging(ConnectionState.Connecting); 185 | try 186 | { 187 | var socketAsyncEventArgs = new SocketAsyncEventArgs(); 188 | socketAsyncEventArgs.Completed += (sender, e) => 189 | { 190 | if (e.SocketError == SocketError.Success) 191 | { 192 | _socket = e.AcceptSocket; 193 | StartSocketReceive(); 194 | } 195 | else 196 | { 197 | Logger.Info($"Start T5 Timer: {T5 / 1000} sec."); 198 | Thread.Sleep(T5); 199 | CommunicationStateChanging(ConnectionState.Retry); 200 | } 201 | }; 202 | server.AcceptAsync(socketAsyncEventArgs); 203 | } 204 | catch (Exception ex) 205 | { 206 | if (IsDisposed) 207 | return; 208 | Logger.Error("", ex); 209 | } 210 | }; 211 | _stopImpl = () => 212 | { 213 | if (IsDisposed) 214 | server.Dispose(); 215 | }; 216 | } 217 | } 218 | 219 | 220 | 221 | public void Start() => new TaskFactory(TaskScheduler.Default).StartNew(_startImpl); 222 | 223 | public void Stop() => new TaskFactory(TaskScheduler.Default).StartNew(_stopImpl); 224 | 225 | private void Reset() 226 | { 227 | _timer7.Change(Timeout.Infinite, Timeout.Infinite); 228 | _timer8.Change(Timeout.Infinite, Timeout.Infinite); 229 | _timerLinkTest.Change(Timeout.Infinite, Timeout.Infinite); 230 | 231 | _secsDecoder.Reset(); 232 | _replyExpectedMsgs.Clear(); 233 | _stopImpl?.Invoke(); 234 | 235 | if (_socket is null) 236 | return; 237 | if (_socket.Connected) 238 | _socket.Shutdown(SocketShutdown.Both); 239 | _socket.Dispose(); 240 | _socket = null; 241 | } 242 | 243 | #endregion 244 | 245 | #region connection 246 | private void CommunicationStateChanging(ConnectionState state) 247 | { 248 | State = state; 249 | ConnectionChanged?.Invoke(this, new TEventArgs(state)); 250 | 251 | switch (State) 252 | { 253 | case ConnectionState.Connecting: 254 | break; 255 | case ConnectionState.Connected: 256 | #if !DISABLE_TIMER 257 | Logger.Info($"Start T7 Timer: {T7 / 1000} sec."); 258 | _timer7.Change(T7, Timeout.Infinite); 259 | #endif 260 | break; 261 | case ConnectionState.Selected: 262 | _timer7.Change(Timeout.Infinite, Timeout.Infinite); 263 | Logger.Info("Stop T7 Timer"); 264 | break; 265 | case ConnectionState.Retry: 266 | if (IsDisposed) 267 | return; 268 | Reset(); 269 | Task.Factory.StartNew(_startImpl); 270 | break; 271 | default: 272 | break; 273 | } 274 | } 275 | #endregion 276 | 277 | #region receive 278 | private void StartSocketReceive() 279 | { 280 | CommunicationStateChanging(ConnectionState.Connected); 281 | var receiveCompleteEventArgs = new SocketAsyncEventArgs(); 282 | receiveCompleteEventArgs.SetBuffer(_secsDecoder.Buffer, _secsDecoder.BufferOffset, _secsDecoder.BufferCount); 283 | receiveCompleteEventArgs.Completed += ReceiveCompleteEventArgs_Completed; 284 | if (!_socket.ReceiveAsync(receiveCompleteEventArgs)) 285 | ReceiveCompleteEventArgs_Completed(_socket, receiveCompleteEventArgs); 286 | } 287 | 288 | private void ReceiveCompleteEventArgs_Completed(object sender, SocketAsyncEventArgs e) 289 | { 290 | if (e.SocketError != SocketError.Success) 291 | { 292 | var ex = new SocketException((int)e.SocketError); 293 | Logger.Error($"RecieveComplete socket error:{ex.Message}, ErrorCode:{ex.SocketErrorCode}", ex); 294 | CommunicationStateChanging(ConnectionState.Retry); 295 | return; 296 | } 297 | 298 | try 299 | { 300 | _timer8.Change(Timeout.Infinite, Timeout.Infinite); 301 | var receiveCount = e.BytesTransferred; 302 | if (receiveCount == 0) 303 | { 304 | Logger.Error("receive 0 byte."); 305 | CommunicationStateChanging(ConnectionState.Retry); 306 | return; 307 | } 308 | 309 | if (_secsDecoder.Decode(receiveCount)) 310 | { 311 | #if !DISABLE_T8 312 | Trace.WriteLine($"Start T8 Timer: {T8 / 1000} sec."); 313 | _timer8.Change(T8, Timeout.Infinite); 314 | #endif 315 | } 316 | 317 | if (_secsDecoder.Buffer.Length != DecoderBufferSize) 318 | { 319 | // buffer size changed 320 | e.SetBuffer(_secsDecoder.Buffer, _secsDecoder.BufferOffset, _secsDecoder.BufferCount); 321 | DecoderBufferSize = _secsDecoder.Buffer.Length; 322 | } 323 | else 324 | { 325 | e.SetBuffer(_secsDecoder.BufferOffset, _secsDecoder.BufferCount); 326 | } 327 | 328 | if (_socket is null || IsDisposed) 329 | return; 330 | 331 | if (!_socket.ReceiveAsync(e)) 332 | ReceiveCompleteEventArgs_Completed(sender, e); 333 | } 334 | catch (Exception ex) 335 | { 336 | Logger.Error("Unexpected exception", ex); 337 | CommunicationStateChanging(ConnectionState.Retry); 338 | } 339 | } 340 | 341 | private void HandleDataMessage(MessageHeader header, SecsMessage secsMessage) 342 | { 343 | var systemByte = header.SystemBytes; 344 | if (header.DeviceId != DeviceId && secsMessage.S != 9 && secsMessage.F != 1) 345 | { 346 | Logger.Error("Received Unrecognized Device Id Message"); 347 | SecsMessage replyWrongDeviceIdMsg = new SecsMessage( 348 | 9, 349 | 1, 350 | false, 351 | Resources.S9F1, 352 | Item.B(header.EncodeTo(new byte[10])));// 将header作为消息体返回 353 | SendDataMessageAsync(replyWrongDeviceIdMsg, NewSystemId); 354 | 355 | if (_replyExpectedMsgs.TryGetValue(systemByte, out var ar1)) 356 | ar1.HandleReplyMessage(replyWrongDeviceIdMsg); 357 | return; 358 | } 359 | 360 | if (secsMessage.F % 2 != 0) 361 | { 362 | if (secsMessage.S != 9) 363 | { 364 | _taskFactory.StartNew((wrapper) => 365 | { 366 | PrimaryMessageReceived?.Invoke(this, wrapper as PrimaryMessageWrapper); 367 | }, new PrimaryMessageWrapper(this, header, secsMessage)); 368 | return; 369 | } 370 | // Error message 371 | var headerBytes = secsMessage.SecsItem.GetValues();// 解析出MessageHeader的Bytes 372 | systemByte = BitConverter.ToInt32(new byte[] { headerBytes[9], headerBytes[8], headerBytes[7], headerBytes[6] }, 0); 373 | } 374 | 375 | if (_replyExpectedMsgs.TryGetValue(systemByte, out var ar)) 376 | ar.HandleReplyMessage(secsMessage); 377 | } 378 | 379 | private void HandlerControlMessage(MessageHeader header) 380 | { 381 | var systemByte = header.SystemBytes; 382 | if ((byte)header.MessageType % 2 == 0) 383 | {// 收到Control message的response信息 384 | if (_replyExpectedMsgs.TryGetValue(systemByte, out var ar)) 385 | { 386 | ar.SetResult(ControlMessage); 387 | } 388 | else 389 | { 390 | Logger.Error("Received Unexpected Control Message: " + header.MessageType); 391 | return; 392 | } 393 | } 394 | 395 | Logger.Info("Received Control Message: " + header.MessageType); 396 | switch (header.MessageType) 397 | { 398 | case MessageType.DataMessage: 399 | break; 400 | case MessageType.SelectRequest: 401 | SendControlMessage(MessageType.SelectResponse, systemByte); 402 | CommunicationStateChanging(ConnectionState.Selected); 403 | break; 404 | case MessageType.SelectResponse: 405 | switch (header.F) 406 | { 407 | case 0: 408 | CommunicationStateChanging(ConnectionState.Selected); 409 | break; 410 | case 1: 411 | Logger.Error("Communication Already Active."); 412 | break; 413 | case 2: 414 | Logger.Error("Connection Not Ready"); 415 | break; 416 | case 3: 417 | Logger.Error("Connection Exhaust"); 418 | break; 419 | default: 420 | Logger.Error("Connection Status is unknown."); 421 | break; 422 | } 423 | break; 424 | case MessageType.Deselect_req: 425 | break; 426 | case MessageType.Deselect_rsp: 427 | break; 428 | case MessageType.LinkTestRequest: 429 | SendControlMessage(MessageType.LinkTestResponse, systemByte); 430 | break; 431 | case MessageType.LinkTestResponse: 432 | break; 433 | case MessageType.Reject_req: 434 | break; 435 | case MessageType.SeperateRequest: 436 | CommunicationStateChanging(ConnectionState.Retry); 437 | break; 438 | default: 439 | break; 440 | } 441 | } 442 | #endregion 443 | 444 | #region send message 445 | private void SendControlMessage(MessageType messageType, int systemByte) 446 | { 447 | var token = new TaskCompletionSourceToken(ControlMessage, systemByte, messageType); 448 | if ((byte)messageType % 2 == 1 && messageType != MessageType.SeperateRequest) 449 | { 450 | _replyExpectedMsgs[systemByte] = token; 451 | } 452 | 453 | var sendEventArgs = new SocketAsyncEventArgs 454 | { 455 | BufferList = new List>(2) 456 | { 457 | ControlMessageLengthBytes, 458 | new ArraySegment(new MessageHeader 459 | { 460 | DeviceId = 0xffff, 461 | MessageType = messageType, 462 | SystemBytes = systemByte 463 | }.EncodeTo(new byte[10])) 464 | }, 465 | UserToken = token, 466 | }; 467 | 468 | sendEventArgs.Completed += SendControlMessage_Completed; 469 | if (!_socket.SendAsync(sendEventArgs)) 470 | SendControlMessage_Completed(_socket, sendEventArgs); 471 | } 472 | 473 | private void SendControlMessage_Completed(object sender, SocketAsyncEventArgs e) 474 | { 475 | var completeToken = e.UserToken as TaskCompletionSourceToken; 476 | if (e.SocketError != SocketError.Success) 477 | { 478 | completeToken.SetException(new SocketException((int)e.SocketError)); 479 | return; 480 | } 481 | 482 | Logger.Info("Sent Control message: " + completeToken.MsgType); 483 | if (_replyExpectedMsgs.ContainsKey(completeToken.Id)) 484 | { 485 | if (!completeToken.Task.Wait(T6)) 486 | { 487 | Logger.Error($"T6 Timeout: {T6 / 1000} sec."); 488 | CommunicationStateChanging(ConnectionState.Retry); 489 | } 490 | _replyExpectedMsgs.TryRemove(completeToken.Id, out TaskCompletionSourceToken _); 491 | } 492 | } 493 | 494 | internal Task SendDataMessageAsync(SecsMessage secsMessage, int systemByte) 495 | { 496 | if (State != ConnectionState.Selected) 497 | throw new SecsException("Device is not selected"); 498 | 499 | var token = new TaskCompletionSourceToken(secsMessage, systemByte, MessageType.DataMessage); 500 | 501 | if (secsMessage.ReplyExpected) 502 | _replyExpectedMsgs[systemByte] = token; 503 | 504 | var header = new MessageHeader() 505 | { 506 | S = secsMessage.S, 507 | F = secsMessage.F, 508 | ReplyExpected = secsMessage.ReplyExpected, 509 | DeviceId = DeviceId, 510 | SystemBytes = systemByte, 511 | }; 512 | var bufferList = secsMessage.RawDatas.Value; 513 | bufferList[1] = new ArraySegment(header.EncodeTo(new byte[10])); 514 | var sendEventArgs = new SocketAsyncEventArgs 515 | { 516 | BufferList = bufferList, 517 | UserToken = token, 518 | }; 519 | sendEventArgs.Completed += SendDataMessage_Completed; 520 | if (!_socket.SendAsync(sendEventArgs)) 521 | SendDataMessage_Completed(_socket, sendEventArgs); 522 | return token.Task; 523 | } 524 | 525 | private void SendDataMessage_Completed(object sender, SocketAsyncEventArgs e) 526 | { 527 | var completeToken = e.UserToken as TaskCompletionSourceToken; 528 | if (e.SocketError != SocketError.Success) 529 | { 530 | completeToken.SetException(new SocketException((int)e.SocketError)); 531 | CommunicationStateChanging(ConnectionState.Retry); 532 | return; 533 | } 534 | 535 | Trace.WriteLine("Send Data Message: " + completeToken.MessageSent.ToString()); 536 | if (!_replyExpectedMsgs.ContainsKey(completeToken.Id)) 537 | { 538 | completeToken.SetResult(null); 539 | return; 540 | } 541 | 542 | try 543 | { 544 | if (!completeToken.Task.Wait(T3)) 545 | { 546 | Logger.Error($"T3 Timeout[id=0x{completeToken.Id:X8}]: {T3 / 1000} sec."); 547 | completeToken.SetException(new SecsException(completeToken.MessageSent, Resources.T3Timeout)); 548 | } 549 | } 550 | catch (Exception) 551 | { 552 | } 553 | finally 554 | { 555 | _replyExpectedMsgs.TryRemove(completeToken.Id, out TaskCompletionSourceToken _); 556 | } 557 | } 558 | /// 559 | /// Asynchronously send message to device . 560 | /// 561 | /// primary message 562 | /// senondary 563 | public Task SendAsync(SecsMessage secsMessage) => SendAsync(secsMessage, NewSystemId); 564 | /// 565 | /// Asynchronously send message to device . 566 | /// 567 | /// 568 | /// 569 | /// null 570 | public Task SendAsync(SecsMessage secsMessage, int systemId) => SendDataMessageAsync(secsMessage, systemId); 571 | #endregion 572 | 573 | #region dispose 574 | private const int DisposalNotStarted = 0; 575 | private const int DisposalComplete = 1; 576 | private int _disposeStage; 577 | private StreamDecoder _secsDecoder; 578 | private Socket _socket; 579 | private static readonly ArraySegment ControlMessageLengthBytes = new ArraySegment(new byte[] { 0, 0, 0, 10 }); 580 | 581 | public bool IsDisposed => Interlocked.CompareExchange(ref _disposeStage, DisposalComplete, DisposalComplete) == DisposalComplete; 582 | 583 | public int DecoderBufferSize { get; private set; } 584 | 585 | private static readonly SecsMessage ControlMessage = new SecsMessage(0, 0, String.Empty); 586 | 587 | public void Dispose() 588 | { 589 | if (Interlocked.Exchange(ref _disposeStage, DisposalComplete) != DisposalNotStarted) 590 | return; 591 | 592 | ConnectionChanged = null; 593 | if (State == ConnectionState.Selected) 594 | SendControlMessage(MessageType.SeperateRequest, NewSystemId); 595 | Reset(); 596 | _timer7.Dispose(); 597 | _timer8.Dispose(); 598 | _timerLinkTest.Dispose(); 599 | } 600 | #endregion 601 | } 602 | } 603 | -------------------------------------------------------------------------------- /Secs4Frmk4/SecsMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Granda.HSMS.Properties; 4 | 5 | namespace Granda.HSMS 6 | { 7 | public sealed class SecsMessage 8 | { 9 | #region 静态构造方法 10 | static SecsMessage() 11 | { 12 | if (!BitConverter.IsLittleEndian) 13 | { 14 | throw new PlatformNotSupportedException("This version is only work on little endian hardware."); 15 | } 16 | } 17 | #endregion 18 | 19 | #region Fields/Properties 20 | /// 21 | /// 重写ToString 22 | /// 23 | /// 24 | public override string ToString() => $"{Name ?? String.Empty}:S{S}F{F} {(ReplyExpected ? "W" : string.Empty)}"; 25 | /// 26 | /// Function Name 27 | /// 28 | public string Name { get; internal set; } 29 | /// 30 | /// message stream number 31 | /// 32 | public byte F { get; } 33 | /// 34 | /// message function number 35 | /// 36 | public byte S { get; } 37 | 38 | /// 39 | /// expect reply message 40 | /// 41 | public bool ReplyExpected { get; internal set; } 42 | 43 | /// 44 | /// the root item of message 45 | /// 46 | public Item SecsItem { get; } 47 | 48 | public int SystenBytes { get; set; } 49 | 50 | internal readonly Lazy>> RawDatas; 51 | 52 | public IList> RawBytes => RawDatas.Value.AsReadOnly(); 53 | 54 | private static readonly List> EmptyMsgDatas = new List> 55 | { 56 | new ArraySegment(new byte[]{0, 0, 0, 10}),// total length: 10 57 | new ArraySegment(new byte[]{ })// header 58 | // item 59 | }; 60 | #endregion 61 | 62 | #region 构造方法 63 | /// 64 | /// constructor of SecsMessage 65 | /// 66 | /// 67 | /// 68 | /// 69 | /// 70 | /// 71 | public SecsMessage(byte s, byte f, bool replyExpected = true, string name = null, Item secsItem = null) 72 | { 73 | if (s > 0b0111_1111) 74 | { 75 | throw new ArgumentOutOfRangeException(nameof(s), s, Resources.SecsMessageStreamNumberMustLessThan127); 76 | } 77 | S = s; 78 | F = f; 79 | ReplyExpected = replyExpected; 80 | Name = name; 81 | SecsItem = secsItem; 82 | 83 | RawDatas = new Lazy>>(() => 84 | { 85 | if (SecsItem == null) 86 | { 87 | return EmptyMsgDatas; 88 | } 89 | 90 | var result = new List> 91 | { 92 | default(ArraySegment),// total length 93 | new ArraySegment(new byte[]{})// header 94 | //item 95 | 96 | }; 97 | 98 | var length = 10 + SecsItem.EncodeTo(result);// total length = item + header; 99 | 100 | byte[] msgLengthByte = BitConverter.GetBytes(length); 101 | Array.Reverse(msgLengthByte); 102 | result[0] = new ArraySegment(msgLengthByte); 103 | 104 | return result; 105 | }); 106 | } 107 | /// 108 | /// constructor of SecsMessage 109 | /// 110 | /// 111 | /// 112 | /// 113 | /// 114 | public SecsMessage(byte s, byte f, string name, Item item = null) : this(s, f, true, name, item) { } 115 | #endregion 116 | 117 | } 118 | } -------------------------------------------------------------------------------- /Secs4Frmk4/Sml/SmlExtension.cs: -------------------------------------------------------------------------------- 1 | #region 文件说明 2 | /*------------------------------------------------------------------------------ 3 | // Copyright © 2018 Granda. All Rights Reserved. 4 | // 苏州广林达电子科技有限公司 版权所有 5 | //------------------------------------------------------------------------------ 6 | // File Name: SmlExtension 7 | // Author: Ivan JL Zhang Date: 2018/4/27 13:10:46 Version: 1.0.0 8 | // Description: 9 | // 10 | // 11 | // Revision History: 12 | // 13 | // 14 | //----------------------------------------------------------------------------*/ 15 | #endregion 16 | using System; 17 | using System.Collections.Generic; 18 | using System.IO; 19 | using static Granda.HSMS.Item; 20 | namespace Granda.HSMS.Sml 21 | { 22 | public static class SmlExtension 23 | { 24 | #region ToSml 25 | public static string ToSml(this SecsMessage secsMessage) 26 | { 27 | if (secsMessage == null) 28 | return null; 29 | using (var sw = new StringWriter()) 30 | { 31 | secsMessage.WriteTo(sw, 0); 32 | return sw.ToString(); 33 | } 34 | } 35 | 36 | private static void WriteTo(this SecsMessage secsMessage, TextWriter textWriter, int indent = 4) 37 | { 38 | textWriter.WriteLine(secsMessage.ToString()); 39 | if (secsMessage.SecsItem != null) 40 | { 41 | Write(textWriter, secsMessage.SecsItem, indent); 42 | } 43 | textWriter.Write("."); 44 | } 45 | 46 | private static void Write(TextWriter textWriter, Item secsItem, int indent = 4) 47 | { 48 | var indentStr = new string(' ', indent); 49 | textWriter.Write($"{indentStr}<{secsItem.Format.ToSml()} [{secsItem.Count}]");// ().ToHexString()); 67 | break; 68 | default: 69 | throw new ArgumentOutOfRangeException(nameof(secsItem.Format), secsItem.Format, "invalid SecsFormat value"); 70 | } 71 | textWriter.WriteLine('>'); 72 | } 73 | public static string ToHexString(this byte[] value) 74 | { 75 | if (value.Length == 0) return string.Empty; 76 | int length = value.Length * 3; 77 | char[] chs = new char[length]; 78 | for (int ci = 0, i = 0; ci < length; ci += 3) 79 | { 80 | byte num = value[i++]; 81 | chs[ci] = GetHexValue(num / 0x10); 82 | chs[ci + 1] = GetHexValue(num % 0x10); 83 | chs[ci + 2] = ' '; 84 | } 85 | return new string(chs, 0, length - 1); 86 | 87 | char GetHexValue(int i) => (i < 10) ? (char)(i + 0x30) : (char)((i - 10) + 0x41); 88 | } 89 | public static string ToSml(this SecsFormat secsFormat) 90 | { 91 | switch (secsFormat) 92 | { 93 | case SecsFormat.List: 94 | return "L"; 95 | 96 | case SecsFormat.ASCII: 97 | return "A"; 98 | case SecsFormat.Binary: 99 | return "B"; 100 | default: 101 | throw new ArgumentOutOfRangeException(nameof(secsFormat), (int)secsFormat, "Invalid enum value"); 102 | } 103 | } 104 | #endregion 105 | 106 | #region ToSecsMessage 107 | public static SecsMessage ToSecsMessage(this string str) 108 | { 109 | using (var sr = new StringReader(str)) 110 | { 111 | return sr.ToSecsMessage(); 112 | } 113 | } 114 | 115 | private static SecsMessage ToSecsMessage(this TextReader reader) 116 | { 117 | var line = reader.ReadLine(); 118 | #region parse First line 119 | int index = line.IndexOf(':'); 120 | var name = line.Substring(0, index); 121 | line = line.Substring(index + 1).Trim(); 122 | index = line.IndexOf("S", 0, 1, StringComparison.OrdinalIgnoreCase); 123 | int indeF = line.IndexOf("F", 0, line.Length, StringComparison.OrdinalIgnoreCase); 124 | var str = line.Substring(index, indeF - index - 1); 125 | Byte.TryParse(line.Substring(index + 1, indeF - index - 1), out byte s); 126 | 127 | line = line.Substring(indeF + 1); 128 | bool replyExpected = false; 129 | if (line.Contains("W")) 130 | replyExpected = true; 131 | var f = replyExpected 132 | ? byte.Parse(line.Substring(0, line.IndexOf('W')).Trim()) 133 | : byte.Parse(line.Trim()); 134 | #endregion 135 | 136 | Item rootItem = null; 137 | var stack = new Stack>(); 138 | while ((line = reader.ReadLine()) != null && ParseItem(line, stack, ref rootItem)) { } 139 | 140 | return new SecsMessage(s, f, replyExpected, name, rootItem); 141 | } 142 | 143 | private static bool ParseItem(string line, Stack> stack, ref Item rootItem) 144 | { 145 | line = line.TrimStart(); 146 | if (line[0] == '.') 147 | return false; 148 | 149 | if (line[0] == '>') 150 | { 151 | var itemList = stack.Pop(); 152 | var item = itemList.Count > 0 ? L(itemList) : L(); 153 | if (stack.Count > 0) 154 | stack.Peek().Add(item); 155 | else 156 | rootItem = item; 157 | return true; 158 | } 159 | 160 | int indexItemL = line.IndexOf('<') + 1; 161 | int indexSizeL = line.IndexOf('[', indexItemL); 162 | 163 | string format = line.Substring(indexItemL, indexSizeL - indexItemL).Trim(); 164 | 165 | if (format == "L") 166 | stack.Push(new List()); 167 | else 168 | { 169 | int indexSizeR = line.IndexOf(']', indexSizeL); 170 | int indexItemR = line.IndexOf('>'); 171 | string valueStr = line.Substring(indexSizeR + 1, indexItemR - indexSizeR - 1); 172 | Item item = Create(format, valueStr); 173 | if (stack.Count > 0) 174 | stack.Peek().Add(item); 175 | else 176 | rootItem = item; 177 | } 178 | 179 | return true; 180 | } 181 | 182 | private static readonly Tuple, Func> AParser = new Tuple, Func>(A, A); 183 | 184 | private static Item Create(string format, string valueStr) 185 | { 186 | switch (format) 187 | { 188 | case "A": 189 | return ParseStringItem(valueStr, AParser); 190 | default: 191 | throw new SecsException("Unknown SML format :" + format); 192 | } 193 | 194 | Item ParseStringItem(string str, Tuple, Func> parser) 195 | { 196 | str = str.TrimStart(' ', '\'', '"').TrimEnd(' ', '\'', '"'); 197 | return String.IsNullOrEmpty(str) 198 | ? parser.Item1() 199 | : parser.Item2(str); 200 | } 201 | } 202 | #endregion 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /Secs4Frmk4/StreamDecoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace Granda.HSMS 6 | { 7 | internal sealed class StreamDecoder 8 | { 9 | public byte[] Buffer => _buffer; 10 | 11 | public int BufferCount => Buffer.Length - _bufferOffset; 12 | 13 | public int BufferOffset => _bufferOffset; 14 | private byte[] _buffer; 15 | private int _bufferOffset; 16 | 17 | private int _decodeIndex; 18 | private Action _dataMsgHandler; 19 | private Action _controlMsgHandler; 20 | private Decoder[] _decoders; 21 | private uint _messageDataLength; 22 | private MessageHeader _messageHeader; 23 | private SecsFormat _format; 24 | private byte _lengthBits; 25 | private int _itemLength; 26 | private Stack> _stack = new Stack>(); 27 | private int _previousRemainedCount; 28 | private int _decodeStep; 29 | private readonly byte[] _itemLengthBytes = new byte[4]; 30 | 31 | /// 32 | /// decoder step 33 | /// 34 | /// 35 | /// 36 | /// 37 | public delegate int Decoder(ref int length, out int need); 38 | 39 | internal StreamDecoder(int streamBufferSize, Action controlMsgHandler, Action dataMsgHandler) 40 | { 41 | _buffer = new byte[streamBufferSize]; 42 | _bufferOffset = 0; 43 | _decodeIndex = 0; 44 | _dataMsgHandler = dataMsgHandler; 45 | _controlMsgHandler = controlMsgHandler; 46 | 47 | _decoders = new Decoder[] 48 | { 49 | GetTotalMessageLength, 50 | GetMessageHeader, 51 | GetItemHeader, 52 | GetItemLength, 53 | GetItem, 54 | }; 55 | } 56 | 57 | #region Decoders 58 | // 0: get total message length 4 bytes 59 | int GetTotalMessageLength(ref int length, out int need) 60 | { 61 | if (!CheckAvailable(ref length, 4, out need)) 62 | return 0; 63 | Array.Reverse(_buffer, _decodeIndex, 4); 64 | _messageDataLength = BitConverter.ToUInt32(_buffer, _decodeIndex); 65 | //Trace.WriteLine($"Get Message Length: {_messageDataLength}"); 66 | _decodeIndex += 4; 67 | length -= 4; 68 | return GetMessageHeader(ref length, out need); 69 | } 70 | // 1: get message header 10 bytes 71 | private int GetMessageHeader(ref int length, out int need) 72 | { 73 | if (!CheckAvailable(ref length, 10, out need)) 74 | return 1; 75 | _messageHeader = MessageHeader.Decode(_buffer, _decodeIndex); 76 | _decodeIndex += 10; 77 | _messageDataLength -= 10; 78 | length -= 10; 79 | if (_messageDataLength == 0) 80 | { 81 | if (_messageHeader.MessageType == MessageType.DataMessage) 82 | _dataMsgHandler(_messageHeader, new SecsMessage(_messageHeader.S, _messageHeader.F, _messageHeader.ReplyExpected, string.Empty)); 83 | else 84 | _controlMsgHandler(_messageHeader); 85 | return 0; 86 | } 87 | 88 | if (length >= _messageDataLength) 89 | { 90 | Trace.WriteLine("Get Complete Data Message with total data"); 91 | _dataMsgHandler(_messageHeader, new SecsMessage(_messageHeader.S, 92 | _messageHeader.F, 93 | _messageHeader.ReplyExpected, 94 | string.Empty, 95 | BufferdDecodeItem(_buffer, ref _decodeIndex))); 96 | 97 | length -= (int)_messageDataLength; 98 | _messageDataLength = 0; 99 | return 0;// complete with message received 100 | } 101 | 102 | return GetItemHeader(ref length, out need); 103 | } 104 | // 2: get _format + lengtnBits(2bit) 1 byte 105 | private int GetItemHeader(ref int length, out int need) 106 | { 107 | if (!CheckAvailable(ref length, 1, out need)) 108 | return 2; 109 | _format = (SecsFormat)(_buffer[_decodeIndex] & 0b1111_1100); 110 | _lengthBits = (byte)(_buffer[_decodeIndex] & 0b0000_0011); 111 | _decodeIndex++; 112 | _messageDataLength--; 113 | length--; 114 | return GetItemLength(ref length, out need); 115 | } 116 | // 3: get _itemLength _lengthBits bytes, at most 3 byte 117 | private int GetItemLength(ref int length, out int need) 118 | { 119 | if (!CheckAvailable(ref length, _lengthBits, out need)) 120 | return 3; 121 | Array.Copy(_buffer, _decodeIndex, _itemLengthBytes, 0, _lengthBits); 122 | Array.Reverse(_itemLengthBytes, 0, _lengthBits); 123 | 124 | _itemLength = BitConverter.ToInt32(_itemLengthBytes, 0); 125 | Array.Clear(_itemLengthBytes, 0, 4); 126 | Trace.WriteLineIf(_format != SecsFormat.List, $"Get format: {_format}, length: {_itemLength}"); 127 | 128 | _decodeIndex += _lengthBits; 129 | _messageDataLength -= _lengthBits; 130 | length -= _lengthBits; 131 | return GetItem(ref length, out need); 132 | } 133 | // 4: get item value 134 | private int GetItem(ref int length, out int need) 135 | { 136 | need = 0; 137 | Item item; 138 | if (_format == SecsFormat.List) 139 | { 140 | if (_itemLength == 0) 141 | { 142 | item = Item.L(); 143 | } 144 | else 145 | { 146 | _stack.Push(new List(_itemLength)); 147 | return GetItemHeader(ref length, out need); 148 | } 149 | } 150 | else 151 | { 152 | if (!CheckAvailable(ref length, _itemLength, out need)) 153 | return 4; 154 | 155 | item = Item.BytesDecode(ref _format, _buffer, ref _decodeIndex, ref _itemLength); 156 | Trace.WriteLine($"Complete Item: {_format}"); 157 | 158 | _decodeIndex += _itemLength; 159 | _messageDataLength -= (uint)_itemLength; 160 | length -= _itemLength; 161 | } 162 | 163 | if (_stack.Count == 0) 164 | { 165 | Trace.WriteLine("Get Complete Data Message by stream decoded"); 166 | _dataMsgHandler(_messageHeader, new SecsMessage(_messageHeader.S, _messageHeader.F, _messageHeader.ReplyExpected, string.Empty, item)); 167 | return 0; 168 | } 169 | 170 | var list = _stack.Peek(); 171 | list.Add(item); 172 | 173 | while (list.Count == list.Capacity) 174 | { 175 | item = Item.L(_stack.Pop()); 176 | Trace.WriteLine($"Complete List: {item.Count}"); 177 | if (_stack.Count > 0) 178 | { 179 | list = _stack.Peek(); 180 | list.Add(item); 181 | } 182 | else 183 | { 184 | Trace.WriteLine("Get Complete Data Message by stream decoded"); 185 | _dataMsgHandler(_messageHeader, new SecsMessage(_messageHeader.S, _messageHeader.F, _messageHeader.ReplyExpected, string.Empty, item)); 186 | return 0; 187 | } 188 | } 189 | 190 | return GetItemHeader(ref length, out need); 191 | } 192 | 193 | 194 | private Item BufferdDecodeItem(byte[] bytes, ref int index) 195 | { 196 | var format = (SecsFormat)(bytes[index] & 0b1111_1100); 197 | var lengthBits = (byte)(bytes[index] & 0b0000_0011); 198 | index++; 199 | 200 | var itemLengthBytes = new byte[4]; 201 | Array.Copy(bytes, index, itemLengthBytes, 0, lengthBits); 202 | Array.Reverse(itemLengthBytes, 0, lengthBits); 203 | 204 | int dataLength = BitConverter.ToInt32(itemLengthBytes, 0); 205 | index += lengthBits; 206 | 207 | if (format == SecsFormat.List) 208 | { 209 | if (dataLength == 0) 210 | return Item.L(); 211 | var list = new List(dataLength); 212 | for (int indey = 0; indey < dataLength; indey++) 213 | { 214 | list.Add(BufferdDecodeItem(bytes, ref index)); 215 | } 216 | return Item.L(list); 217 | } 218 | 219 | var item = Item.BytesDecode(ref format, bytes, ref index, ref dataLength); 220 | index += dataLength; 221 | return item; 222 | } 223 | 224 | private bool CheckAvailable(ref int length, int required, out int need) 225 | { 226 | need = required - length;// 超过了收到的字节长度,报错 227 | if (need > 0) 228 | return false; 229 | need = 0; 230 | return true; 231 | } 232 | #endregion 233 | 234 | internal bool Decode(int length) 235 | { 236 | Debug.Assert(length > 0, "decode data length is 0."); 237 | 238 | string byteStr = String.Empty; 239 | for (int index = 0; index < length; index++) 240 | { 241 | byteStr += $"{_buffer[index]:X2} "; 242 | if ((index + 1) % 10 == 0) 243 | byteStr += " "; 244 | if ((index + 1) % 20 == 0) 245 | byteStr += "\r\n"; 246 | } 247 | Logger.Info(byteStr); 248 | var decodeLength = length; 249 | length += _previousRemainedCount;// total available length = current length + previous remained 250 | int need; 251 | var nextStep = _decodeStep; 252 | do 253 | { 254 | _decodeStep = nextStep; 255 | nextStep = _decoders[_decodeStep](ref length, out need); 256 | 257 | } while (nextStep != _decodeStep); 258 | Debug.Assert(_decodeIndex >= _bufferOffset, "decode index should ahead of buffer index"); 259 | 260 | var remainCount = length; 261 | Debug.Assert(remainCount >= 0, "remain count is only possible grater and equal zero"); 262 | //Trace.WriteLine($"remain data length: {remainCount}"); 263 | Trace.WriteLineIf(_messageDataLength > 0, $"need data count: {need}"); 264 | 265 | if (remainCount == 0) 266 | { 267 | if (need > Buffer.Length) 268 | { 269 | var newSize = need * 2; 270 | Trace.WriteLine($@"<>: current size = {_buffer.Length}, new size = {newSize}"); 271 | 272 | // increase buffer size 273 | _buffer = new byte[newSize]; 274 | } 275 | _bufferOffset = 0; 276 | _decodeIndex = 0; 277 | _previousRemainedCount = 0; 278 | } 279 | else 280 | { 281 | _bufferOffset += decodeLength; 282 | var nextStepReqiredCount = remainCount + need; 283 | if (nextStepReqiredCount > BufferCount) 284 | { 285 | if (nextStepReqiredCount > Buffer.Length) 286 | { 287 | var newSize = Math.Max(_messageDataLength / 2, nextStepReqiredCount) * 2; 288 | Trace.WriteLine($@"<>: current size = {_buffer.Length}, remained = {remainCount}, new size = {newSize}"); 289 | 290 | // out of total buffer size 291 | // increase buffer size 292 | var newBuffer = new byte[newSize]; 293 | // keep remained data to new buffer's head 294 | Array.Copy(_buffer, _bufferOffset - remainCount, newBuffer, 0, remainCount); 295 | _buffer = newBuffer; 296 | } 297 | 298 | else 299 | { 300 | Trace.WriteLine($@"<>: available = {BufferCount}, need = {nextStepReqiredCount}, remained = {remainCount}"); 301 | 302 | // move remained data to buffer's head 303 | Array.Copy(_buffer, _bufferOffset - remainCount, _buffer, 0, remainCount); 304 | } 305 | _bufferOffset = remainCount; 306 | _decodeIndex = 0; 307 | } 308 | _previousRemainedCount = remainCount; 309 | } 310 | return _messageDataLength > 0; 311 | } 312 | 313 | public void Reset() 314 | { 315 | _stack.Clear(); 316 | _decodeStep = 0; 317 | _decodeIndex = 0; 318 | _bufferOffset = 0; 319 | _messageDataLength = 0; 320 | _previousRemainedCount = 0; 321 | } 322 | } 323 | } -------------------------------------------------------------------------------- /Secs4Frmk4/SystemByteGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace Granda.HSMS 5 | { 6 | internal sealed class SystemByteGenerator 7 | { 8 | private int _systemByte = new Random(Guid.NewGuid().GetHashCode()).Next(); 9 | public int New() => Interlocked.Increment(ref _systemByte); 10 | } 11 | } -------------------------------------------------------------------------------- /Secs4Frmk4/TEventArgs.cs: -------------------------------------------------------------------------------- 1 | #region 文件说明 2 | /*------------------------------------------------------------------------------ 3 | // Copyright © 2018 Granda. All Rights Reserved. 4 | // 苏州广林达电子科技有限公司 版权所有 5 | //------------------------------------------------------------------------------ 6 | // File Name: TEventArgs 7 | // Author: Ivan JL Zhang Date: 2018/4/25 15:31:52 Version: 1.0.0 8 | // Description: 9 | // 10 | // 11 | // Revision History: 12 | // 13 | // 14 | //----------------------------------------------------------------------------*/ 15 | #endregion 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Linq; 19 | using System.Text; 20 | 21 | namespace Granda.HSMS 22 | { 23 | public class TEventArgs : EventArgs 24 | { 25 | public TEventArgs(T date) 26 | { 27 | this.Data = date; 28 | } 29 | 30 | public T Data { get; private set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Secs4Frmk4/TaskCompletionSourceToken.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Granda.HSMS.Properties; 3 | 4 | namespace Granda.HSMS 5 | { 6 | internal class TaskCompletionSourceToken : TaskCompletionSource 7 | { 8 | internal readonly SecsMessage MessageSent; 9 | internal readonly int Id; 10 | internal readonly MessageType MsgType; 11 | 12 | internal TaskCompletionSourceToken(SecsMessage secsMessage, int systemByte, MessageType dataMessage) 13 | { 14 | this.MessageSent = secsMessage; 15 | this.Id = systemByte; 16 | this.MsgType = dataMessage; 17 | } 18 | 19 | internal void HandleReplyMessage(SecsMessage replyMsg) 20 | { 21 | replyMsg.Name = MessageSent.Name; 22 | if (replyMsg.F == 0) 23 | { 24 | SetException(new SecsException(MessageSent, Resources.SxF0)); 25 | return; 26 | } 27 | 28 | if (replyMsg.S == 9) 29 | { 30 | switch (replyMsg.F) 31 | { 32 | case 1: 33 | SetException(new SecsException(MessageSent, Resources.S9F1)); 34 | break; 35 | case 3: 36 | SetException(new SecsException(MessageSent, Resources.S9F3)); 37 | break; 38 | case 5: 39 | SetException(new SecsException(MessageSent, Resources.S9F5)); 40 | break; 41 | case 7: 42 | SetException(new SecsException(MessageSent, Resources.S9F7)); 43 | break; 44 | case 9: 45 | SetException(new SecsException(MessageSent, Resources.S9F9)); 46 | break; 47 | case 11: 48 | SetException(new SecsException(MessageSent, Resources.S9F11)); 49 | break; 50 | case 13: 51 | SetException(new SecsException(MessageSent, Resources.S9F13)); 52 | break; 53 | default: 54 | SetException(new SecsException(MessageSent, Resources.S9Fy)); 55 | break; 56 | } 57 | return; 58 | } 59 | 60 | SetResult(replyMsg); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Secs4Frmk4/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Secs4Net4.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2011 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Secs4Frmk4", "Secs4Frmk4\Secs4Frmk4.csproj", "{7E51F1AE-3104-4858-AF49-6A81AABE7BCB}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Granda.AATS.Log", "..\Granda.AATS.Log\Granda.AATS.Log.csproj", "{57E0A100-47FD-43DE-BBDA-7908B0DC5DDF}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wrap", "Wrap", "{CD28F2E1-60C9-4A70-A2B3-7C6BDB0F5722}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecsClient", "..\Granda.ATTS.CIMModule\SecsClient\SecsClient.csproj", "{55EF156D-1DF2-4D32-9266-BD256118F1DF}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecsServer", "..\Granda.ATTS.CIMModule\SecsServer\SecsServer.csproj", "{59B27FD7-D199-43F8-9942-BA4183D9C0C4}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {7E51F1AE-3104-4858-AF49-6A81AABE7BCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {7E51F1AE-3104-4858-AF49-6A81AABE7BCB}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {7E51F1AE-3104-4858-AF49-6A81AABE7BCB}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {7E51F1AE-3104-4858-AF49-6A81AABE7BCB}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {57E0A100-47FD-43DE-BBDA-7908B0DC5DDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {57E0A100-47FD-43DE-BBDA-7908B0DC5DDF}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {57E0A100-47FD-43DE-BBDA-7908B0DC5DDF}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {57E0A100-47FD-43DE-BBDA-7908B0DC5DDF}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {55EF156D-1DF2-4D32-9266-BD256118F1DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {55EF156D-1DF2-4D32-9266-BD256118F1DF}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {55EF156D-1DF2-4D32-9266-BD256118F1DF}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {55EF156D-1DF2-4D32-9266-BD256118F1DF}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {59B27FD7-D199-43F8-9942-BA4183D9C0C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {59B27FD7-D199-43F8-9942-BA4183D9C0C4}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {59B27FD7-D199-43F8-9942-BA4183D9C0C4}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {59B27FD7-D199-43F8-9942-BA4183D9C0C4}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(NestedProjects) = preSolution 43 | {57E0A100-47FD-43DE-BBDA-7908B0DC5DDF} = {CD28F2E1-60C9-4A70-A2B3-7C6BDB0F5722} 44 | EndGlobalSection 45 | GlobalSection(ExtensibilityGlobals) = postSolution 46 | VisualSVNWorkingCopyRoot = . 47 | SolutionGuid = {7C87E420-8361-4FC6-8334-E3F37ADF9615} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /TestApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Secs4Frmk4.Sml; 6 | 7 | namespace TestApp 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | var bytes = BitConverter.GetBytes(16711679); 14 | var byteValue = BitConverter.ToInt32(bytes, 0); 15 | 16 | byte[] value = new byte[] { 0xff, 0xff, 0xfe }; 17 | byte[] value1 = new byte[] { 0xfe, 0xff, 0xff }; 18 | Byte[] result = new byte[4]; 19 | byte[] result1 = new byte[4]; 20 | Array.Copy(value, result, value.Length); 21 | Array.Copy(value1, result1, value1.Length); 22 | 23 | int INT = BitConverter.ToInt32(result, 0); 24 | Array.Reverse(result1); 25 | int result2 = BitConverter.ToInt32(result1, 0); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /TestApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("TestApp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TestApp")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // 将 ComVisible 设置为 false 会使此程序集中的类型 18 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("ae3edfa9-398b-466b-a0fb-5ae9503f8d3b")] 24 | 25 | // 程序集的版本信息由下列四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | // 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 33 | // 方法是按如下所示使用“*”: : 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TestApp/TestApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AE3EDFA9-398B-466B-A0FB-5AE9503F8D3B} 8 | Exe 9 | TestApp 10 | TestApp 11 | v4.0 12 | 512 13 | 14 | 15 | AnyCPU 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | AnyCPU 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | {7e51f1ae-3104-4858-af49-6a81aabe7bcb} 49 | Secs4Frmk4 50 | 51 | 52 | 53 | --------------------------------------------------------------------------------