├── .gitattributes ├── .gitignore ├── Secs4Net ├── ConnectionState.cs ├── ExtensionHelper.cs ├── ITracer.cs ├── Item.cs ├── Lazy.cs ├── MessageType.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── RawData.cs ├── SecsCore.csproj ├── SecsException.cs ├── SecsFormat.cs ├── SecsGem.cs └── SecsMessage.cs ├── SecsDevice ├── Form1.Designer.cs ├── Form1.cs ├── Form1.resx ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── DataSources │ │ └── RecvMessage.datasource │ ├── Resources.Designer.cs │ └── Resources.resx ├── ReadMe.txt ├── SecsDevice.csproj └── app.config ├── SmlHelper ├── Helper.cs ├── Properties │ └── AssemblyInfo.cs ├── SmlHelper.csproj └── packages.config ├── WPFVisuallizer ├── App.xaml ├── App.xaml.cs ├── Properties │ ├── .svn │ │ └── text-base │ │ │ └── Resources.resx.svn-base │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── ReplyExpectedToStringConverter.cs ├── SecsMessageList.cs ├── SecsMessageTreeView.cs ├── Themes │ └── Generic.xaml ├── VeiwModelToTreeViewItemConverter.cs ├── ViewModel │ ├── SecsItemViewModel.cs │ ├── SecsMessageCollectionViewModel.cs │ ├── SecsMessageViewModel.cs │ └── TreeViewItemViewModel.cs ├── WPFVisuallizer.csproj ├── Window1.xaml ├── Window1.xaml.cs └── app.config └── secs4net.sln /.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 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # Roslyn cache directories 20 | *.ide/ 21 | 22 | # MSTest test Results 23 | [Tt]est[Rr]esult*/ 24 | [Bb]uild[Ll]og.* 25 | 26 | #NUNIT 27 | *.VisualState.xml 28 | TestResult.xml 29 | 30 | # Build Results of an ATL Project 31 | [Dd]ebugPS/ 32 | [Rr]eleasePS/ 33 | dlldata.c 34 | 35 | *_i.c 36 | *_p.c 37 | *_i.h 38 | *.ilk 39 | *.meta 40 | *.obj 41 | *.pch 42 | *.pdb 43 | *.pgc 44 | *.pgd 45 | *.rsp 46 | *.sbr 47 | *.tlb 48 | *.tli 49 | *.tlh 50 | *.tmp 51 | *.tmp_proj 52 | *.log 53 | *.vspscc 54 | *.vssscc 55 | .builds 56 | *.pidb 57 | *.svclog 58 | *.scc 59 | 60 | # Chutzpah Test files 61 | _Chutzpah* 62 | 63 | # Visual C++ cache files 64 | ipch/ 65 | *.aps 66 | *.ncb 67 | *.opensdf 68 | *.sdf 69 | *.cachefile 70 | 71 | # Visual Studio profiler 72 | *.psess 73 | *.vsp 74 | *.vspx 75 | 76 | # TFS 2012 Local Workspace 77 | $tf/ 78 | 79 | # Guidance Automation Toolkit 80 | *.gpState 81 | 82 | # ReSharper is a .NET coding add-in 83 | _ReSharper*/ 84 | *.[Rr]e[Ss]harper 85 | *.DotSettings.user 86 | 87 | # JustCode is a .NET coding addin-in 88 | .JustCode 89 | 90 | # TeamCity is a build add-in 91 | _TeamCity* 92 | 93 | # DotCover is a Code Coverage Tool 94 | *.dotCover 95 | 96 | # NCrunch 97 | _NCrunch_* 98 | .*crunch*.local.xml 99 | 100 | # MightyMoose 101 | *.mm.* 102 | AutoTest.Net/ 103 | 104 | # Web workbench (sass) 105 | .sass-cache/ 106 | 107 | # Installshield output folder 108 | [Ee]xpress/ 109 | 110 | # DocProject is a documentation generator add-in 111 | DocProject/buildhelp/ 112 | DocProject/Help/*.HxT 113 | DocProject/Help/*.HxC 114 | DocProject/Help/*.hhc 115 | DocProject/Help/*.hhk 116 | DocProject/Help/*.hhp 117 | DocProject/Help/Html2 118 | DocProject/Help/html 119 | 120 | # Click-Once directory 121 | publish/ 122 | 123 | # Publish Web Output 124 | *.[Pp]ublish.xml 125 | *.azurePubxml 126 | ## TODO: Comment the next line if you want to checkin your 127 | ## web deploy settings but do note that will include unencrypted 128 | ## passwords 129 | #*.pubxml 130 | 131 | # NuGet Packages Directory 132 | packages/* 133 | ## TODO: If the tool you use requires repositories.config 134 | ## uncomment the next line 135 | #!packages/repositories.config 136 | 137 | # Enable "build/" folder in the NuGet Packages folder since 138 | # NuGet packages use it for MSBuild targets. 139 | # This line needs to be after the ignore of the build folder 140 | # (and the packages folder if the line above has been uncommented) 141 | !packages/build/ 142 | 143 | # Windows Azure Build Output 144 | csx/ 145 | *.build.csdef 146 | 147 | # Windows Store app package directory 148 | AppPackages/ 149 | 150 | # Others 151 | sql/ 152 | *.Cache 153 | ClientBin/ 154 | [Ss]tyle[Cc]op.* 155 | ~$* 156 | *~ 157 | *.dbmdl 158 | *.dbproj.schemaview 159 | *.pfx 160 | *.publishsettings 161 | node_modules/ 162 | bower_components/ 163 | 164 | # RIA/Silverlight projects 165 | Generated_Code/ 166 | 167 | # Backup & report files from converting an old project file 168 | # to a newer Visual Studio version. Backup files are not needed, 169 | # because we have git ;-) 170 | _UpgradeReport_Files/ 171 | Backup*/ 172 | UpgradeLog*.XML 173 | UpgradeLog*.htm 174 | 175 | # SQL Server files 176 | *.mdf 177 | *.ldf 178 | 179 | # Business Intelligence projects 180 | *.rdl.data 181 | *.bim.layout 182 | *.bim_*.settings 183 | 184 | # Microsoft Fakes 185 | FakesAssemblies/ 186 | 187 | # LightSwitch generated files 188 | GeneratedArtifacts/ 189 | _Pvt_Extensions/ 190 | ModelManifest.xml -------------------------------------------------------------------------------- /Secs4Net/ConnectionState.cs: -------------------------------------------------------------------------------- 1 | namespace Secs4Net { 2 | public enum ConnectionState { 3 | Connecting, 4 | Connected, 5 | Selected, 6 | Retry 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Secs4Net/ExtensionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using static Secs4Net.Item; 5 | 6 | namespace Secs4Net { 7 | static class SecsExtension { 8 | #region Bytes To Item Value 9 | internal static Item BytesDecode(this SecsFormat format) { 10 | switch (format) { 11 | case SecsFormat.ASCII: return A(); 12 | case SecsFormat.JIS8: return J(); 13 | case SecsFormat.Boolean: return Boolean(); 14 | case SecsFormat.Binary: return B(); 15 | case SecsFormat.U1: return U1(); 16 | case SecsFormat.U2: return U2(); 17 | case SecsFormat.U4: return U4(); 18 | case SecsFormat.U8: return U8(); 19 | case SecsFormat.I1: return I1(); 20 | case SecsFormat.I2: return I2(); 21 | case SecsFormat.I4: return I4(); 22 | case SecsFormat.I8: return I8(); 23 | case SecsFormat.F4: return F4(); 24 | case SecsFormat.F8: return F8(); 25 | } 26 | throw new ArgumentException("Invalid format:" + format, nameof(format)); 27 | } 28 | 29 | internal static Item BytesDecode(this SecsFormat format, byte[] bytes, int index, int length) { 30 | switch (format) { 31 | case SecsFormat.ASCII: return A(Encoding.ASCII.GetString(bytes, index, length)); 32 | case SecsFormat.JIS8: return J(Item.JIS8Encoding.GetString(bytes, index, length)); 33 | case SecsFormat.Boolean: return Boolean(Decode(sizeof(bool), bytes, index, length)); 34 | case SecsFormat.Binary: return B(Decode(sizeof(byte), bytes, index, length)); 35 | case SecsFormat.U1: return U1(Decode(sizeof(byte), bytes, index, length)); 36 | case SecsFormat.U2: return U2(Decode(sizeof(ushort), bytes, index, length)); 37 | case SecsFormat.U4: return U4(Decode(sizeof(uint), bytes, index, length)); 38 | case SecsFormat.U8: return U8(Decode(sizeof(ulong), bytes, index, length)); 39 | case SecsFormat.I1: return I1(Decode(sizeof(sbyte), bytes, index, length)); 40 | case SecsFormat.I2: return I2(Decode(sizeof(short), bytes, index, length)); 41 | case SecsFormat.I4: return I4(Decode(sizeof(int), bytes, index, length)); 42 | case SecsFormat.I8: return I8(Decode(sizeof(long), bytes, index, length)); 43 | case SecsFormat.F4: return F4(Decode(sizeof(float), bytes, index, length)); 44 | case SecsFormat.F8: return F8(Decode(sizeof(double), bytes, index, length)); 45 | } 46 | throw new ArgumentException("Invalid format", nameof(format)); 47 | } 48 | 49 | static T[] Decode(int elmSize, byte[] bytes, int index, int length) where T : struct { 50 | bytes.Reverse(index, index + length, elmSize); 51 | var values = new T[length / elmSize]; 52 | Buffer.BlockCopy(bytes, index, values, 0, length); 53 | return values; 54 | } 55 | #endregion 56 | 57 | #region Value To SML 58 | internal static string ToHexString(this byte[] value) { 59 | if (value.Length == 0) return string.Empty; 60 | int length = value.Length * 3; 61 | char[] chs = new char[length]; 62 | for (int ci = 0, i = 0; ci < length; ci += 3) { 63 | byte num = value[i++]; 64 | chs[ci] = GetHexValue(num / 0x10); 65 | chs[ci + 1] = GetHexValue(num % 0x10); 66 | chs[ci + 2] = ' '; 67 | } 68 | return new string(chs, 0, length - 1); 69 | } 70 | 71 | static char GetHexValue(int i) => (i < 10) ? (char)(i + 0x30) : (char)((i - 10) + 0x41); 72 | 73 | internal static string ToSmlString(this T[] value) where T : struct => 74 | value.Length == 1 ? value[0].ToString() : string.Join(" ", value); 75 | #endregion 76 | 77 | internal static void Reverse(this byte[] bytes, int begin, int end, int offSet) { 78 | if (offSet > 1) 79 | for (int i = begin; i < end; i += offSet) 80 | Array.Reverse(bytes, i, offSet); 81 | } 82 | 83 | /// 84 | /// Encode Item header + value (initial array only) 85 | /// 86 | /// Item value bytes length 87 | /// return header bytes length 88 | /// header bytes + initial bytes of value 89 | internal static byte[] EncodeItem(this SecsFormat format, int valueCount, out int headerlength) { 90 | byte[] lengthBytes = BitConverter.GetBytes(valueCount); 91 | int dataLength = format == SecsFormat.List ? 0 : valueCount; 92 | 93 | if (valueCount <= 0xff) {// 1 byte 94 | headerlength = 2; 95 | var result = new byte[dataLength + 2]; 96 | result[0] = (byte)((byte)format | 1); 97 | result[1] = lengthBytes[0]; 98 | return result; 99 | } 100 | if (valueCount <= 0xffff) {// 2 byte 101 | headerlength = 3; 102 | var result = new byte[dataLength + 3]; 103 | result[0] = (byte)((byte)format | 2); 104 | result[1] = lengthBytes[1]; 105 | result[2] = lengthBytes[0]; 106 | return result; 107 | } 108 | if (valueCount <= 0xffffff) {// 3 byte 109 | headerlength = 4; 110 | var result = new byte[dataLength + 4]; 111 | result[0] = (byte)((byte)format | 3); 112 | result[1] = lengthBytes[2]; 113 | result[2] = lengthBytes[1]; 114 | result[3] = lengthBytes[0]; 115 | return result; 116 | } 117 | throw new ArgumentOutOfRangeException(nameof(valueCount), valueCount, $"Item data length({valueCount}) is overflow"); 118 | } 119 | 120 | /// 121 | /// Encode item to raw data buffer 122 | /// 123 | /// 124 | /// 125 | /// 126 | internal static uint Encode(this Item item, List buffer) { 127 | uint length = (uint)item.RawData.Count; 128 | buffer.Add(item.RawData); 129 | if (item.Format == SecsFormat.List) 130 | for (int i = 0,cnt = item.Items.Count; i < cnt; i++) 131 | length += item.Items[i].Encode(buffer); 132 | return length; 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /Secs4Net/ITracer.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | 4 | namespace Secs4Net { 5 | public class SecsTracer 6 | { 7 | public virtual void TraceMessageIn(SecsMessage msg, int systembyte) { } 8 | public virtual void TraceMessageOut(SecsMessage msg, int systembyte) { } 9 | public virtual void TraceInfo(string msg) { } 10 | public virtual void TraceWarning(string msg) { } 11 | public virtual void TraceError(string msg, Exception ex = null) { } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Secs4Net/Item.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Runtime.Remoting.Lifetime; 8 | using System.Security.Permissions; 9 | using System.Text; 10 | 11 | namespace Secs4Net 12 | { 13 | [DebuggerDisplay("<{Format} [{Count}] { (Format==SecsFormat.List) ? string.Empty : ToString() ,nq}>")] 14 | public sealed class Item : MarshalByRefObject { 15 | [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)] 16 | public override object InitializeLifetimeService() { 17 | var lease = (ILease)base.InitializeLifetimeService(); 18 | if (lease.CurrentState == LeaseState.Initial) { 19 | lease.InitialLeaseTime = TimeSpan.FromSeconds(10); 20 | lease.RenewOnCallTime = TimeSpan.FromSeconds(10); 21 | } 22 | return lease; 23 | } 24 | 25 | public SecsFormat Format { get; } 26 | public int Count { get; } 27 | 28 | public IReadOnlyList Items { get; } 29 | public object Value { get; }// 當Format不為List時 _value才有值,否則為null;不是string就是Array 30 | 31 | public T GetValue() { 32 | if (Value == null) 33 | throw new InvalidOperationException("Item format is List"); 34 | 35 | if (Value is T) 36 | return (T)((ICloneable)Value).Clone(); 37 | 38 | if (Value is T[]) 39 | return ((T[])Value)[0]; 40 | 41 | Type valueType = Nullable.GetUnderlyingType(typeof(T)); 42 | if (valueType != null && Value.GetType().GetElementType() == valueType) 43 | return ((IEnumerable)Value).Cast().FirstOrDefault(); 44 | 45 | throw new InvalidOperationException("Item value type is incompatible"); 46 | } 47 | 48 | internal RawData RawData => _rawBytes.Value; 49 | 50 | public override string ToString() => _sml.Value; 51 | 52 | /// 53 | /// if Format is List RawBytes is only header bytes. 54 | /// otherwise include header and data bytes. 55 | /// 56 | readonly Lazy _rawBytes; 57 | readonly Lazy _sml; 58 | 59 | #region Constructor 60 | /// 61 | /// List 62 | /// 63 | Item(IReadOnlyList items) { 64 | Format = SecsFormat.List; 65 | Count = items.Count; 66 | Items = items; 67 | _sml = EmptySml; 68 | _rawBytes = Lazy.Create(() => { 69 | int _; 70 | return new RawData(Format.EncodeItem(Count, out _)); 71 | }); 72 | } 73 | 74 | /// 75 | /// U2,U4,U8 76 | /// I1,I2,I4,I8 77 | /// F4,F8 78 | /// Boolean 79 | /// 80 | Item(SecsFormat format, Array value, Func sml) { 81 | Format = format; 82 | Count = value.Length; 83 | Value = value; 84 | _sml = Lazy.Create(sml); 85 | _rawBytes = Lazy.Create(() => { 86 | Array val = (Array)Value; 87 | int bytelength = Buffer.ByteLength(val); 88 | int headerLength; 89 | byte[] result = Format.EncodeItem(bytelength, out headerLength); 90 | Buffer.BlockCopy(val, 0, result, headerLength, bytelength); 91 | result.Reverse(headerLength, headerLength + bytelength, bytelength / val.Length); 92 | return new RawData(result); 93 | }); 94 | } 95 | 96 | /// 97 | /// A,J 98 | /// 99 | Item(SecsFormat format, string value, Encoding encoder) { 100 | Format = format; 101 | Count = value.Length; 102 | Value = value; 103 | _sml = Lazy.Create(value); 104 | _rawBytes = Lazy.Create(() => { 105 | string str = (string)Value; 106 | int headerLength; 107 | byte[] result = Format.EncodeItem(str.Length, out headerLength); 108 | encoder.GetBytes(str, 0, str.Length, result, headerLength); 109 | return new RawData(result); 110 | }); 111 | } 112 | 113 | /// 114 | /// Empty Item(none List) 115 | /// 116 | /// 117 | /// 118 | Item(SecsFormat format, ICloneable value) { 119 | Format = format; 120 | Value = value; 121 | _rawBytes = Lazy.Create(new RawData(new byte[] { (byte)((byte)Format | 1), 0 })); 122 | _sml = EmptySml; 123 | } 124 | #endregion 125 | 126 | #region Value Access Operator 127 | public static explicit operator string (Item item) => item.GetValue(); 128 | public static explicit operator byte (Item item) => item.GetValue(); 129 | public static explicit operator sbyte (Item item) => item.GetValue(); 130 | public static explicit operator ushort (Item item) => item.GetValue(); 131 | public static explicit operator short (Item item) => item.GetValue(); 132 | public static explicit operator uint (Item item) => item.GetValue(); 133 | public static explicit operator int (Item item) => item.GetValue(); 134 | public static explicit operator ulong (Item item) => item.GetValue(); 135 | public static explicit operator long (Item item) => item.GetValue(); 136 | public static explicit operator float (Item item) => item.GetValue(); 137 | public static explicit operator double (Item item) => item.GetValue(); 138 | public static explicit operator bool (Item item) => item.GetValue(); 139 | public static explicit operator byte? (Item item) => item.GetValue(); 140 | public static explicit operator sbyte? (Item item) => item.GetValue(); 141 | public static explicit operator ushort? (Item item) => item.GetValue(); 142 | public static explicit operator short? (Item item) => item.GetValue(); 143 | public static explicit operator uint? (Item item) => item.GetValue(); 144 | public static explicit operator int? (Item item) => item.GetValue(); 145 | public static explicit operator ulong? (Item item) => item.GetValue(); 146 | public static explicit operator long? (Item item) => item.GetValue(); 147 | public static explicit operator float? (Item item) => item.GetValue(); 148 | public static explicit operator double? (Item item) => item.GetValue(); 149 | public static explicit operator bool? (Item item) => item.GetValue(); 150 | public static explicit operator byte[] (Item item) => item.GetValue(); 151 | public static explicit operator sbyte[] (Item item) => item.GetValue(); 152 | public static explicit operator ushort[] (Item item) => item.GetValue(); 153 | public static explicit operator short[] (Item item) => item.GetValue(); 154 | public static explicit operator uint[] (Item item) => item.GetValue(); 155 | public static explicit operator int[] (Item item) => item.GetValue(); 156 | public static explicit operator ulong[] (Item item) => item.GetValue(); 157 | public static explicit operator long[] (Item item) => item.GetValue(); 158 | public static explicit operator float[] (Item item) => item.GetValue(); 159 | public static explicit operator double[] (Item item) => item.GetValue(); 160 | public static explicit operator bool[] (Item item) => item.GetValue(); 161 | #endregion 162 | 163 | #region Factory Methods 164 | internal static Item L(IList items) => new Item(new ReadOnlyCollection(items)); 165 | public static Item L(IEnumerable items) => items.Any() ? L(items.ToList()) : L(); 166 | public static Item L(params Item[] items) => L(items.ToList()); 167 | public static Item B(params byte[] value) => new Item(SecsFormat.Binary, value, value.ToHexString); 168 | public static Item U1(params byte[] value) => new Item(SecsFormat.U1, value, value.ToSmlString); 169 | public static Item U2(params ushort[] value) => new Item(SecsFormat.U2, value, value.ToSmlString); 170 | public static Item U4(params uint[] value) => new Item(SecsFormat.U4, value, value.ToSmlString); 171 | public static Item U8(params ulong[] value) => new Item(SecsFormat.U8, value, value.ToSmlString); 172 | public static Item I1(params sbyte[] value) => new Item(SecsFormat.I1, value, value.ToSmlString); 173 | public static Item I2(params short[] value) => new Item(SecsFormat.I2, value, value.ToSmlString); 174 | public static Item I4(params int[] value) => new Item(SecsFormat.I4, value, value.ToSmlString); 175 | public static Item I8(params long[] value) => new Item(SecsFormat.I8, value, value.ToSmlString); 176 | public static Item F4(params float[] value) => new Item(SecsFormat.F4, value, value.ToSmlString); 177 | public static Item F8(params double[] value) => new Item(SecsFormat.F8, value, value.ToSmlString); 178 | public static Item Boolean(params bool[] value) => new Item(SecsFormat.Boolean, value, value.ToSmlString); 179 | public static Item A(string value) => new Item(SecsFormat.ASCII, value, Encoding.ASCII); 180 | public static Item J(string value) => new Item(SecsFormat.JIS8, value, JIS8Encoding); 181 | #endregion 182 | 183 | #region Empty Item Factory 184 | public static Item L() => Empty_L; 185 | public static Item B() => Empty_Binary; 186 | public static Item U1() => Empty_U1; 187 | public static Item U2() => Empty_U2; 188 | public static Item U4() => Empty_U4; 189 | public static Item U8() => Empty_U8; 190 | public static Item I1() => Empty_I1; 191 | public static Item I2() => Empty_I2; 192 | public static Item I4() => Empty_I4; 193 | public static Item I8() => Empty_I8; 194 | public static Item F4() => Empty_F4; 195 | public static Item F8() => Empty_F8; 196 | public static Item Boolean() => Empty_Boolean; 197 | public static Item A() => Empty_A; 198 | public static Item J() => Empty_J; 199 | #endregion 200 | 201 | #region Share Object 202 | internal static readonly Encoding JIS8Encoding = Encoding.GetEncoding(50222); 203 | internal static readonly Lazy EmptySml = Lazy.Create(string.Empty); 204 | static readonly Item Empty_L = new Item(Array.AsReadOnly(new Item[0])); 205 | static readonly Item Empty_A = new Item(SecsFormat.ASCII, string.Empty); 206 | static readonly Item Empty_J = new Item(SecsFormat.JIS8, string.Empty); 207 | static readonly Item Empty_Boolean = new Item(SecsFormat.Boolean, new bool[0]); 208 | static readonly Item Empty_Binary = new Item(SecsFormat.Binary, new byte[0]); 209 | static readonly Item Empty_U1 = new Item(SecsFormat.U1, new byte[0]); 210 | static readonly Item Empty_U2 = new Item(SecsFormat.U2, new ushort[0]); 211 | static readonly Item Empty_U4 = new Item(SecsFormat.U4, new uint[0]); 212 | static readonly Item Empty_U8 = new Item(SecsFormat.U8, new ulong[0]); 213 | static readonly Item Empty_I1 = new Item(SecsFormat.I1, new sbyte[0]); 214 | static readonly Item Empty_I2 = new Item(SecsFormat.I2, new short[0]); 215 | static readonly Item Empty_I4 = new Item(SecsFormat.I4, new int[0]); 216 | static readonly Item Empty_I8 = new Item(SecsFormat.I8, new long[0]); 217 | static readonly Item Empty_F4 = new Item(SecsFormat.F4, new float[0]); 218 | static readonly Item Empty_F8 = new Item(SecsFormat.F8, new double[0]); 219 | 220 | #endregion 221 | } 222 | } -------------------------------------------------------------------------------- /Secs4Net/Lazy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace Secs4Net 5 | { 6 | sealed class Lazy where T : class 7 | { 8 | readonly Func creator; 9 | T _value; 10 | public Lazy(Func func) 11 | { 12 | creator = func; 13 | } 14 | public Lazy(T value) 15 | { 16 | _value = value; 17 | } 18 | public T Value 19 | { 20 | get 21 | { 22 | if (Volatile.Read(ref _value) != null) 23 | return _value; 24 | Interlocked.CompareExchange(ref _value, creator(), null); 25 | return _value; 26 | } 27 | } 28 | } 29 | 30 | static class Lazy 31 | { 32 | public static Lazy Create(Func creator) where T : class => new Lazy(creator); 33 | 34 | public static Lazy Create(T value) where T : class => new Lazy(value); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Secs4Net/MessageType.cs: -------------------------------------------------------------------------------- 1 | namespace Secs4Net { 2 | enum MessageType : byte { 3 | Data_Message = 0, /// 00000000 4 | Select_req = 1, /// 00000001 ReplyExpected 5 | Select_rsp = 2, /// 00000010 6 | //Deselect_req = 3, /// 00000011 ReplyExpected 7 | //Deselect_rsp = 4, /// 00000100 8 | Linktest_req = 5, /// 00000101 ReplyExpected 9 | Linktest_rsp = 6, /// 00000110 10 | //Reject_req = 7, /// 00000111 11 | Seperate_req = 9 /// 00001001 12 | } 13 | } -------------------------------------------------------------------------------- /Secs4Net/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using System; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("Secs4Net")] 10 | [assembly: AssemblyDescription("SECSII Driver On HSMS Implement,LittleEndian")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("mkjeff@outlook.com")] 13 | [assembly: AssemblyProduct("Secs4Net")] 14 | [assembly: AssemblyCopyright("Copyright © dogmouse 2015")] 15 | [assembly: AssemblyTrademark("Dogmouse")] 16 | [assembly: AssemblyCulture("")] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | [assembly: Guid("ca81166e-4920-45a2-932a-b94121d37085")] 25 | 26 | // Version information for an assembly consists of the following four values: 27 | // 28 | // Major Version 29 | // Minor Version 30 | // Build Number 31 | // Revision 32 | // 33 | // You can specify all the values or you can default the Build and Revision Numbers 34 | // by using the '*' as shown below: 35 | // [assembly: AssemblyVersion("1.0.*")] 36 | [assembly: AssemblyVersion("3.0.0.0")] 37 | [assembly: AssemblyFileVersion("3.0.0.0")] 38 | -------------------------------------------------------------------------------- /Secs4Net/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Secs4Net.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.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 | /// Returns the cached ResourceManager instance used by this class. 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("Secs4Net.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 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 | /// Looks up a localized string similar to Unrecognized Device Id. 65 | /// 66 | internal static string S9F1 { 67 | get { 68 | return ResourceManager.GetString("S9F1", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized string similar to Data Too Long. 74 | /// 75 | internal static string S9F11 { 76 | get { 77 | return ResourceManager.GetString("S9F11", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// Looks up a localized string similar to Conversation Timeout. 83 | /// 84 | internal static string S9F13 { 85 | get { 86 | return ResourceManager.GetString("S9F13", resourceCulture); 87 | } 88 | } 89 | 90 | /// 91 | /// Looks up a localized string similar to Unrecognized Stream Type. 92 | /// 93 | internal static string S9F3 { 94 | get { 95 | return ResourceManager.GetString("S9F3", resourceCulture); 96 | } 97 | } 98 | 99 | /// 100 | /// Looks up a localized string similar to Unrecognized Function Type. 101 | /// 102 | internal static string S9F5 { 103 | get { 104 | return ResourceManager.GetString("S9F5", resourceCulture); 105 | } 106 | } 107 | 108 | /// 109 | /// Looks up a localized string similar to Illegal Data. 110 | /// 111 | internal static string S9F7 { 112 | get { 113 | return ResourceManager.GetString("S9F7", resourceCulture); 114 | } 115 | } 116 | 117 | /// 118 | /// Looks up a localized string similar to Transaction Timer Timeout. 119 | /// 120 | internal static string S9F9 { 121 | get { 122 | return ResourceManager.GetString("S9F9", resourceCulture); 123 | } 124 | } 125 | 126 | /// 127 | /// Looks up a localized string similar to S9Fy message reply.. 128 | /// 129 | internal static string S9Fy { 130 | get { 131 | return ResourceManager.GetString("S9Fy", resourceCulture); 132 | } 133 | } 134 | 135 | /// 136 | /// Looks up a localized string similar to Equipment is not online mode. 137 | /// 138 | internal static string SxF0 { 139 | get { 140 | return ResourceManager.GetString("SxF0", resourceCulture); 141 | } 142 | } 143 | 144 | /// 145 | /// Looks up a localized string similar to T3 Timeout!. 146 | /// 147 | internal static string T3Timeout { 148 | get { 149 | return ResourceManager.GetString("T3Timeout", resourceCulture); 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /Secs4Net/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=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | T3 Timeout! 122 | T3Timeout description 123 | 124 | 125 | Equipment is not online mode 126 | EquipmentIsNotOnlineMode description 127 | 128 | 129 | Unrecognized Device Id 130 | S9F1 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 | Data Too Long 150 | S9F11 description 151 | 152 | 153 | Conversation Timeout 154 | S9F13 description 155 | 156 | 157 | S9Fy message reply. 158 | S9Fy description 159 | 160 | -------------------------------------------------------------------------------- /Secs4Net/RawData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | 4 | namespace Secs4Net { 5 | [Serializable] 6 | public sealed class RawData : ReadOnlyCollection { 7 | internal RawData(byte[] bytes) : base(bytes) { } 8 | internal byte[] Bytes => (byte[])Items; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Secs4Net/SecsCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {3C8C67B8-3746-4D26-B3A8-DCB01B5AA66B} 9 | Library 10 | Properties 11 | Secs4Net 12 | Secs4Net 13 | v4.6 14 | 512 15 | false 16 | 17 | 18 | 19 | 20 | Svn 21 | Svn 22 | Svn 23 | SubversionScc 24 | 0 25 | 26 | 27 | 28 | 29 | 3.5 30 | publish\ 31 | true 32 | Disk 33 | false 34 | Foreground 35 | 7 36 | Days 37 | false 38 | false 39 | true 40 | 0 41 | 1.0.0.%2a 42 | false 43 | false 44 | true 45 | 46 | 47 | 48 | true 49 | pdbonly 50 | true 51 | bin\Debug\ 52 | 53 | 54 | prompt 55 | 4 56 | 57 | 58 | false 59 | Auto 60 | false 61 | false 62 | False 63 | False 64 | True 65 | False 66 | False 67 | True 68 | True 69 | True 70 | True 71 | True 72 | True 73 | True 74 | False 75 | False 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | ..\..\baseline.xml 87 | Full 88 | %28none%29 89 | MinimumRecommendedRules.ruleset 90 | false 91 | false 92 | 93 | 94 | none 95 | true 96 | bin\Release\ 97 | 98 | 99 | prompt 100 | 4 101 | false 102 | true 103 | 104 | 105 | true 106 | Migrated rules for SecsCore (2).ruleset 107 | false 108 | 109 | 110 | true 111 | bin\x86\Debug\ 112 | true 113 | pdbonly 114 | x86 115 | bin\Debug\Secs4Net.dll.CodeAnalysisLog.xml 116 | true 117 | GlobalSuppressions.cs 118 | false 119 | prompt 120 | MinimumRecommendedRules.ruleset 121 | ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets 122 | ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules 123 | false 124 | 125 | 126 | bin\x86\Release\ 127 | CODE_ANALYSIS; 128 | true 129 | x86 130 | true 131 | bin\Release\Secs4Net.dll.CodeAnalysisLog.xml 132 | true 133 | GlobalSuppressions.cs 134 | prompt 135 | Migrated rules for SecsCore (2).ruleset 136 | ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets 137 | ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules 138 | false 139 | 140 | 141 | 142 | 143 | False 144 | False 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | True 156 | True 157 | Resources.resx 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | False 169 | .NET Framework 3.5 SP1 Client Profile 170 | false 171 | 172 | 173 | False 174 | .NET Framework 3.5 SP1 175 | true 176 | 177 | 178 | False 179 | Windows Installer 3.1 180 | true 181 | 182 | 183 | 184 | 185 | ResXFileCodeGenerator 186 | Resources.Designer.cs 187 | 188 | 189 | 190 | 197 | -------------------------------------------------------------------------------- /Secs4Net/SecsException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using System.Security.Permissions; 4 | namespace Secs4Net { 5 | 6 | [Serializable] 7 | public class SecsException : Exception { 8 | public SecsMessage SecsMsg { get; } 9 | 10 | protected SecsException(SerializationInfo info, StreamingContext context) 11 | : base(info, context) { 12 | SecsMsg = info.GetValue("msg", typeof(SecsMessage)) as SecsMessage; 13 | } 14 | 15 | public SecsException(SecsMessage msg, string description) 16 | : base(description) { 17 | SecsMsg = msg; 18 | } 19 | 20 | public SecsException(string msg) 21 | : this(null, msg) { } 22 | 23 | [SecurityPermission(SecurityAction.LinkDemand,Flags= SecurityPermissionFlag.SerializationFormatter)] 24 | public override void GetObjectData(SerializationInfo info, StreamingContext context) { 25 | base.GetObjectData(info, context); 26 | info.AddValue("msg", SecsMsg); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Secs4Net/SecsFormat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace Secs4Net { 3 | public enum SecsFormat : byte { 4 | List = 0, // 000000 00 5 | Binary = 0x20, // 001000 00 6 | Boolean = 0x24, // 001001 00 7 | ASCII = 0x40, // 010000 00 8 | JIS8 = 0x44, // 010001 00 9 | I8 = 0x60, // 011000 00 10 | I1 = 0x64, // 011001 00 11 | I2 = 0x68, // 011010 00 12 | I4 = 0x70, // 011100 00 13 | F8 = 0x80, // 100000 00 14 | F4 = 0x90, // 100100 00 15 | U8 = 0xA0, // 101000 00 16 | U1 = 0xA4, // 101001 00 17 | U2 = 0xA8, // 101010 00 18 | U4 = 0xB0, // 101100 00 19 | } 20 | } -------------------------------------------------------------------------------- /Secs4Net/SecsGem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Threading; 9 | using Secs4Net.Properties; 10 | using System.Threading.Tasks; 11 | 12 | namespace Secs4Net 13 | { 14 | public sealed class SecsGem : IDisposable 15 | { 16 | public event EventHandler ConnectionChanged; 17 | public ConnectionState State { get; private set; } 18 | public short DeviceId { get; set; } = 0; 19 | public int LinkTestInterval { get; set; } = 60000; 20 | public int T3 { get; set; } = 45000; 21 | public int T5 { get; set; } = 10000; 22 | public int T6 { get; set; } = 5000; 23 | public int T7 { get; set; } = 10000; 24 | public int T8 { get; set; } = 5000; 25 | 26 | public bool LinkTestEnable 27 | { 28 | get { return _timerLinkTest.Enabled; } 29 | set 30 | { 31 | _timerLinkTest.Interval = LinkTestInterval; 32 | _timerLinkTest.Enabled = value; 33 | } 34 | } 35 | 36 | readonly bool _isActive; 37 | readonly IPAddress _ip; 38 | readonly int _port; 39 | Socket _socket; 40 | 41 | readonly SecsDecoder _secsDecoder; 42 | readonly ConcurrentDictionary _replyExpectedMsgs = new ConcurrentDictionary(); 43 | readonly Action> PrimaryMessageHandler; 44 | readonly SecsTracer _tracer; 45 | readonly System.Timers.Timer _timer7 = new System.Timers.Timer(); // between socket connected and received Select.req timer 46 | readonly System.Timers.Timer _timer8 = new System.Timers.Timer(); 47 | readonly System.Timers.Timer _timerLinkTest = new System.Timers.Timer(); 48 | 49 | readonly Func StartImpl; 50 | readonly Action StopImpl; 51 | 52 | readonly byte[] _recvBuffer; 53 | static readonly SecsMessage ControlMessage = new SecsMessage(0, 0, string.Empty); 54 | static readonly ArraySegment ControlMessageLengthBytes = new ArraySegment(new byte[] { 0, 0, 0, 10 }); 55 | static readonly SecsTracer DefaultTracer = new SecsTracer(); 56 | readonly Func NewSystemByte; 57 | 58 | public SecsGem(IPAddress ip, int port, bool isActive, SecsTracer tracer = null, Action> primaryMsgHandler = null, int receiveBufferSize = 0x4000) 59 | { 60 | if (ip == null) 61 | throw new ArgumentNullException(nameof(ip)); 62 | 63 | _ip = ip; 64 | _port = port; 65 | _isActive = isActive; 66 | _recvBuffer = new byte[receiveBufferSize < 0x4000 ? 0x4000 : receiveBufferSize]; 67 | _secsDecoder = new SecsDecoder(HandleControlMessage, HandleDataMessage); 68 | _tracer = tracer ?? DefaultTracer; 69 | PrimaryMessageHandler = primaryMsgHandler ?? ((primary, reply) => reply(null)); 70 | 71 | int systemByte = new Random(Guid.NewGuid().GetHashCode()).Next(); 72 | NewSystemByte = () => Interlocked.Increment(ref systemByte); 73 | 74 | #region Timer Action 75 | _timer7.Elapsed += delegate 76 | { 77 | _tracer.TraceError("T7 Timeout"); 78 | CommunicationStateChanging(ConnectionState.Retry); 79 | }; 80 | 81 | _timer8.Elapsed += delegate 82 | { 83 | _tracer.TraceError("T8 Timeout"); 84 | CommunicationStateChanging(ConnectionState.Retry); 85 | }; 86 | 87 | _timerLinkTest.Elapsed += delegate 88 | { 89 | if (State == ConnectionState.Selected) 90 | SendControlMessage(MessageType.Linktest_req, NewSystemByte()); 91 | }; 92 | #endregion 93 | if (_isActive) 94 | { 95 | #region Active Impl 96 | var timer5 = new System.Timers.Timer(); 97 | timer5.Elapsed += delegate 98 | { 99 | timer5.Enabled = false; 100 | _tracer.TraceError("T5 Timeout"); 101 | CommunicationStateChanging(ConnectionState.Retry); 102 | }; 103 | 104 | StartImpl = async delegate 105 | { 106 | CommunicationStateChanging(ConnectionState.Connecting); 107 | try 108 | { 109 | var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 110 | await Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, _ip, _port, null); 111 | CommunicationStateChanging(ConnectionState.Connected); 112 | _socket = socket; 113 | SendControlMessage(MessageType.Select_req, NewSystemByte()); 114 | _socket.BeginReceive(_recvBuffer, 0, _recvBuffer.Length, SocketFlags.None, ReceiveComplete, null); 115 | } 116 | catch (Exception ex) 117 | { 118 | if (_isDisposed) return; 119 | _tracer.TraceError(ex.Message); 120 | _tracer.TraceInfo("Start T5 Timer"); 121 | timer5.Interval = T5; 122 | timer5.Enabled = true; 123 | } 124 | }; 125 | 126 | StopImpl = delegate 127 | { 128 | timer5.Stop(); 129 | if (_isDisposed) timer5.Dispose(); 130 | }; 131 | #endregion 132 | } 133 | else 134 | { 135 | #region Passive Impl 136 | var server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 137 | server.Bind(new IPEndPoint(_ip, _port)); 138 | server.Listen(0); 139 | 140 | StartImpl = async delegate 141 | { 142 | CommunicationStateChanging(ConnectionState.Connecting); 143 | try 144 | { 145 | _socket = await Task.Factory.FromAsync(server.BeginAccept, server.EndAccept, null); 146 | CommunicationStateChanging(ConnectionState.Connected); 147 | _socket.BeginReceive(_recvBuffer, 0, _recvBuffer.Length, SocketFlags.None, ReceiveComplete, null); 148 | } 149 | catch (Exception ex) 150 | { 151 | _tracer.TraceError("System Exception", ex); 152 | CommunicationStateChanging(ConnectionState.Retry); 153 | } 154 | }; 155 | 156 | StopImpl = delegate 157 | { 158 | if (_isDisposed) 159 | server?.Close(); 160 | }; 161 | #endregion 162 | } 163 | } 164 | 165 | #region Socket Receive Process 166 | void ReceiveComplete(IAsyncResult iar) 167 | { 168 | try 169 | { 170 | int count = _socket.EndReceive(iar); 171 | 172 | _timer8.Enabled = false; 173 | 174 | if (count == 0) 175 | { 176 | _tracer.TraceError("Received 0 byte data. Close the socket."); 177 | CommunicationStateChanging(ConnectionState.Retry); 178 | return; 179 | } 180 | 181 | if (_secsDecoder.Decode(_recvBuffer, 0, count)) 182 | { 183 | _tracer.TraceInfo("Start T8 Timer"); 184 | _timer8.Interval = T8; 185 | _timer8.Enabled = true; 186 | } 187 | 188 | _socket.BeginReceive(_recvBuffer, 0, _recvBuffer.Length, SocketFlags.None, ReceiveComplete, null); 189 | } 190 | catch (NullReferenceException ex) 191 | { 192 | _tracer.TraceWarning("unexpected NullReferenceException:" + ex.ToString()); 193 | } 194 | catch (SocketException ex) 195 | { 196 | _tracer.TraceError($"RecieveComplete socket error:{ex.Message + ex}, ErrorCode:{ex.SocketErrorCode}", ex); 197 | CommunicationStateChanging(ConnectionState.Retry); 198 | } 199 | catch (Exception ex) 200 | { 201 | _tracer.TraceError("unexpected exception", ex); 202 | CommunicationStateChanging(ConnectionState.Retry); 203 | } 204 | } 205 | 206 | void HandleControlMessage(Header header) 207 | { 208 | int systembyte = header.SystemBytes; 209 | if ((byte)header.MessageType % 2 == 0) 210 | { 211 | SecsAsyncResult ar = null; 212 | if (_replyExpectedMsgs.TryGetValue(systembyte, out ar)) 213 | { 214 | ar.EndProcess(ControlMessage, false); 215 | } 216 | else 217 | { 218 | _tracer.TraceWarning("Received Unexpected Control Message: " + header.MessageType); 219 | return; 220 | } 221 | } 222 | _tracer.TraceInfo("Receive Control message: " + header.MessageType); 223 | switch (header.MessageType) 224 | { 225 | case MessageType.Select_req: 226 | SendControlMessage(MessageType.Select_rsp, systembyte); 227 | CommunicationStateChanging(ConnectionState.Selected); 228 | break; 229 | case MessageType.Select_rsp: 230 | switch (header.F) 231 | { 232 | case 0: 233 | CommunicationStateChanging(ConnectionState.Selected); 234 | break; 235 | case 1: 236 | _tracer.TraceError("Communication Already Active."); 237 | break; 238 | case 2: 239 | _tracer.TraceError("Connection Not Ready."); 240 | break; 241 | case 3: 242 | _tracer.TraceError("Connection Exhaust."); 243 | break; 244 | default: 245 | _tracer.TraceError("Connection Status Is Unknown."); 246 | break; 247 | } 248 | break; 249 | case MessageType.Linktest_req: 250 | SendControlMessage(MessageType.Linktest_rsp, systembyte); 251 | break; 252 | case MessageType.Seperate_req: 253 | CommunicationStateChanging(ConnectionState.Retry); 254 | break; 255 | } 256 | } 257 | 258 | void HandleDataMessage(Header header, SecsMessage msg) 259 | { 260 | int systembyte = header.SystemBytes; 261 | 262 | if (header.DeviceId != DeviceId && msg.S != 9 && msg.F != 1) 263 | { 264 | _tracer.TraceMessageIn(msg, systembyte); 265 | _tracer.TraceWarning("Received Unrecognized Device Id Message"); 266 | try 267 | { 268 | SendDataMessage(new SecsMessage(9, 1, false, "Unrecognized Device Id", Item.B(header.Bytes)), NewSystemByte()); 269 | } 270 | catch (Exception ex) 271 | { 272 | _tracer.TraceError("Send S9F1 Error", ex); 273 | } 274 | return; 275 | } 276 | 277 | if (msg.F % 2 != 0) 278 | { 279 | if (msg.S != 9) 280 | { 281 | //Primary message 282 | _tracer.TraceMessageIn(msg, systembyte); 283 | PrimaryMessageHandler(msg, secondary => 284 | { 285 | if (!header.ReplyExpected || State != ConnectionState.Selected) 286 | return; 287 | 288 | secondary = secondary ?? new SecsMessage(9, 7, false, "Unknown Message", Item.B(header.Bytes)); 289 | secondary.ReplyExpected = false; 290 | try 291 | { 292 | SendDataMessage(secondary, secondary.S == 9 ? NewSystemByte() : header.SystemBytes); 293 | } 294 | catch (Exception ex) 295 | { 296 | _tracer.TraceError("Reply Secondary Message Error", ex); 297 | } 298 | }); 299 | return; 300 | } 301 | // Error message 302 | var headerBytes = (byte[])msg.SecsItem; 303 | systembyte = BitConverter.ToInt32(new byte[] { headerBytes[9], headerBytes[8], headerBytes[7], headerBytes[6] }, 0); 304 | } 305 | 306 | // Secondary message 307 | SecsAsyncResult ar = null; 308 | if (_replyExpectedMsgs.TryGetValue(systembyte, out ar)) 309 | ar.EndProcess(msg, false); 310 | _tracer.TraceMessageIn(msg, systembyte); 311 | } 312 | #endregion 313 | #region Socket Send Process 314 | void SendControlMessage(MessageType msgType, int systembyte) 315 | { 316 | if (_socket == null || !_socket.Connected) 317 | return; 318 | 319 | if ((byte)msgType % 2 == 1 && msgType != MessageType.Seperate_req) 320 | { 321 | var ar = new SecsAsyncResult(ControlMessage); 322 | _replyExpectedMsgs[systembyte] = ar; 323 | 324 | ThreadPool.RegisterWaitForSingleObject(ar.AsyncWaitHandle, 325 | (state, timeout) => 326 | { 327 | SecsAsyncResult ars; 328 | if (_replyExpectedMsgs.TryRemove((int)state, out ars) && timeout) 329 | { 330 | _tracer.TraceError("T6 Timeout"); 331 | CommunicationStateChanging(ConnectionState.Retry); 332 | } 333 | }, systembyte, T6, true); 334 | } 335 | 336 | var header = new Header(new byte[10]) 337 | { 338 | MessageType = msgType, 339 | SystemBytes = systembyte 340 | }; 341 | header.Bytes[0] = 0xFF; 342 | header.Bytes[1] = 0xFF; 343 | _socket.Send(new List>(2){ 344 | ControlMessageLengthBytes, 345 | new ArraySegment(header.Bytes) 346 | }); 347 | _tracer.TraceInfo("Sent Control Message: " + header.MessageType); 348 | } 349 | 350 | SecsAsyncResult SendDataMessage(SecsMessage msg, int systembyte, AsyncCallback callback=null, object syncState=null) 351 | { 352 | if (State != ConnectionState.Selected) 353 | throw new SecsException("Device is not selected"); 354 | 355 | var header = new Header(new byte[10]) 356 | { 357 | S = msg.S, 358 | F = msg.F, 359 | ReplyExpected = msg.ReplyExpected, 360 | DeviceId = DeviceId, 361 | SystemBytes = systembyte 362 | }; 363 | var buffer = new EncodedBuffer(header.Bytes, msg.RawDatas); 364 | 365 | SecsAsyncResult ar = null; 366 | if (msg.ReplyExpected) 367 | { 368 | ar = new SecsAsyncResult(msg, callback, syncState); 369 | _replyExpectedMsgs[systembyte] = ar; 370 | 371 | ThreadPool.RegisterWaitForSingleObject(ar.AsyncWaitHandle, 372 | (state, timeout) => 373 | { 374 | SecsAsyncResult ars; 375 | if (_replyExpectedMsgs.TryRemove((int)state, out ars) && timeout) 376 | { 377 | _tracer.TraceError($"T3 Timeout[id=0x{state:X8}]"); 378 | ars.EndProcess(null, timeout); 379 | } 380 | }, systembyte, T3, true); 381 | } 382 | 383 | SocketError error; 384 | _socket.Send(buffer, SocketFlags.None, out error); 385 | if (error != SocketError.Success) 386 | { 387 | var errorMsg = "Socket send error :" + new SocketException((int)error).Message; 388 | _tracer.TraceError(errorMsg); 389 | CommunicationStateChanging(ConnectionState.Retry); 390 | throw new SecsException(errorMsg); 391 | } 392 | 393 | _tracer.TraceMessageOut(msg, systembyte); 394 | return ar; 395 | } 396 | #endregion 397 | #region Internal State Transition 398 | void CommunicationStateChanging(ConnectionState newState) 399 | { 400 | State = newState; 401 | ConnectionChanged?.Invoke(this, EventArgs.Empty); 402 | 403 | switch (State) 404 | { 405 | case ConnectionState.Selected: 406 | _timer7.Enabled = false; 407 | _tracer.TraceInfo("Stop T7 Timer"); 408 | break; 409 | case ConnectionState.Connected: 410 | _tracer.TraceInfo("Start T7 Timer"); 411 | _timer7.Interval = T7; 412 | _timer7.Enabled = true; 413 | break; 414 | case ConnectionState.Retry: 415 | if (_isDisposed) 416 | return; 417 | Reset(); 418 | Thread.Sleep(2000); 419 | StartImpl().Start(); 420 | break; 421 | } 422 | } 423 | 424 | void Reset() 425 | { 426 | _timer7.Stop(); 427 | _timer8.Stop(); 428 | _timerLinkTest.Stop(); 429 | _secsDecoder.Reset(); 430 | if (_socket != null) 431 | { 432 | _socket.Shutdown(SocketShutdown.Both); 433 | _socket.Close(); 434 | _socket = null; 435 | } 436 | _replyExpectedMsgs.Clear(); 437 | StopImpl(); 438 | } 439 | #endregion 440 | #region Public API 441 | public async Task Start() => await StartImpl(); 442 | 443 | /// 444 | /// Send SECS message to device. 445 | /// 446 | /// 447 | /// Device's reply msg if msg.ReplyExpected is true;otherwise, null. 448 | public SecsMessage Send(SecsMessage msg) => EndSend(BeginSend(msg)); 449 | 450 | /// 451 | /// Send SECS message asynchronously to device . 452 | /// 453 | /// 454 | /// 455 | public async Task SendAsync(SecsMessage msg) => await Task.Factory.FromAsync(BeginSend, EndSend, msg, null, TaskCreationOptions.PreferFairness); 456 | 457 | /// 458 | /// Send SECS message asynchronously to device . 459 | /// 460 | /// 461 | /// Device's reply message handler callback. 462 | /// synchronize state object 463 | /// An IAsyncResult that references the asynchronous send if msg.ReplyExpected is true;otherwise, null. 464 | public IAsyncResult BeginSend(SecsMessage msg, AsyncCallback callback = null, object state = null) => SendDataMessage(msg, NewSystemByte(), callback, state); 465 | 466 | /// 467 | /// Ends a asynchronous send. 468 | /// 469 | /// An IAsyncResult that references the asynchronous send 470 | /// Device's reply message if is an IAsyncResult that references the asynchronous send, otherwise null. 471 | public SecsMessage EndSend(IAsyncResult asyncResult) 472 | { 473 | if (asyncResult == null) 474 | throw new ArgumentNullException(nameof(asyncResult)); 475 | var ar = asyncResult as SecsAsyncResult; 476 | if (ar == null) 477 | throw new ArgumentException($"argument {nameof(asyncResult)} was not created by a call to {nameof(BeginSend)}", nameof(asyncResult)); 478 | ar.AsyncWaitHandle.WaitOne(); 479 | return ar.Secondary; 480 | } 481 | 482 | volatile bool _isDisposed; 483 | public void Dispose() 484 | { 485 | if (!_isDisposed) 486 | { 487 | _isDisposed = true; 488 | ConnectionChanged = null; 489 | if (State == ConnectionState.Selected) 490 | SendControlMessage(MessageType.Seperate_req, NewSystemByte()); 491 | Reset(); 492 | _timer7.Dispose(); 493 | _timer8.Dispose(); 494 | _timerLinkTest.Dispose(); 495 | } 496 | } 497 | 498 | public string DeviceAddress => _isActive 499 | ? _ip.ToString() 500 | // : _socket == null 501 | //? "N/A" 502 | : ((IPEndPoint)_socket?.RemoteEndPoint).Address?.ToString()??"NA"; 503 | #endregion 504 | #region Async Impl 505 | sealed class SecsAsyncResult : IAsyncResult 506 | { 507 | readonly ManualResetEvent _ev = new ManualResetEvent(false); 508 | readonly SecsMessage Primary; 509 | readonly AsyncCallback _callback; 510 | 511 | SecsMessage _secondary; 512 | bool _timeout; 513 | 514 | internal SecsAsyncResult(SecsMessage primaryMsg, AsyncCallback callback = null, object state = null) 515 | { 516 | Primary = primaryMsg; 517 | AsyncState = state; 518 | _callback = callback; 519 | } 520 | 521 | internal void EndProcess(SecsMessage replyMsg, bool timeout) 522 | { 523 | if (replyMsg != null) 524 | { 525 | _secondary = replyMsg; 526 | _secondary.Name = Primary.Name; 527 | } 528 | _timeout = timeout; 529 | IsCompleted = !timeout; 530 | _ev.Set(); 531 | _callback?.Invoke(this); 532 | } 533 | 534 | internal SecsMessage Secondary 535 | { 536 | get 537 | { 538 | if (_timeout) throw new SecsException(Primary, Resources.T3Timeout); 539 | if (_secondary == null) return null; 540 | if (_secondary.F == 0) throw new SecsException(Primary, Resources.SxF0); 541 | if (_secondary.S == 9) 542 | { 543 | switch (_secondary.F) 544 | { 545 | case 1: throw new SecsException(Primary, Resources.S9F1); 546 | case 3: throw new SecsException(Primary, Resources.S9F3); 547 | case 5: throw new SecsException(Primary, Resources.S9F5); 548 | case 7: throw new SecsException(Primary, Resources.S9F7); 549 | case 9: throw new SecsException(Primary, Resources.S9F9); 550 | case 11: throw new SecsException(Primary, Resources.S9F11); 551 | case 13: throw new SecsException(Primary, Resources.S9F13); 552 | default: throw new SecsException(Primary, Resources.S9Fy); 553 | } 554 | } 555 | return _secondary; 556 | } 557 | } 558 | 559 | #region IAsyncResult Members 560 | 561 | public object AsyncState { get; } 562 | 563 | public WaitHandle AsyncWaitHandle => _ev; 564 | 565 | public bool CompletedSynchronously => false; 566 | 567 | public bool IsCompleted { get; private set; } 568 | 569 | #endregion 570 | } 571 | #endregion 572 | #region SECS Decoder 573 | sealed class SecsDecoder 574 | { 575 | /// 576 | /// 577 | /// 578 | /// 579 | /// 580 | /// 581 | /// 582 | /// pipeline decoder index 583 | delegate int Decoder(byte[] data, int length, ref int index, out int need); 584 | #region share 585 | uint _messageLength;// total byte length 586 | Header _msgHeader; // message header 587 | readonly Stack> _stack = new Stack>(); // List Item stack 588 | SecsFormat _format; 589 | byte _lengthBits; 590 | int _itemLength; 591 | #endregion 592 | 593 | /// 594 | /// decode pipeline 595 | /// 596 | readonly Decoder[] decoders; 597 | readonly Action DataMsgHandler; 598 | readonly Action
ControlMsgHandler; 599 | 600 | internal SecsDecoder(Action
controlMsgHandler, Action msgHandler) 601 | { 602 | DataMsgHandler = msgHandler; 603 | ControlMsgHandler = controlMsgHandler; 604 | 605 | decoders = new Decoder[]{ 606 | #region decoders[0]: get total message length 4 bytes 607 | (byte[] data, int length, ref int index, out int need) => 608 | { 609 | if (!CheckAvailable(length, index, 4, out need)) return 0; 610 | 611 | Array.Reverse(data, index, 4); 612 | _messageLength = BitConverter.ToUInt32(data, index); 613 | Trace.WriteLine("Get Message Length =" + _messageLength); 614 | index += 4; 615 | 616 | return 1; 617 | }, 618 | #endregion 619 | #region decoders[1]: get message header 10 bytes 620 | (byte[] data, int length, ref int index, out int need) => 621 | { 622 | if (!CheckAvailable(length, index, 10, out need)) return 1; 623 | 624 | _msgHeader = new Header(new byte[10]); 625 | Array.Copy(data, index, _msgHeader.Bytes, 0, 10); 626 | index += 10; 627 | _messageLength -= 10; 628 | if (_messageLength == 0) 629 | { 630 | if (_msgHeader.MessageType == MessageType.Data_Message) 631 | { 632 | ProcessMessage(new SecsMessage(_msgHeader.S, _msgHeader.F, _msgHeader.ReplyExpected, string.Empty)); 633 | } 634 | else 635 | { 636 | ControlMsgHandler(_msgHeader); 637 | _messageLength = 0; 638 | } 639 | return 0; 640 | } 641 | else if (length - index >= _messageLength) 642 | { 643 | ProcessMessage(new SecsMessage(_msgHeader.S, _msgHeader.F, _msgHeader.ReplyExpected, data, ref index)); 644 | return 0; //completeWith message received 645 | } 646 | return 2; 647 | }, 648 | #endregion 649 | #region decoders[2]: get _format + lengthBits(2bit) 1 byte 650 | (byte[] data, int length, ref int index, out int need) => 651 | { 652 | if (!CheckAvailable(length, index, 1, out need)) return 2; 653 | 654 | _format = (SecsFormat)(data[index] & 0xFC); 655 | _lengthBits = (byte)(data[index] & 3); 656 | index++; 657 | _messageLength--; 658 | return 3; 659 | }, 660 | #endregion 661 | #region decoders[3]: get _itemLength _lengthBits bytes 662 | (byte[] data, int length, ref int index, out int need) => 663 | { 664 | if (!CheckAvailable(length, index, _lengthBits, out need)) return 3; 665 | 666 | byte[] itemLengthBytes = new byte[4]; 667 | Array.Copy(data, index, itemLengthBytes, 0, _lengthBits); 668 | Array.Reverse(itemLengthBytes, 0, _lengthBits); 669 | 670 | _itemLength = BitConverter.ToInt32(itemLengthBytes, 0); 671 | Array.Clear(itemLengthBytes, 0, 4); 672 | 673 | index += _lengthBits; 674 | _messageLength -= _lengthBits; 675 | return 4; 676 | }, 677 | #endregion 678 | #region decoders[4]: get item value 679 | (byte[] data, int length, ref int index, out int need) => 680 | { 681 | need = 0; 682 | Item item = null; 683 | if (_format == SecsFormat.List) 684 | { 685 | if (_itemLength == 0) { 686 | item = Item.L(); 687 | } 688 | else 689 | { 690 | _stack.Push(new List(_itemLength)); 691 | return 2; 692 | } 693 | } 694 | else 695 | { 696 | if (!CheckAvailable(length, index, _itemLength, out need)) return 4; 697 | 698 | item = _itemLength == 0 ? _format.BytesDecode() : _format.BytesDecode(data, index, _itemLength); 699 | index += _itemLength; 700 | _messageLength -= (uint)_itemLength; 701 | } 702 | 703 | if (_stack.Count > 0) 704 | { 705 | var list = _stack.Peek(); 706 | list.Add(item); 707 | while (list.Count == list.Capacity) 708 | { 709 | item = Item.L(_stack.Pop()); 710 | if (_stack.Count > 0) 711 | { 712 | list = _stack.Peek(); 713 | list.Add(item); 714 | } 715 | else 716 | { 717 | ProcessMessage(new SecsMessage(_msgHeader.S, _msgHeader.F, _msgHeader.ReplyExpected, string.Empty, item)); 718 | return 0; 719 | } 720 | } 721 | } 722 | return 2; 723 | }, 724 | #endregion 725 | }; 726 | } 727 | 728 | void ProcessMessage(SecsMessage msg) 729 | { 730 | DataMsgHandler(_msgHeader, msg); 731 | _messageLength = 0; 732 | } 733 | 734 | static bool CheckAvailable(int length, int index, int requireCount, out int need) 735 | { 736 | need = requireCount - (length - index); 737 | return need <= 0; 738 | } 739 | 740 | public void Reset() 741 | { 742 | _stack.Clear(); 743 | _currentStep = 0; 744 | _remainBytes = new ArraySegment(); 745 | _messageLength = 0; 746 | } 747 | 748 | /// 749 | /// Offset: next fill index 750 | /// Cout : next fill count 751 | /// 752 | ArraySegment _remainBytes; 753 | 754 | /// 755 | /// 756 | /// 757 | /// 位元組 758 | /// 有效位元的起始索引 759 | /// 有效位元長度 760 | /// 如果輸入的位元組經解碼後尚有不足則回傳true,否則回傳false 761 | public bool Decode(byte[] bytes, int index, int length) 762 | { 763 | if (_remainBytes.Count == 0) 764 | { 765 | int need = Decode(bytes, length, ref index); 766 | int remainLength = length - index; 767 | if (remainLength > 0) 768 | { 769 | var temp = new byte[remainLength + need]; 770 | Array.Copy(bytes, index, temp, 0, remainLength); 771 | _remainBytes = new ArraySegment(temp, remainLength, need); 772 | Trace.WriteLine("Remain Length: " + _remainBytes.Offset + ", Need:" + _remainBytes.Count); 773 | } 774 | else 775 | { 776 | _remainBytes = new ArraySegment(); 777 | } 778 | } 779 | else if (length - index >= _remainBytes.Count) 780 | { 781 | Array.Copy(bytes, index, _remainBytes.Array, _remainBytes.Offset, _remainBytes.Count); 782 | index = _remainBytes.Count; 783 | byte[] temp = _remainBytes.Array; 784 | _remainBytes = new ArraySegment(); 785 | if (Decode(temp, 0, temp.Length)) 786 | Decode(bytes, index, length); 787 | } 788 | else 789 | { 790 | int remainLength = length - index; 791 | Array.Copy(bytes, index, _remainBytes.Array, _remainBytes.Offset, remainLength); 792 | _remainBytes = new ArraySegment(_remainBytes.Array, _remainBytes.Offset + remainLength, _remainBytes.Count - remainLength); 793 | Trace.WriteLine("Remain Length: " + _remainBytes.Offset + ", Need:" + _remainBytes.Count); 794 | } 795 | return _messageLength > 0; 796 | } 797 | 798 | int _currentStep; 799 | /// 800 | /// 將位元組通過decode pipeline處理 801 | /// 802 | /// 位元組 803 | /// 有效位元的起始索引 804 | /// 位元組的起始索引 805 | /// 回傳_currentStep不足的byte數 806 | int Decode(byte[] bytes, int length, ref int index) 807 | { 808 | int need; 809 | int nexStep = _currentStep; 810 | do 811 | { 812 | _currentStep = nexStep; 813 | nexStep = decoders[_currentStep](bytes, length, ref index, out need); 814 | } while (nexStep != _currentStep); 815 | return need; 816 | } 817 | } 818 | #endregion 819 | #region Message Header Struct 820 | struct Header 821 | { 822 | internal readonly byte[] Bytes; 823 | internal Header(byte[] headerbytes) 824 | { 825 | Bytes = headerbytes; 826 | } 827 | 828 | public short DeviceId 829 | { 830 | get 831 | { 832 | return BitConverter.ToInt16(new[] { Bytes[1], Bytes[0] }, 0); 833 | } 834 | set 835 | { 836 | byte[] values = BitConverter.GetBytes(value); 837 | Bytes[0] = values[1]; 838 | Bytes[1] = values[0]; 839 | } 840 | } 841 | public bool ReplyExpected 842 | { 843 | get { return (Bytes[2] & 0x80) == 0x80; } 844 | set { Bytes[2] = (byte)(S | (value ? 0x80 : 0)); } 845 | } 846 | public byte S 847 | { 848 | get { return (byte)(Bytes[2] & 0x7F); } 849 | set { Bytes[2] = (byte)(value | (ReplyExpected ? 0x80 : 0)); } 850 | } 851 | public byte F 852 | { 853 | get { return Bytes[3]; } 854 | set { Bytes[3] = value; } 855 | } 856 | public MessageType MessageType 857 | { 858 | get { return (MessageType)Bytes[5]; } 859 | set { Bytes[5] = (byte)value; } 860 | } 861 | public int SystemBytes 862 | { 863 | get 864 | { 865 | return BitConverter.ToInt32(new[] { 866 | Bytes[9], 867 | Bytes[8], 868 | Bytes[7], 869 | Bytes[6] 870 | }, 0); 871 | } 872 | set 873 | { 874 | byte[] values = BitConverter.GetBytes(value); 875 | Bytes[6] = values[3]; 876 | Bytes[7] = values[2]; 877 | Bytes[8] = values[1]; 878 | Bytes[9] = values[0]; 879 | } 880 | } 881 | } 882 | #endregion 883 | #region EncodedByteList Wrapper just need IList.Count and Indexer 884 | sealed class EncodedBuffer : IList> 885 | { 886 | readonly IReadOnlyList _data;// raw data include first message length 4 byte 887 | readonly byte[] _header; 888 | 889 | internal EncodedBuffer(byte[] header, IReadOnlyList msgRawDatas) 890 | { 891 | _header = header; 892 | _data = msgRawDatas; 893 | } 894 | 895 | #region IList> Members 896 | int IList>.IndexOf(ArraySegment item) => -1; 897 | void IList>.Insert(int index, ArraySegment item) { } 898 | void IList>.RemoveAt(int index) { } 899 | ArraySegment IList>.this[int index] 900 | { 901 | get { return new ArraySegment(index == 1 ? _header : _data[index].Bytes); } 902 | set { } 903 | } 904 | #endregion 905 | #region ICollection> Members 906 | void ICollection>.Add(ArraySegment item) { } 907 | void ICollection>.Clear() { } 908 | bool ICollection>.Contains(ArraySegment item) => false; 909 | void ICollection>.CopyTo(ArraySegment[] array, int arrayIndex) { } 910 | int ICollection>.Count => _data.Count; 911 | bool ICollection>.IsReadOnly => true; 912 | bool ICollection>.Remove(ArraySegment item) => false; 913 | #endregion 914 | #region IEnumerable> Members 915 | public IEnumerator> GetEnumerator() 916 | { 917 | for (int i = 0, length = _data.Count; i < length; i++) 918 | yield return new ArraySegment(i == 1 ? _header : _data[i].Bytes); 919 | } 920 | #endregion 921 | #region IEnumerable Members 922 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 923 | #endregion 924 | } 925 | #endregion 926 | } 927 | } -------------------------------------------------------------------------------- /Secs4Net/SecsMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Runtime.Remoting.Lifetime; 7 | using System.Runtime.Serialization; 8 | using System.Security.Permissions; 9 | 10 | namespace Secs4Net { 11 | [Serializable] 12 | public sealed class SecsMessage : MarshalByRefObject, ISerializable { 13 | static SecsMessage() { 14 | if (!BitConverter.IsLittleEndian) 15 | throw new PlatformNotSupportedException("This version is only work on little endian hardware."); 16 | } 17 | [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)] 18 | public override object InitializeLifetimeService() { 19 | ILease lease = (ILease)base.InitializeLifetimeService(); 20 | if (lease.CurrentState == LeaseState.Initial) { 21 | lease.InitialLeaseTime = TimeSpan.FromSeconds(30); 22 | lease.RenewOnCallTime = TimeSpan.FromSeconds(10); 23 | } 24 | return lease; 25 | } 26 | 27 | public override string ToString() => $"{Name ?? string.Empty} : 'S{S}F{F}' {(ReplyExpected ? " W" : string.Empty)}"; 28 | 29 | public byte S { get; } 30 | public byte F { get; } 31 | public bool ReplyExpected { get; internal set; } 32 | public Item SecsItem { get; } 33 | public string Name { get; set; } 34 | 35 | public ReadOnlyCollection RawDatas => _rawDatas.Value; 36 | readonly Lazy> _rawDatas; 37 | 38 | static readonly RawData dummyHeaderDatas = new RawData(new byte[10]); 39 | static readonly Lazy> emptyMsgDatas = Lazy.Create(new List { new RawData(new byte[] { 0, 0, 0, 10 }), null }.AsReadOnly()); 40 | #region Constructor 41 | 42 | public SecsMessage(byte s, byte f, bool replyExpected = true, string name = null, Item item = null) 43 | { 44 | if (s > 0x7F) 45 | throw new ArgumentOutOfRangeException(nameof(s), s, "Stream number must be less than 127"); 46 | 47 | S = s; 48 | F = f; 49 | Name = name; 50 | ReplyExpected = replyExpected; 51 | SecsItem = item; 52 | 53 | _rawDatas = item == null ? emptyMsgDatas : Lazy.Create(() => 54 | { 55 | var result = new List { null, dummyHeaderDatas }; 56 | uint length = 10 + SecsItem.Encode(result); 57 | byte[] msgLengthByte = BitConverter.GetBytes(length); 58 | Array.Reverse(msgLengthByte); 59 | result[0] = new RawData(msgLengthByte); 60 | return result.AsReadOnly(); 61 | }); 62 | } 63 | 64 | public SecsMessage(byte s, byte f, string name, Item item = null) 65 | : this(s, f, true, name, item) 66 | { } 67 | 68 | internal SecsMessage(byte s, byte f, bool replyExpected, byte[] itemBytes, ref int index) 69 | : this(s, f, replyExpected, string.Empty, Decode(itemBytes, ref index)) 70 | { } 71 | 72 | #endregion 73 | #region ISerializable Members 74 | SecsMessage(SerializationInfo info, StreamingContext context) 75 | { 76 | S = info.GetByte(nameof(S)); 77 | F = info.GetByte(nameof(F)); 78 | ReplyExpected = info.GetBoolean(nameof(ReplyExpected)); 79 | Name = info.GetString(nameof(Name)); 80 | _rawDatas = Lazy.Create(info.GetValue(nameof(_rawDatas), typeof(ReadOnlyCollection)) as ReadOnlyCollection); 81 | int i = 0; 82 | if (_rawDatas.Value.Count > 2) 83 | SecsItem = Decode(_rawDatas.Value.Skip(2).SelectMany(arr => arr.Bytes).ToArray(), ref i); 84 | } 85 | 86 | [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)] 87 | void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { 88 | info.AddValue(nameof(S), S); 89 | info.AddValue(nameof(F), F); 90 | info.AddValue(nameof(ReplyExpected), ReplyExpected); 91 | info.AddValue(nameof(Name), Name); 92 | info.AddValue(nameof(_rawDatas), _rawDatas.Value); 93 | } 94 | #endregion 95 | 96 | static Item Decode(byte[] bytes, ref int index) { 97 | var format = (SecsFormat)(bytes[index] & 0xFC); 98 | var lengthBits = (byte)(bytes[index] & 3); 99 | index++; 100 | 101 | var itemLengthBytes = new byte[4]; 102 | Array.Copy(bytes, index, itemLengthBytes, 0, lengthBits); 103 | Array.Reverse(itemLengthBytes, 0, lengthBits); 104 | int length = BitConverter.ToInt32(itemLengthBytes, 0); // max to 3 byte length 105 | index += lengthBits; 106 | 107 | if (format == SecsFormat.List) { 108 | if (length == 0) 109 | return Item.L(); 110 | 111 | var list = new List(length); 112 | for (int i = 0; i < length; i++) 113 | list.Add(Decode(bytes, ref index)); 114 | return Item.L(list); 115 | } 116 | var item = length == 0 ? format.BytesDecode() : format.BytesDecode(bytes, index, length); 117 | index += length; 118 | return item; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /SecsDevice/Form1.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace SecsDevice { 2 | partial class Form1 { 3 | /// 4 | /// Required designer variable. 5 | /// 6 | private System.ComponentModel.IContainer components = null; 7 | 8 | /// 9 | /// Clean up any resources being used. 10 | /// 11 | /// true if managed resources should be disposed; otherwise, false. 12 | protected override void Dispose(bool disposing) { 13 | if (disposing && (components != null)) { 14 | components.Dispose(); 15 | } 16 | base.Dispose(disposing); 17 | } 18 | 19 | #region Windows Form Designer generated code 20 | 21 | /// 22 | /// Required method for Designer support - do not modify 23 | /// the contents of this method with the code editor. 24 | /// 25 | private void InitializeComponent() { 26 | this.components = new System.ComponentModel.Container(); 27 | System.Windows.Forms.GroupBox groupBox1; 28 | System.Windows.Forms.Label label3; 29 | System.Windows.Forms.Label label2; 30 | System.Windows.Forms.Label label1; 31 | System.Windows.Forms.GroupBox groupBox4; 32 | System.Windows.Forms.GroupBox groupBox2; 33 | System.Windows.Forms.GroupBox groupBox5; 34 | System.Windows.Forms.GroupBox groupBox3; 35 | System.Windows.Forms.Button btnSendPrimary; 36 | System.Windows.Forms.Button btnReplySecondary; 37 | this.numDeviceId = new System.Windows.Forms.NumericUpDown(); 38 | this.lbStatus = new System.Windows.Forms.Label(); 39 | this.btnDisable = new System.Windows.Forms.Button(); 40 | this.btnEnable = new System.Windows.Forms.Button(); 41 | this.numPort = new System.Windows.Forms.NumericUpDown(); 42 | this.txtAddress = new System.Windows.Forms.TextBox(); 43 | this.radioPassiveMode = new System.Windows.Forms.RadioButton(); 44 | this.radioActiveMode = new System.Windows.Forms.RadioButton(); 45 | this.txtRecvSecondary = new System.Windows.Forms.TextBox(); 46 | this.txtSendPrimary = new System.Windows.Forms.TextBox(); 47 | this.txtReplySeconary = new System.Windows.Forms.TextBox(); 48 | this.txtRecvPrimary = new System.Windows.Forms.TextBox(); 49 | this.lstUnreplyMsg = new System.Windows.Forms.ListBox(); 50 | this.recvMessageBindingSource = new System.Windows.Forms.BindingSource(this.components); 51 | this.splitContainer1 = new System.Windows.Forms.SplitContainer(); 52 | this.splitContainer2 = new System.Windows.Forms.SplitContainer(); 53 | this.richTextBox1 = new System.Windows.Forms.RichTextBox(); 54 | groupBox1 = new System.Windows.Forms.GroupBox(); 55 | label3 = new System.Windows.Forms.Label(); 56 | label2 = new System.Windows.Forms.Label(); 57 | label1 = new System.Windows.Forms.Label(); 58 | groupBox4 = new System.Windows.Forms.GroupBox(); 59 | groupBox2 = new System.Windows.Forms.GroupBox(); 60 | groupBox5 = new System.Windows.Forms.GroupBox(); 61 | groupBox3 = new System.Windows.Forms.GroupBox(); 62 | btnSendPrimary = new System.Windows.Forms.Button(); 63 | btnReplySecondary = new System.Windows.Forms.Button(); 64 | groupBox1.SuspendLayout(); 65 | ((System.ComponentModel.ISupportInitialize)(this.numDeviceId)).BeginInit(); 66 | ((System.ComponentModel.ISupportInitialize)(this.numPort)).BeginInit(); 67 | groupBox4.SuspendLayout(); 68 | groupBox2.SuspendLayout(); 69 | groupBox5.SuspendLayout(); 70 | groupBox3.SuspendLayout(); 71 | ((System.ComponentModel.ISupportInitialize)(this.recvMessageBindingSource)).BeginInit(); 72 | ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); 73 | this.splitContainer1.Panel1.SuspendLayout(); 74 | this.splitContainer1.Panel2.SuspendLayout(); 75 | this.splitContainer1.SuspendLayout(); 76 | ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit(); 77 | this.splitContainer2.Panel1.SuspendLayout(); 78 | this.splitContainer2.Panel2.SuspendLayout(); 79 | this.splitContainer2.SuspendLayout(); 80 | this.SuspendLayout(); 81 | // 82 | // groupBox1 83 | // 84 | groupBox1.Controls.Add(this.numDeviceId); 85 | groupBox1.Controls.Add(label3); 86 | groupBox1.Controls.Add(this.lbStatus); 87 | groupBox1.Controls.Add(this.btnDisable); 88 | groupBox1.Controls.Add(this.btnEnable); 89 | groupBox1.Controls.Add(this.numPort); 90 | groupBox1.Controls.Add(label2); 91 | groupBox1.Controls.Add(label1); 92 | groupBox1.Controls.Add(this.txtAddress); 93 | groupBox1.Controls.Add(this.radioPassiveMode); 94 | groupBox1.Controls.Add(this.radioActiveMode); 95 | groupBox1.Dock = System.Windows.Forms.DockStyle.Top; 96 | groupBox1.Location = new System.Drawing.Point(0, 0); 97 | groupBox1.Name = "groupBox1"; 98 | groupBox1.Size = new System.Drawing.Size(1214, 76); 99 | groupBox1.TabIndex = 1; 100 | groupBox1.TabStop = false; 101 | groupBox1.Text = "Config"; 102 | // 103 | // numDeviceId 104 | // 105 | this.numDeviceId.Location = new System.Drawing.Point(405, 29); 106 | this.numDeviceId.Maximum = new decimal(new int[] { 107 | 10, 108 | 0, 109 | 0, 110 | 0}); 111 | this.numDeviceId.Name = "numDeviceId"; 112 | this.numDeviceId.Size = new System.Drawing.Size(43, 20); 113 | this.numDeviceId.TabIndex = 10; 114 | // 115 | // label3 116 | // 117 | label3.AutoSize = true; 118 | label3.Location = new System.Drawing.Point(349, 34); 119 | label3.Name = "label3"; 120 | label3.Size = new System.Drawing.Size(53, 13); 121 | label3.TabIndex = 9; 122 | label3.Text = "Device Id"; 123 | // 124 | // lbStatus 125 | // 126 | this.lbStatus.AutoSize = true; 127 | this.lbStatus.Font = new System.Drawing.Font("PMingLiU", 24F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(136))); 128 | this.lbStatus.Location = new System.Drawing.Point(660, 27); 129 | this.lbStatus.Name = "lbStatus"; 130 | this.lbStatus.Size = new System.Drawing.Size(94, 32); 131 | this.lbStatus.TabIndex = 8; 132 | this.lbStatus.Text = "Status"; 133 | // 134 | // btnDisable 135 | // 136 | this.btnDisable.DialogResult = System.Windows.Forms.DialogResult.Cancel; 137 | this.btnDisable.Enabled = false; 138 | this.btnDisable.Location = new System.Drawing.Point(545, 27); 139 | this.btnDisable.Name = "btnDisable"; 140 | this.btnDisable.Size = new System.Drawing.Size(75, 25); 141 | this.btnDisable.TabIndex = 7; 142 | this.btnDisable.Text = "Disable"; 143 | this.btnDisable.UseVisualStyleBackColor = true; 144 | this.btnDisable.Click += new System.EventHandler(this.btnDisable_Click); 145 | // 146 | // btnEnable 147 | // 148 | this.btnEnable.Location = new System.Drawing.Point(464, 28); 149 | this.btnEnable.Name = "btnEnable"; 150 | this.btnEnable.Size = new System.Drawing.Size(75, 25); 151 | this.btnEnable.TabIndex = 6; 152 | this.btnEnable.Text = "Enable"; 153 | this.btnEnable.UseVisualStyleBackColor = true; 154 | this.btnEnable.Click += new System.EventHandler(this.btnEnable_Click); 155 | // 156 | // numPort 157 | // 158 | this.numPort.Location = new System.Drawing.Point(280, 29); 159 | this.numPort.Maximum = new decimal(new int[] { 160 | 9999, 161 | 0, 162 | 0, 163 | 0}); 164 | this.numPort.Minimum = new decimal(new int[] { 165 | 5000, 166 | 0, 167 | 0, 168 | 0}); 169 | this.numPort.Name = "numPort"; 170 | this.numPort.Size = new System.Drawing.Size(52, 20); 171 | this.numPort.TabIndex = 5; 172 | this.numPort.Value = new decimal(new int[] { 173 | 5000, 174 | 0, 175 | 0, 176 | 0}); 177 | // 178 | // label2 179 | // 180 | label2.AutoSize = true; 181 | label2.Location = new System.Drawing.Point(250, 34); 182 | label2.Name = "label2"; 183 | label2.Size = new System.Drawing.Size(26, 13); 184 | label2.TabIndex = 4; 185 | label2.Text = "Port"; 186 | // 187 | // label1 188 | // 189 | label1.AutoSize = true; 190 | label1.Location = new System.Drawing.Point(80, 34); 191 | label1.Name = "label1"; 192 | label1.Size = new System.Drawing.Size(17, 13); 193 | label1.TabIndex = 3; 194 | label1.Text = "IP"; 195 | // 196 | // txtAddress 197 | // 198 | this.txtAddress.Location = new System.Drawing.Point(101, 29); 199 | this.txtAddress.Name = "txtAddress"; 200 | this.txtAddress.Size = new System.Drawing.Size(143, 20); 201 | this.txtAddress.TabIndex = 2; 202 | // 203 | // radioPassiveMode 204 | // 205 | this.radioPassiveMode.AutoSize = true; 206 | this.radioPassiveMode.Location = new System.Drawing.Point(12, 46); 207 | this.radioPassiveMode.Name = "radioPassiveMode"; 208 | this.radioPassiveMode.Size = new System.Drawing.Size(62, 17); 209 | this.radioPassiveMode.TabIndex = 1; 210 | this.radioPassiveMode.Text = "Passive"; 211 | this.radioPassiveMode.UseVisualStyleBackColor = true; 212 | // 213 | // radioActiveMode 214 | // 215 | this.radioActiveMode.AutoSize = true; 216 | this.radioActiveMode.Checked = true; 217 | this.radioActiveMode.Location = new System.Drawing.Point(12, 22); 218 | this.radioActiveMode.Name = "radioActiveMode"; 219 | this.radioActiveMode.Size = new System.Drawing.Size(55, 17); 220 | this.radioActiveMode.TabIndex = 0; 221 | this.radioActiveMode.TabStop = true; 222 | this.radioActiveMode.Text = "Active"; 223 | this.radioActiveMode.UseVisualStyleBackColor = true; 224 | // 225 | // groupBox4 226 | // 227 | groupBox4.Controls.Add(this.txtRecvSecondary); 228 | groupBox4.Dock = System.Windows.Forms.DockStyle.Fill; 229 | groupBox4.Location = new System.Drawing.Point(0, 325); 230 | groupBox4.Name = "groupBox4"; 231 | groupBox4.Size = new System.Drawing.Size(449, 300); 232 | groupBox4.TabIndex = 5; 233 | groupBox4.TabStop = false; 234 | groupBox4.Text = "Received Secondary Message"; 235 | // 236 | // txtRecvSecondary 237 | // 238 | this.txtRecvSecondary.Dock = System.Windows.Forms.DockStyle.Fill; 239 | this.txtRecvSecondary.Location = new System.Drawing.Point(3, 16); 240 | this.txtRecvSecondary.Multiline = true; 241 | this.txtRecvSecondary.Name = "txtRecvSecondary"; 242 | this.txtRecvSecondary.ReadOnly = true; 243 | this.txtRecvSecondary.Size = new System.Drawing.Size(443, 281); 244 | this.txtRecvSecondary.TabIndex = 0; 245 | this.txtRecvSecondary.WordWrap = false; 246 | // 247 | // groupBox2 248 | // 249 | groupBox2.Controls.Add(this.txtSendPrimary); 250 | groupBox2.Dock = System.Windows.Forms.DockStyle.Top; 251 | groupBox2.Location = new System.Drawing.Point(0, 0); 252 | groupBox2.Name = "groupBox2"; 253 | groupBox2.Size = new System.Drawing.Size(449, 300); 254 | groupBox2.TabIndex = 3; 255 | groupBox2.TabStop = false; 256 | groupBox2.Text = "Send Primary Message"; 257 | // 258 | // txtSendPrimary 259 | // 260 | this.txtSendPrimary.Dock = System.Windows.Forms.DockStyle.Fill; 261 | this.txtSendPrimary.Location = new System.Drawing.Point(3, 16); 262 | this.txtSendPrimary.Multiline = true; 263 | this.txtSendPrimary.Name = "txtSendPrimary"; 264 | this.txtSendPrimary.Size = new System.Drawing.Size(443, 281); 265 | this.txtSendPrimary.TabIndex = 1; 266 | this.txtSendPrimary.WordWrap = false; 267 | // 268 | // groupBox5 269 | // 270 | groupBox5.Controls.Add(this.txtReplySeconary); 271 | groupBox5.Dock = System.Windows.Forms.DockStyle.Fill; 272 | groupBox5.Location = new System.Drawing.Point(0, 325); 273 | groupBox5.Name = "groupBox5"; 274 | groupBox5.Size = new System.Drawing.Size(492, 275); 275 | groupBox5.TabIndex = 2; 276 | groupBox5.TabStop = false; 277 | groupBox5.Text = "Reply Secondary Message"; 278 | // 279 | // txtReplySeconary 280 | // 281 | this.txtReplySeconary.Dock = System.Windows.Forms.DockStyle.Fill; 282 | this.txtReplySeconary.Location = new System.Drawing.Point(3, 16); 283 | this.txtReplySeconary.Multiline = true; 284 | this.txtReplySeconary.Name = "txtReplySeconary"; 285 | this.txtReplySeconary.Size = new System.Drawing.Size(486, 256); 286 | this.txtReplySeconary.TabIndex = 0; 287 | this.txtReplySeconary.WordWrap = false; 288 | // 289 | // groupBox3 290 | // 291 | groupBox3.Controls.Add(this.txtRecvPrimary); 292 | groupBox3.Controls.Add(this.lstUnreplyMsg); 293 | groupBox3.Dock = System.Windows.Forms.DockStyle.Top; 294 | groupBox3.Location = new System.Drawing.Point(0, 0); 295 | groupBox3.Name = "groupBox3"; 296 | groupBox3.Size = new System.Drawing.Size(492, 325); 297 | groupBox3.TabIndex = 0; 298 | groupBox3.TabStop = false; 299 | groupBox3.Text = "Received Primary Message"; 300 | // 301 | // txtRecvPrimary 302 | // 303 | this.txtRecvPrimary.Dock = System.Windows.Forms.DockStyle.Fill; 304 | this.txtRecvPrimary.Location = new System.Drawing.Point(208, 16); 305 | this.txtRecvPrimary.Multiline = true; 306 | this.txtRecvPrimary.Name = "txtRecvPrimary"; 307 | this.txtRecvPrimary.ReadOnly = true; 308 | this.txtRecvPrimary.Size = new System.Drawing.Size(281, 306); 309 | this.txtRecvPrimary.TabIndex = 1; 310 | this.txtRecvPrimary.WordWrap = false; 311 | // 312 | // lstUnreplyMsg 313 | // 314 | this.lstUnreplyMsg.DataSource = this.recvMessageBindingSource; 315 | this.lstUnreplyMsg.DisplayMember = "Msg"; 316 | this.lstUnreplyMsg.Dock = System.Windows.Forms.DockStyle.Left; 317 | this.lstUnreplyMsg.FormattingEnabled = true; 318 | this.lstUnreplyMsg.Location = new System.Drawing.Point(3, 16); 319 | this.lstUnreplyMsg.Name = "lstUnreplyMsg"; 320 | this.lstUnreplyMsg.Size = new System.Drawing.Size(205, 306); 321 | this.lstUnreplyMsg.TabIndex = 0; 322 | this.lstUnreplyMsg.SelectedIndexChanged += new System.EventHandler(this.lstUnreplyMsg_SelectedIndexChanged); 323 | // 324 | // recvMessageBindingSource 325 | // 326 | this.recvMessageBindingSource.DataSource = typeof(SecsDevice.RecvMessage); 327 | // 328 | // btnSendPrimary 329 | // 330 | btnSendPrimary.Dock = System.Windows.Forms.DockStyle.Top; 331 | btnSendPrimary.Location = new System.Drawing.Point(0, 300); 332 | btnSendPrimary.Name = "btnSendPrimary"; 333 | btnSendPrimary.Size = new System.Drawing.Size(449, 25); 334 | btnSendPrimary.TabIndex = 4; 335 | btnSendPrimary.Text = "Send"; 336 | btnSendPrimary.UseVisualStyleBackColor = true; 337 | btnSendPrimary.Click += new System.EventHandler(this.btnSendPrimary_Click); 338 | // 339 | // btnReplySecondary 340 | // 341 | btnReplySecondary.Dock = System.Windows.Forms.DockStyle.Bottom; 342 | btnReplySecondary.Location = new System.Drawing.Point(0, 600); 343 | btnReplySecondary.Name = "btnReplySecondary"; 344 | btnReplySecondary.Size = new System.Drawing.Size(492, 25); 345 | btnReplySecondary.TabIndex = 1; 346 | btnReplySecondary.Text = "Reply"; 347 | btnReplySecondary.UseVisualStyleBackColor = true; 348 | btnReplySecondary.Click += new System.EventHandler(this.btnReplySecondary_Click); 349 | // 350 | // splitContainer1 351 | // 352 | this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; 353 | this.splitContainer1.Location = new System.Drawing.Point(0, 0); 354 | this.splitContainer1.Name = "splitContainer1"; 355 | // 356 | // splitContainer1.Panel1 357 | // 358 | this.splitContainer1.Panel1.Controls.Add(groupBox4); 359 | this.splitContainer1.Panel1.Controls.Add(btnSendPrimary); 360 | this.splitContainer1.Panel1.Controls.Add(groupBox2); 361 | // 362 | // splitContainer1.Panel2 363 | // 364 | this.splitContainer1.Panel2.Controls.Add(groupBox5); 365 | this.splitContainer1.Panel2.Controls.Add(btnReplySecondary); 366 | this.splitContainer1.Panel2.Controls.Add(groupBox3); 367 | this.splitContainer1.Size = new System.Drawing.Size(945, 625); 368 | this.splitContainer1.SplitterDistance = 449; 369 | this.splitContainer1.TabIndex = 3; 370 | // 371 | // splitContainer2 372 | // 373 | this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill; 374 | this.splitContainer2.Location = new System.Drawing.Point(0, 76); 375 | this.splitContainer2.Name = "splitContainer2"; 376 | // 377 | // splitContainer2.Panel1 378 | // 379 | this.splitContainer2.Panel1.Controls.Add(this.splitContainer1); 380 | // 381 | // splitContainer2.Panel2 382 | // 383 | this.splitContainer2.Panel2.Controls.Add(this.richTextBox1); 384 | this.splitContainer2.Size = new System.Drawing.Size(1214, 625); 385 | this.splitContainer2.SplitterDistance = 945; 386 | this.splitContainer2.TabIndex = 11; 387 | // 388 | // richTextBox1 389 | // 390 | this.richTextBox1.Dock = System.Windows.Forms.DockStyle.Fill; 391 | this.richTextBox1.Location = new System.Drawing.Point(0, 0); 392 | this.richTextBox1.Name = "richTextBox1"; 393 | this.richTextBox1.ReadOnly = true; 394 | this.richTextBox1.Size = new System.Drawing.Size(265, 625); 395 | this.richTextBox1.TabIndex = 1; 396 | this.richTextBox1.Text = ""; 397 | this.richTextBox1.WordWrap = false; 398 | // 399 | // Form1 400 | // 401 | this.AcceptButton = this.btnEnable; 402 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 403 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 404 | this.CancelButton = this.btnDisable; 405 | this.ClientSize = new System.Drawing.Size(1214, 701); 406 | this.Controls.Add(this.splitContainer2); 407 | this.Controls.Add(groupBox1); 408 | this.Name = "Form1"; 409 | this.Text = "SECS Device"; 410 | this.WindowState = System.Windows.Forms.FormWindowState.Maximized; 411 | groupBox1.ResumeLayout(false); 412 | groupBox1.PerformLayout(); 413 | ((System.ComponentModel.ISupportInitialize)(this.numDeviceId)).EndInit(); 414 | ((System.ComponentModel.ISupportInitialize)(this.numPort)).EndInit(); 415 | groupBox4.ResumeLayout(false); 416 | groupBox4.PerformLayout(); 417 | groupBox2.ResumeLayout(false); 418 | groupBox2.PerformLayout(); 419 | groupBox5.ResumeLayout(false); 420 | groupBox5.PerformLayout(); 421 | groupBox3.ResumeLayout(false); 422 | groupBox3.PerformLayout(); 423 | ((System.ComponentModel.ISupportInitialize)(this.recvMessageBindingSource)).EndInit(); 424 | this.splitContainer1.Panel1.ResumeLayout(false); 425 | this.splitContainer1.Panel2.ResumeLayout(false); 426 | ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); 427 | this.splitContainer1.ResumeLayout(false); 428 | this.splitContainer2.Panel1.ResumeLayout(false); 429 | this.splitContainer2.Panel2.ResumeLayout(false); 430 | ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); 431 | this.splitContainer2.ResumeLayout(false); 432 | this.ResumeLayout(false); 433 | 434 | } 435 | 436 | #endregion 437 | 438 | private System.Windows.Forms.RadioButton radioPassiveMode; 439 | private System.Windows.Forms.RadioButton radioActiveMode; 440 | private System.Windows.Forms.Button btnDisable; 441 | private System.Windows.Forms.Button btnEnable; 442 | private System.Windows.Forms.NumericUpDown numPort; 443 | private System.Windows.Forms.TextBox txtAddress; 444 | private System.Windows.Forms.SplitContainer splitContainer1; 445 | private System.Windows.Forms.TextBox txtSendPrimary; 446 | private System.Windows.Forms.TextBox txtRecvSecondary; 447 | private System.Windows.Forms.TextBox txtRecvPrimary; 448 | private System.Windows.Forms.ListBox lstUnreplyMsg; 449 | private System.Windows.Forms.TextBox txtReplySeconary; 450 | private System.Windows.Forms.Label lbStatus; 451 | private System.Windows.Forms.NumericUpDown numDeviceId; 452 | private System.Windows.Forms.BindingSource recvMessageBindingSource; 453 | private System.Windows.Forms.SplitContainer splitContainer2; 454 | private System.Windows.Forms.RichTextBox richTextBox1; 455 | } 456 | } 457 | 458 | -------------------------------------------------------------------------------- /SecsDevice/Form1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Windows.Forms; 4 | using Secs4Net; 5 | using System.Net; 6 | using System.Drawing; 7 | 8 | namespace SecsDevice { 9 | public partial class Form1 : Form { 10 | SecsGem _secsGem; 11 | readonly SecsTracer Logger; 12 | readonly BindingList recvBuffer = new BindingList(); 13 | public Form1() { 14 | InitializeComponent(); 15 | 16 | radioActiveMode.DataBindings.Add("Enabled", btnEnable, "Enabled"); 17 | radioPassiveMode.DataBindings.Add("Enabled", btnEnable, "Enabled"); 18 | txtAddress.DataBindings.Add("Enabled", btnEnable, "Enabled"); 19 | numPort.DataBindings.Add("Enabled", btnEnable, "Enabled"); 20 | numDeviceId.DataBindings.Add("Enabled", btnEnable, "Enabled"); 21 | recvMessageBindingSource.DataSource = recvBuffer; 22 | Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException); 23 | 24 | Logger = new SecsLogger(this); 25 | } 26 | 27 | void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) { 28 | MessageBox.Show(e.Exception.ToString()); 29 | } 30 | 31 | private async void btnEnable_Click(object sender, EventArgs e) 32 | { 33 | _secsGem?.Dispose(); 34 | _secsGem = new SecsGem( 35 | ip: IPAddress.Parse(txtAddress.Text), 36 | port: (int)numPort.Value, 37 | isActive: radioActiveMode.Checked, 38 | tracer: Logger, 39 | primaryMsgHandler: (primaryMsg, replyAction) => 40 | this.Invoke(new MethodInvoker(() => 41 | recvBuffer.Add(new RecvMessage 42 | { 43 | Msg = primaryMsg, 44 | ReplyAction = replyAction 45 | })))); 46 | 47 | _secsGem.ConnectionChanged += delegate 48 | { 49 | this.Invoke((MethodInvoker)delegate 50 | { 51 | lbStatus.Text = _secsGem.State.ToString(); 52 | }); 53 | }; 54 | 55 | btnEnable.Enabled = false; 56 | await _secsGem.Start(); 57 | btnDisable.Enabled = true; 58 | } 59 | 60 | private void btnDisable_Click(object sender, EventArgs e) 61 | { 62 | _secsGem?.Dispose(); 63 | _secsGem = null; 64 | btnEnable.Enabled = true; 65 | btnDisable.Enabled = false; 66 | lbStatus.Text = "Disable"; 67 | recvBuffer.Clear(); 68 | } 69 | 70 | private async void btnSendPrimary_Click(object sender, EventArgs e) 71 | { 72 | if (_secsGem.State != ConnectionState.Selected) 73 | return; 74 | if (string.IsNullOrWhiteSpace(txtSendPrimary.Text)) 75 | return; 76 | 77 | try 78 | { 79 | var reply = await _secsGem.SendAsync(txtSendPrimary.Text.ToSecsMessage()); 80 | txtRecvSecondary.Text = reply.ToSML(); 81 | } 82 | catch (SecsException ex) 83 | { 84 | txtRecvSecondary.Text = ex.Message; 85 | } 86 | } 87 | 88 | private void lstUnreplyMsg_SelectedIndexChanged(object sender, EventArgs e) { 89 | var recv = lstUnreplyMsg.SelectedItem as RecvMessage; 90 | txtRecvPrimary.Text = recv?.Msg.ToSML(); 91 | } 92 | 93 | private void btnReplySecondary_Click(object sender, EventArgs e) { 94 | var recv = lstUnreplyMsg.SelectedItem as RecvMessage; 95 | if (recv == null) 96 | return; 97 | 98 | if (string.IsNullOrWhiteSpace(txtReplySeconary.Text)) 99 | return; 100 | 101 | recv.ReplyAction(txtReplySeconary.Text.ToSecsMessage()); 102 | recvBuffer.Remove(recv); 103 | txtRecvPrimary.Clear(); 104 | } 105 | 106 | class SecsLogger : SecsTracer 107 | { 108 | readonly Form1 _form; 109 | internal SecsLogger(Form1 form) 110 | { 111 | _form = form; 112 | } 113 | public override void TraceMessageIn(SecsMessage msg, int systembyte) 114 | { 115 | _form.Invoke((MethodInvoker)delegate { 116 | _form.richTextBox1.SelectionColor = Color.Black; 117 | _form.richTextBox1.AppendText($"<-- [0x{systembyte:X8}] {msg.ToSML()}\n"); 118 | }); 119 | } 120 | 121 | public override void TraceMessageOut(SecsMessage msg, int systembyte) 122 | { 123 | _form.Invoke((MethodInvoker)delegate { 124 | _form.richTextBox1.SelectionColor = Color.Black; 125 | _form.richTextBox1.AppendText($"--> [0x{systembyte:X8}] {msg.ToSML()}\n"); 126 | }); 127 | } 128 | 129 | public override void TraceInfo(string msg) 130 | { 131 | _form.Invoke((MethodInvoker)delegate { 132 | _form.richTextBox1.SelectionColor = Color.Blue; 133 | _form.richTextBox1.AppendText($"{msg}\n"); 134 | }); 135 | } 136 | 137 | public override void TraceWarning(string msg) 138 | { 139 | _form.Invoke((MethodInvoker)delegate { 140 | _form.richTextBox1.SelectionColor = Color.Green; 141 | _form.richTextBox1.AppendText($"{msg}\n"); 142 | }); 143 | } 144 | 145 | public override void TraceError(string msg, Exception ex = null) 146 | { 147 | _form.Invoke((MethodInvoker)delegate { 148 | _form.richTextBox1.SelectionColor = Color.Red; 149 | _form.richTextBox1.AppendText($"{msg}\n"); 150 | _form.richTextBox1.SelectionColor = Color.Gray; 151 | _form.richTextBox1.AppendText($"{ex?.ToString()}\n"); 152 | }); 153 | } 154 | } 155 | } 156 | 157 | public sealed class RecvMessage { 158 | public SecsMessage Msg { get; set; } 159 | public Action ReplyAction { get; set; } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /SecsDevice/Form1.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 | False 122 | 123 | 124 | False 125 | 126 | 127 | False 128 | 129 | 130 | False 131 | 132 | 133 | False 134 | 135 | 136 | False 137 | 138 | 139 | False 140 | 141 | 142 | False 143 | 144 | 145 | False 146 | 147 | 148 | False 149 | 150 | 151 | False 152 | 153 | 154 | 17, 17 155 | 156 | 157 | 17, 17 158 | 159 | 160 | False 161 | 162 | 163 | False 164 | 165 | -------------------------------------------------------------------------------- /SecsDevice/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows.Forms; 4 | 5 | namespace SecsDevice { 6 | static class Program { 7 | /// 8 | /// The main entry point for the application. 9 | /// 10 | [STAThread] 11 | static void Main() { 12 | Application.EnableVisualStyles(); 13 | Application.SetCompatibleTextRenderingDefault(false); 14 | Application.Run(new Form1()); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SecsDevice/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SecsDevice")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("mkjeff@gmail.com")] 12 | [assembly: AssemblyProduct("SecsDevice")] 13 | [assembly: AssemblyCopyright("Copyright © dogmouse 2010")] 14 | [assembly: AssemblyTrademark("Dogmouse")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("2fe7431a-7186-4c92-a522-1ab002415c68")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SecsDevice/Properties/DataSources/RecvMessage.datasource: -------------------------------------------------------------------------------- 1 |  2 | 8 | 9 | SecsDevice.RecvMessage, SecsDevice, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null 10 | -------------------------------------------------------------------------------- /SecsDevice/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace SecsDevice.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.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 | /// Returns the cached ResourceManager instance used by this class. 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("SecsDevice.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 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 | -------------------------------------------------------------------------------- /SecsDevice/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 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /SecsDevice/ReadMe.txt: -------------------------------------------------------------------------------- 1 | This sample program is the demo of Secs4Net.You can simulate a device/host to communicate with other HSMS-SS/SECS-II device. 2 | Try to send/reply message with following SML format, notice the end of angle brackets of the list item.(or refer to received message textbox) 3 | This just a simple demo,expand it by yourself for fun. 4 | 5 | S6F11ReadyToLoad: 'S6F11' W 6 | 8 | 9 | 12 | 14 | > 15 | > 16 | > 17 | > 18 | . 19 | 20 | S6F12: 'S6F12' 21 | 22 | . -------------------------------------------------------------------------------- /SecsDevice/SecsDevice.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {8BF9676F-4514-4C43-A33A-23B9A098DC63} 9 | WinExe 10 | Properties 11 | SecsDevice 12 | SecsDevice 13 | v4.6 14 | 512 15 | 16 | 17 | 18 | 19 | 3.5 20 | publish\ 21 | true 22 | Disk 23 | false 24 | Foreground 25 | 7 26 | Days 27 | false 28 | false 29 | true 30 | 0 31 | 1.0.0.%2a 32 | false 33 | false 34 | true 35 | 36 | 37 | 38 | true 39 | full 40 | false 41 | bin\Debug\ 42 | DEBUG;TRACE 43 | prompt 44 | 4 45 | AllRules.ruleset 46 | false 47 | 48 | 49 | pdbonly 50 | true 51 | bin\Release\ 52 | TRACE 53 | prompt 54 | 4 55 | AllRules.ruleset 56 | false 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | Form 69 | 70 | 71 | Form1.cs 72 | 73 | 74 | 75 | 76 | Form1.cs 77 | 78 | 79 | ResXFileCodeGenerator 80 | Resources.Designer.cs 81 | Designer 82 | 83 | 84 | True 85 | Resources.resx 86 | True 87 | 88 | 89 | 90 | 91 | 92 | 93 | {3C8C67B8-3746-4D26-B3A8-DCB01B5AA66B} 94 | SecsCore 95 | 96 | 97 | {36A2C439-8771-4F98-B33F-390E6D7821A2} 98 | SmlHelper 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | False 107 | .NET Framework 3.5 SP1 Client Profile 108 | false 109 | 110 | 111 | False 112 | .NET Framework 3.5 SP1 113 | true 114 | 115 | 116 | False 117 | Windows Installer 3.1 118 | true 119 | 120 | 121 | 122 | 129 | -------------------------------------------------------------------------------- /SecsDevice/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | test 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /SmlHelper/Helper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Globalization; 5 | using System.IO; 6 | using System.Collections.Concurrent; 7 | using static Secs4Net.Item; 8 | using System.Threading.Tasks; 9 | using System.Threading; 10 | using System.Reactive.Linq; 11 | 12 | namespace Secs4Net { 13 | public static class SecsMessageExtenstion { 14 | const int SmlIndent = 2; 15 | 16 | public static string ToSML(this SecsMessage msg) { 17 | using (var sw = new StringWriter()) { 18 | msg.WriteTo(sw); 19 | return sw.ToString(); 20 | } 21 | } 22 | 23 | public static void WriteTo(this SecsMessage msg, TextWriter writer) { 24 | writer.WriteLine(msg.ToString()); 25 | Write(writer, msg.SecsItem, SmlIndent); 26 | writer.Write('.'); 27 | } 28 | 29 | static void Write(TextWriter writer, Item item, int indent) { 30 | if (item == null) return; 31 | var indentStr = new string(' ', indent); 32 | writer.Write(indentStr); 33 | writer.Write('<'); 34 | writer.Write(item.Format.ToSML()); 35 | writer.Write(" ["); 36 | writer.Write(item.Count); 37 | writer.Write("] "); 38 | switch (item.Format) { 39 | case SecsFormat.List: 40 | writer.WriteLine(); 41 | var items = item.Items; 42 | int count = items.Count; 43 | for (int i = 0; i < count; i++) 44 | Write(writer, items[i], indent + SmlIndent); 45 | writer.Write(indentStr); 46 | break; 47 | case SecsFormat.ASCII: 48 | case SecsFormat.JIS8: 49 | writer.Write('\''); 50 | writer.Write(item.ToString()); 51 | writer.Write('\''); 52 | break; 53 | default: 54 | writer.Write(item.ToString()); 55 | break; 56 | } 57 | writer.WriteLine('>'); 58 | } 59 | 60 | public static string ToSML(this SecsFormat format) { 61 | switch (format) { 62 | case SecsFormat.List: return "L"; 63 | case SecsFormat.Binary: return "B"; 64 | case SecsFormat.Boolean: return "Boolean"; 65 | case SecsFormat.ASCII: return "A"; 66 | case SecsFormat.JIS8: return "J"; 67 | case SecsFormat.I8: return "I8"; 68 | case SecsFormat.I1: return "I1"; 69 | case SecsFormat.I2: return "I2"; 70 | case SecsFormat.I4: return "I4"; 71 | case SecsFormat.F8: return "F8"; 72 | case SecsFormat.F4: return "F4"; 73 | case SecsFormat.U8: return "U8"; 74 | case SecsFormat.U1: return "U1"; 75 | case SecsFormat.U2: return "U2"; 76 | case SecsFormat.U4: return "U4"; 77 | default: throw new ArgumentException("invalid format", nameof(format)); 78 | } 79 | } 80 | 81 | public static SecsMessage ToSecsMessage(this string str) { 82 | using (var sr = new StringReader(str)) 83 | return sr.ToSecsMessage(); 84 | } 85 | 86 | public static IObservable ParseSmlFile(string filename) => 87 | Observable.Create(async io => 88 | { 89 | using (var reader = new FileInfo(filename).OpenText()) 90 | { 91 | while (!reader.EndOfStream) 92 | { 93 | try 94 | { 95 | io.OnNext(await reader.ToSecsMessageAsync()); 96 | } 97 | catch (Exception ex) 98 | { 99 | io.OnError(new Exception("SML parsing error before:\n" + reader.ReadToEnd(), ex)); 100 | } 101 | } 102 | io.OnCompleted(); 103 | } 104 | return () => { }; 105 | }); 106 | 107 | public static async Task ToSecsMessageAsync(this TextReader sr) 108 | { 109 | string line = await sr.ReadLineAsync(); 110 | #region Parse First Line 111 | int i = line.IndexOf(':'); 112 | 113 | var name = line.Substring(0, i); 114 | 115 | i = line.IndexOf("'S", i + 1) + 2; 116 | int j = line.IndexOf('F', i); 117 | var s = byte.Parse(line.Substring(i, j - i)); 118 | 119 | i = line.IndexOf('\'', j); 120 | var f = byte.Parse(line.Substring(j + 1, i - (j + 1))); 121 | 122 | var replyExpected = line.IndexOf('W', i) != -1; 123 | #endregion 124 | Item rootItem = null; 125 | var stack = new Stack>(); 126 | while ((line = await sr.ReadLineAsync()) != null) 127 | { 128 | line = line.TrimStart(); 129 | if (line[0] == '>') 130 | { 131 | var itemList = stack.Pop(); 132 | var item = itemList.Count > 0 ? Item.L(itemList) : Item.L(); 133 | if (stack.Count > 0) 134 | stack.Peek().Add(item); 135 | else 136 | rootItem = item; 137 | continue; 138 | } 139 | if (line[0] == '.') break; 140 | 141 | #region ()); 150 | continue; 151 | } 152 | else 153 | { 154 | int index_Size_R = line.IndexOf(']', index_Size_L); //Debug.Assert(index_Size_R != -1); 155 | int index_Item_R = line.LastIndexOf('>'); //Debug.Assert(index_Item_R != -1); 156 | string valueStr = line.Substring(index_Size_R + 1, index_Item_R - index_Size_R - 1); 157 | var item = Create(format, valueStr); 158 | if (stack.Count > 0) 159 | stack.Peek().Add(item); 160 | else 161 | rootItem = item; 162 | } 163 | #endregion 164 | } 165 | 166 | return new SecsMessage(s, f, replyExpected, name, rootItem); 167 | } 168 | 169 | public static SecsMessage ToSecsMessage(this TextReader sr) { 170 | string line = sr.ReadLine(); 171 | #region Parse First Line 172 | int i = line.IndexOf(':'); 173 | 174 | var name = line.Substring(0, i); 175 | 176 | i = line.IndexOf("'S", i + 1) + 2; 177 | int j = line.IndexOf('F', i); 178 | var s = byte.Parse(line.Substring(i, j - i)); 179 | 180 | i = line.IndexOf('\'', j); 181 | var f = byte.Parse(line.Substring(j + 1, i - (j + 1))); 182 | 183 | var replyExpected = line.IndexOf('W', i) != -1; 184 | #endregion 185 | Item rootItem = null; 186 | var stack = new Stack>(); 187 | while ((line = sr.ReadLine()) != null) { 188 | line = line.TrimStart(); 189 | if (line[0] == '>') { 190 | var itemList = stack.Pop(); 191 | var item = itemList.Count > 0 ? Item.L(itemList) : Item.L(); 192 | if (stack.Count > 0) 193 | stack.Peek().Add(item); 194 | else 195 | rootItem = item; 196 | continue; 197 | } 198 | if (line[0] == '.') break; 199 | 200 | #region ()); 208 | continue; 209 | } else { 210 | int index_Size_R = line.IndexOf(']', index_Size_L); //Debug.Assert(index_Size_R != -1); 211 | int index_Item_R = line.LastIndexOf('>'); //Debug.Assert(index_Item_R != -1); 212 | string valueStr = line.Substring(index_Size_R + 1, index_Item_R - index_Size_R - 1); 213 | var item = Create(format, valueStr); 214 | if (stack.Count > 0) 215 | stack.Peek().Add(item); 216 | else 217 | rootItem = item; 218 | } 219 | #endregion 220 | } 221 | 222 | return new SecsMessage(s, f, replyExpected, name, rootItem); 223 | } 224 | 225 | static readonly Func SmlParser_A = CreateSmlParser(A, A); 226 | static readonly Func SmlParser_J = CreateSmlParser(J, J); 227 | static readonly Func SmlParser_Boolean = CreateSmlParser(Boolean, Boolean, bool.Parse); 228 | static readonly Func SmlParser_B = CreateSmlParser(B, B, HexStringToByte); 229 | static readonly Func SmlParser_I1 = CreateSmlParser(I1, I1, sbyte.Parse); 230 | static readonly Func SmlParser_I2 = CreateSmlParser(I2, I2, short.Parse); 231 | static readonly Func SmlParser_I4 = CreateSmlParser(I4, I4, int.Parse); 232 | static readonly Func SmlParser_I8 = CreateSmlParser(I8, I8, long.Parse); 233 | static readonly Func SmlParser_U1 = CreateSmlParser(U1, U1, byte.Parse); 234 | static readonly Func SmlParser_U2 = CreateSmlParser(U2, U2, ushort.Parse); 235 | static readonly Func SmlParser_U4 = CreateSmlParser(U4, U4, uint.Parse); 236 | static readonly Func SmlParser_U8 = CreateSmlParser(U8, U8, ulong.Parse); 237 | static readonly Func SmlParser_F4 = CreateSmlParser(F4, F4, float.Parse); 238 | static readonly Func SmlParser_F8 = CreateSmlParser(F8, F8, double.Parse); 239 | static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); 240 | 241 | static byte HexStringToByte(string str) => str.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToByte(str, 16) : byte.Parse(str); 242 | 243 | static Func CreateSmlParser(Func itemCreator, Func emptyCreator) => valueStr => 244 | Cache.GetOrAdd(valueStr, str => 245 | { 246 | str = str.TrimStart(' ', '\'', '"').TrimEnd(' ', '\'', '"'); 247 | return string.IsNullOrEmpty(str) ? 248 | emptyCreator() : 249 | itemCreator(str); 250 | }); 251 | 252 | static Func CreateSmlParser(Func creator, Func emptyCreator, Converter converter) where T : struct => valueStr => 253 | Cache.GetOrAdd(valueStr, str => 254 | { 255 | var valueStrs = str.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); 256 | return (valueStrs.Length == 0) ? 257 | emptyCreator() : 258 | creator(Array.ConvertAll(valueStrs, converter)); 259 | }); 260 | 261 | public static Item Create(this string format, string smlValue) { 262 | switch (format) { 263 | case "A": return SmlParser_A(smlValue); 264 | case "JIS8": 265 | case "J": return SmlParser_J(smlValue); 266 | case "Bool": 267 | case "Boolean": return SmlParser_Boolean(smlValue); 268 | case "Binary": 269 | case "B": return SmlParser_B(smlValue); 270 | case "I1": return SmlParser_I1(smlValue); 271 | case "I2": return SmlParser_I2(smlValue); 272 | case "I4": return SmlParser_I4(smlValue); 273 | case "I8": return SmlParser_I8(smlValue); 274 | case "U1": return SmlParser_U1(smlValue); 275 | case "U2": return SmlParser_U2(smlValue); 276 | case "U4": return SmlParser_U4(smlValue); 277 | case "U8": return SmlParser_U8(smlValue); 278 | case "F4": return SmlParser_F4(smlValue); 279 | case "F8": return SmlParser_F8(smlValue); 280 | case "L": throw new SecsException("Please use Item.L(...) to create list item."); 281 | default: throw new SecsException("Unknown SML format :" + format); 282 | } 283 | } 284 | public static Item Create(this SecsFormat format, string smlValue) => Create(format.ToSML(), smlValue); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /SmlHelper/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SmlHelper")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("mkjeff@gmail.com")] 12 | [assembly: AssemblyProduct("SmlHelper")] 13 | [assembly: AssemblyCopyright("Copyright © dogmouse 2010")] 14 | [assembly: AssemblyTrademark("Dogmouse")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("1abd74ad-5590-47c1-9402-4208e8d1e85c")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SmlHelper/SmlHelper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {36A2C439-8771-4F98-B33F-390E6D7821A2} 9 | Library 10 | Properties 11 | SmlHelper 12 | SmlHelper 13 | v4.6 14 | 512 15 | 16 | 17 | 18 | 19 | 20 | 21 | 3.5 22 | 23 | publish\ 24 | true 25 | Disk 26 | false 27 | Foreground 28 | 7 29 | Days 30 | false 31 | false 32 | true 33 | 0 34 | 1.0.0.%2a 35 | false 36 | false 37 | true 38 | 39 | 40 | true 41 | full 42 | false 43 | bin\Debug\ 44 | DEBUG;TRACE 45 | prompt 46 | 4 47 | MinimumRecommendedRules.ruleset 48 | false 49 | 50 | 51 | pdbonly 52 | true 53 | bin\Release\ 54 | TRACE 55 | prompt 56 | 4 57 | AllRules.ruleset 58 | false 59 | 60 | 61 | 62 | 63 | ..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll 64 | True 65 | 66 | 67 | ..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll 68 | True 69 | 70 | 71 | ..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll 72 | True 73 | 74 | 75 | ..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll 76 | True 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | {3C8C67B8-3746-4D26-B3A8-DCB01B5AA66B} 86 | SecsCore 87 | 88 | 89 | 90 | 91 | False 92 | .NET Framework 3.5 SP1 Client Profile 93 | false 94 | 95 | 96 | False 97 | .NET Framework 3.5 SP1 98 | true 99 | 100 | 101 | False 102 | Windows Installer 3.1 103 | true 104 | 105 | 106 | 107 | 108 | 109 | 110 | 117 | -------------------------------------------------------------------------------- /SmlHelper/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WPFVisuallizer/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WPFVisuallizer/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace SecsMessageVisuallizer 4 | { 5 | /// 6 | /// Interaction logic for App.xaml 7 | /// 8 | public partial class App : Application { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /WPFVisuallizer/Properties/.svn/text-base/Resources.resx.svn-base: -------------------------------------------------------------------------------- 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 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /WPFVisuallizer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("SecsMessageVisuallizer")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("Microsoft")] 14 | [assembly: AssemblyProduct("SecsMessageVisuallizer")] 15 | [assembly: AssemblyCopyright("Copyright © Microsoft 2009")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /WPFVisuallizer/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace SecsMessageVisuallizer.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.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 | /// Returns the cached ResourceManager instance used by this class. 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("SecsMessageVisuallizer.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 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 | -------------------------------------------------------------------------------- /WPFVisuallizer/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 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /WPFVisuallizer/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace SecsMessageVisuallizer.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WPFVisuallizer/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /WPFVisuallizer/ReplyExpectedToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Data; 3 | 4 | namespace SecsMessageVisuallizer 5 | { 6 | [ValueConversion(typeof(bool), typeof(string))] 7 | public class ReplyExpectedToStringConverter : IValueConverter 8 | { 9 | #region IValueConverter Members 10 | 11 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) => ((bool)value) ? "'W'" : string.Empty; 12 | 13 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | 18 | #endregion 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /WPFVisuallizer/SecsMessageList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Collections.ObjectModel; 4 | 5 | namespace Secs4Net 6 | { 7 | public sealed class SecsMessageList : ObservableCollection { 8 | public SecsMessageList(string smlFile) : base() { } 9 | 10 | public SecsMessage this[byte s, byte f, string name] => this[s, f].FirstOrDefault(m => m.Name == name); 11 | 12 | public IEnumerable this[byte s, byte f] => this.Where(m => m.S == s && m.F == f); 13 | 14 | public SecsMessage this[string name] => this.FirstOrDefault(m => m.Name == name); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /WPFVisuallizer/SecsMessageTreeView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace SecsMessageVisuallizer 17 | { 18 | /// 19 | /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file. 20 | /// 21 | /// Step 1a) Using this custom control in a XAML file that exists in the current project. 22 | /// Add this XmlNamespace attribute to the root element of the markup file where it is 23 | /// to be used: 24 | /// 25 | /// xmlns:MyNamespace="clr-namespace:SecsMessageVisuallizer" 26 | /// 27 | /// 28 | /// Step 1b) Using this custom control in a XAML file that exists in a different project. 29 | /// Add this XmlNamespace attribute to the root element of the markup file where it is 30 | /// to be used: 31 | /// 32 | /// xmlns:MyNamespace="clr-namespace:SecsMessageVisuallizer;assembly=SecsMessageVisuallizer" 33 | /// 34 | /// You will also need to add a project reference from the project where the XAML file lives 35 | /// to this project and Rebuild to avoid compilation errors: 36 | /// 37 | /// Right click on the target project in the Solution Explorer and 38 | /// "Add Reference"->"Projects"->[Browse to and select this project] 39 | /// 40 | /// 41 | /// Step 2) 42 | /// Go ahead and use your control in the XAML file. 43 | /// 44 | /// 45 | /// 46 | /// 47 | public class SecsMessageTreeView : TreeView 48 | { 49 | static SecsMessageTreeView() 50 | { 51 | DefaultStyleKeyProperty.OverrideMetadata(typeof(SecsMessageTreeView), new FrameworkPropertyMetadata(typeof(SecsMessageTreeView))); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /WPFVisuallizer/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 19 | 20 | -------------------------------------------------------------------------------- /WPFVisuallizer/VeiwModelToTreeViewItemConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Data; 3 | using System.Windows.Controls; 4 | 5 | namespace SecsMessageVisuallizer 6 | { 7 | class VeiwModelToTreeViewItemConverter:IValueConverter { 8 | #region IValueConverter Members 9 | 10 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { 11 | TreeView tv = value as TreeView; 12 | if (tv != null) { 13 | TreeViewItem tvItem =tv.ItemContainerGenerator.ContainerFromItem(tv.SelectedItem) as TreeViewItem; 14 | if (tvItem != null) 15 | return tvItem.IsFocused; 16 | } 17 | return false; 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { 21 | throw new NotImplementedException(); 22 | } 23 | 24 | #endregion 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WPFVisuallizer/ViewModel/SecsItemViewModel.cs: -------------------------------------------------------------------------------- 1 | using Secs4Net; 2 | 3 | namespace SecsMessageVisuallizer.ViewModel { 4 | public class SecsItemViewModel : TreeViewItemViewModel { 5 | readonly Item _secsItem; 6 | public SecsItemViewModel(Item item, SecsMessageViewModel secsMsg) 7 | : base(secsMsg, item.Format == SecsFormat.List && item.Items.Count > 0) { 8 | _secsItem = item; 9 | } 10 | 11 | public SecsItemViewModel(Item item, SecsItemViewModel parentItem) 12 | : base(parentItem, item.Format == SecsFormat.List && item.Items.Count > 0) { 13 | _secsItem = item; 14 | } 15 | 16 | protected override void LoadChildren() { 17 | foreach (Item item in _secsItem.Items) { 18 | base.Children.Add(new SecsItemViewModel(item, this)); 19 | } 20 | } 21 | 22 | public string Name { 23 | get { 24 | if(_secsItem.Format== SecsFormat.List) 25 | return string.Format("{0} [{1}]",_secsItem.Format.ToSML(),_secsItem.Items.Count); 26 | return string.Format("{0} [{1}] {2}", _secsItem.Format.ToSML(),_secsItem.Count,_secsItem.ToString()); 27 | } 28 | } 29 | 30 | public override string ToString() => _secsItem.ToString(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /WPFVisuallizer/ViewModel/SecsMessageCollectionViewModel.cs: -------------------------------------------------------------------------------- 1 |  2 | using Secs4Net; 3 | using System.Collections.Generic; 4 | 5 | namespace SecsMessageVisuallizer.ViewModel { 6 | public class SecsMessageCollectionViewModel { 7 | readonly SecsMessageList _msgList; 8 | 9 | public SecsMessageCollectionViewModel(SecsMessageList secsMsgList) 10 | { 11 | _msgList = secsMsgList; 12 | } 13 | 14 | public IEnumerable SecsMessages 15 | { 16 | get 17 | { 18 | foreach (SecsMessage msg in _msgList) 19 | yield return new SecsMessageViewModel(msg); 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /WPFVisuallizer/ViewModel/SecsMessageViewModel.cs: -------------------------------------------------------------------------------- 1 | using Secs4Net; 2 | 3 | namespace SecsMessageVisuallizer.ViewModel 4 | { 5 | public class SecsMessageViewModel : TreeViewItemViewModel { 6 | readonly SecsMessage _secsMsg; 7 | public SecsMessageViewModel(SecsMessage secsMsg) 8 | : base(null, false) { 9 | _secsMsg = secsMsg; 10 | if (secsMsg.SecsItem != null) 11 | base.Children.Add(new SecsItemViewModel(secsMsg.SecsItem, this)); 12 | } 13 | 14 | public byte StreamNumber => _secsMsg.S; 15 | public byte FunctionNumber => _secsMsg.F; 16 | public string Name { 17 | get { return _secsMsg.Name; } 18 | set { 19 | if (value != null && value != _secsMsg.Name) { 20 | _secsMsg.Name = value; 21 | base.OnPropertyChanged(); 22 | } 23 | } 24 | } 25 | public bool ReplyExpected => _secsMsg.ReplyExpected; 26 | 27 | public override string ToString() => _secsMsg.ToSML(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /WPFVisuallizer/ViewModel/TreeViewItemViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.ComponentModel; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace SecsMessageVisuallizer.ViewModel 7 | { 8 | public class TreeViewItemViewModel : INotifyPropertyChanged { 9 | #region Data 10 | 11 | static readonly TreeViewItemViewModel DummyChild = new TreeViewItemViewModel(); 12 | 13 | readonly ObservableCollection _children; 14 | readonly TreeViewItemViewModel _parent; 15 | 16 | bool _isExpanded; 17 | bool _isSelected; 18 | 19 | #endregion // Data 20 | 21 | #region Constructors 22 | 23 | protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren) { 24 | _parent = parent; 25 | 26 | _children = new ObservableCollection(); 27 | 28 | if (lazyLoadChildren) 29 | _children.Add(DummyChild); 30 | } 31 | 32 | // This is used to create the DummyChild instance. 33 | private TreeViewItemViewModel() { 34 | } 35 | 36 | #endregion // Constructors 37 | 38 | #region Presentation Members 39 | 40 | #region Children 41 | 42 | /// 43 | /// Returns the logical child items of this object. 44 | /// 45 | public ObservableCollection Children => _children; 46 | 47 | #endregion // Children 48 | 49 | #region HasLoadedChildren 50 | 51 | /// 52 | /// Returns true if this object's Children have not yet been populated. 53 | /// 54 | public bool HasDummyChild => Children.Count == 1 && Children[0] == DummyChild; 55 | 56 | #endregion // HasLoadedChildren 57 | 58 | #region IsExpanded 59 | 60 | /// 61 | /// Gets/sets whether the TreeViewItem 62 | /// associated with this object is expanded. 63 | /// 64 | public bool IsExpanded { 65 | get { return _isExpanded; } 66 | set { 67 | if (value != _isExpanded) { 68 | _isExpanded = value; 69 | OnPropertyChanged(); 70 | } 71 | 72 | // Expand all the way up to the root. 73 | if (_isExpanded && _parent != null) 74 | _parent.IsExpanded = true; 75 | 76 | // Lazy load the child items, if necessary. 77 | if (HasDummyChild) { 78 | Children.Remove(DummyChild); 79 | LoadChildren(); 80 | } 81 | } 82 | } 83 | 84 | #endregion // IsExpanded 85 | 86 | #region IsSelected 87 | 88 | /// 89 | /// Gets/sets whether the TreeViewItem 90 | /// associated with this object is selected. 91 | /// 92 | public bool IsSelected { 93 | get { return _isSelected; } 94 | set { SetField(ref _isSelected, value); } 95 | } 96 | 97 | #endregion // IsSelected 98 | 99 | #region LoadChildren 100 | 101 | /// 102 | /// Invoked when the child items need to be loaded on demand. 103 | /// Subclasses can override this to populate the Children collection. 104 | /// 105 | protected virtual void LoadChildren() { 106 | } 107 | 108 | #endregion // LoadChildren 109 | 110 | #region Parent 111 | 112 | public TreeViewItemViewModel Parent => _parent; 113 | 114 | #endregion // Parent 115 | 116 | #endregion // Presentation Members 117 | 118 | #region INotifyPropertyChanged Members 119 | 120 | public event PropertyChangedEventHandler PropertyChanged; 121 | protected virtual void OnPropertyChanged([CallerMemberName]string propertyName=null) 122 | { 123 | PropertyChangedEventHandler handler = PropertyChanged; 124 | if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); 125 | } 126 | protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null) 127 | { 128 | if (EqualityComparer.Default.Equals(field, value)) 129 | return false; 130 | field = value; 131 | OnPropertyChanged(propertyName); 132 | return true; 133 | } 134 | #endregion // INotifyPropertyChanged Members 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /WPFVisuallizer/WPFVisuallizer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {775B5524-5F1C-412E-9F89-D19611AAE026} 9 | WinExe 10 | Properties 11 | SecsMessageVisuallizer 12 | SecsMessageVisuallizer 13 | v4.6 14 | 512 15 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 16 | 4 17 | 18 | 19 | 20 | 21 | 3.5 22 | publish\ 23 | true 24 | Disk 25 | false 26 | Foreground 27 | 7 28 | Days 29 | false 30 | false 31 | true 32 | 0 33 | 1.0.0.%2a 34 | false 35 | false 36 | true 37 | 38 | 39 | 40 | true 41 | full 42 | false 43 | bin\Debug\ 44 | DEBUG;TRACE 45 | prompt 46 | 4 47 | false 48 | 49 | 50 | pdbonly 51 | true 52 | bin\Release\ 53 | TRACE 54 | prompt 55 | 4 56 | false 57 | 58 | 59 | 60 | 61 | 3.5 62 | 63 | 64 | 65 | 3.5 66 | 67 | 68 | 3.5 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | MSBuild:Compile 79 | Designer 80 | 81 | 82 | MSBuild:Compile 83 | Designer 84 | 85 | 86 | MSBuild:Compile 87 | Designer 88 | 89 | 90 | App.xaml 91 | Code 92 | 93 | 94 | 95 | 96 | Window1.xaml 97 | Code 98 | 99 | 100 | 101 | 102 | Code 103 | 104 | 105 | True 106 | True 107 | Resources.resx 108 | 109 | 110 | True 111 | Settings.settings 112 | True 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | ResXFileCodeGenerator 122 | Resources.Designer.cs 123 | 124 | 125 | 126 | SettingsSingleFileGenerator 127 | Settings.Designer.cs 128 | 129 | 130 | 131 | 132 | 133 | False 134 | .NET Framework 3.5 SP1 135 | true 136 | 137 | 138 | 139 | 140 | {3c8c67b8-3746-4d26-b3a8-dcb01b5aa66b} 141 | SecsCore 142 | 143 | 144 | {36a2c439-8771-4f98-b33f-390e6d7821a2} 145 | SmlHelper 146 | 147 | 148 | 149 | 156 | -------------------------------------------------------------------------------- /WPFVisuallizer/Window1.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 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 | -------------------------------------------------------------------------------- /WPFVisuallizer/Window1.xaml.cs: -------------------------------------------------------------------------------- 1 | using Secs4Net; 2 | using SecsMessageVisuallizer.ViewModel; 3 | using System.Windows; 4 | 5 | namespace SecsMessageVisuallizer { 6 | /// 7 | /// Interaction logic for Window1.xaml 8 | /// 9 | public partial class Window1 : Window { 10 | public Window1() { 11 | InitializeComponent(); 12 | base.DataContext = new SecsMessageCollectionViewModel(new SecsMessageList("common.sml")); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /WPFVisuallizer/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /secs4net.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.22823.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecsCore", "Secs4Net\SecsCore.csproj", "{3C8C67B8-3746-4D26-B3A8-DCB01B5AA66B}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecsDevice", "SecsDevice\SecsDevice.csproj", "{8BF9676F-4514-4C43-A33A-23B9A098DC63}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmlHelper", "SmlHelper\SmlHelper.csproj", "{36A2C439-8771-4F98-B33F-390E6D7821A2}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPFVisuallizer", "WPFVisuallizer\WPFVisuallizer.csproj", "{775B5524-5F1C-412E-9F89-D19611AAE026}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Debug|x86 = Debug|x86 18 | Release|Any CPU = Release|Any CPU 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {3C8C67B8-3746-4D26-B3A8-DCB01B5AA66B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {3C8C67B8-3746-4D26-B3A8-DCB01B5AA66B}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {3C8C67B8-3746-4D26-B3A8-DCB01B5AA66B}.Debug|x86.ActiveCfg = Debug|x86 25 | {3C8C67B8-3746-4D26-B3A8-DCB01B5AA66B}.Debug|x86.Build.0 = Debug|x86 26 | {3C8C67B8-3746-4D26-B3A8-DCB01B5AA66B}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {3C8C67B8-3746-4D26-B3A8-DCB01B5AA66B}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {3C8C67B8-3746-4D26-B3A8-DCB01B5AA66B}.Release|x86.ActiveCfg = Release|x86 29 | {3C8C67B8-3746-4D26-B3A8-DCB01B5AA66B}.Release|x86.Build.0 = Release|x86 30 | {8BF9676F-4514-4C43-A33A-23B9A098DC63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {8BF9676F-4514-4C43-A33A-23B9A098DC63}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {8BF9676F-4514-4C43-A33A-23B9A098DC63}.Debug|x86.ActiveCfg = Debug|Any CPU 33 | {8BF9676F-4514-4C43-A33A-23B9A098DC63}.Debug|x86.Build.0 = Debug|Any CPU 34 | {8BF9676F-4514-4C43-A33A-23B9A098DC63}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {8BF9676F-4514-4C43-A33A-23B9A098DC63}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {8BF9676F-4514-4C43-A33A-23B9A098DC63}.Release|x86.ActiveCfg = Release|Any CPU 37 | {8BF9676F-4514-4C43-A33A-23B9A098DC63}.Release|x86.Build.0 = Release|Any CPU 38 | {36A2C439-8771-4F98-B33F-390E6D7821A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {36A2C439-8771-4F98-B33F-390E6D7821A2}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {36A2C439-8771-4F98-B33F-390E6D7821A2}.Debug|x86.ActiveCfg = Debug|Any CPU 41 | {36A2C439-8771-4F98-B33F-390E6D7821A2}.Debug|x86.Build.0 = Debug|Any CPU 42 | {36A2C439-8771-4F98-B33F-390E6D7821A2}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {36A2C439-8771-4F98-B33F-390E6D7821A2}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {36A2C439-8771-4F98-B33F-390E6D7821A2}.Release|x86.ActiveCfg = Release|Any CPU 45 | {36A2C439-8771-4F98-B33F-390E6D7821A2}.Release|x86.Build.0 = Release|Any CPU 46 | {775B5524-5F1C-412E-9F89-D19611AAE026}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {775B5524-5F1C-412E-9F89-D19611AAE026}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {775B5524-5F1C-412E-9F89-D19611AAE026}.Debug|x86.ActiveCfg = Debug|Any CPU 49 | {775B5524-5F1C-412E-9F89-D19611AAE026}.Debug|x86.Build.0 = Debug|Any CPU 50 | {775B5524-5F1C-412E-9F89-D19611AAE026}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {775B5524-5F1C-412E-9F89-D19611AAE026}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {775B5524-5F1C-412E-9F89-D19611AAE026}.Release|x86.ActiveCfg = Release|Any CPU 53 | {775B5524-5F1C-412E-9F89-D19611AAE026}.Release|x86.Build.0 = Release|Any CPU 54 | EndGlobalSection 55 | GlobalSection(SolutionProperties) = preSolution 56 | HideSolutionNode = FALSE 57 | EndGlobalSection 58 | EndGlobal 59 | --------------------------------------------------------------------------------