├── .editorconfig
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── dotnet.yml
├── .gitignore
├── .idea
└── .idea.secs4net
│ └── .idea
│ ├── .gitignore
│ ├── encodings.xml
│ ├── indexLayout.xml
│ └── vcs.xml
├── .nuget
└── NuGet.Config
├── Directory.Build.props
├── LICENSE
├── LINQPad.md
├── README.md
├── _config.yml
├── common.json
├── common.sml
├── common
├── Channel.Extensions.cs
├── Encoding.Extensions.cs
├── EnumerableExtensions.cs
├── ItemAssertions.cs
├── RuntimeHelpers.cs
├── SecsMessageAssertions.cs
└── Usings.cs
├── global.json
├── samples
├── DeviceWorkerService
│ ├── DeviceLogger.cs
│ ├── DeviceWorker.cs
│ ├── DeviceWorkerService.csproj
│ ├── Program.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── ServiceProvider.cs
│ ├── appsettings.Development.json
│ └── appsettings.json
├── SecsDevice
│ ├── Form1.Designer.cs
│ ├── Form1.cs
│ ├── Form1.resx
│ ├── Program.cs
│ ├── Properties
│ │ └── DataSources
│ │ │ └── RecvMessage.datasource
│ ├── ReadMe.txt
│ └── SecsDevice.csproj
└── WpfVisualizer
│ ├── App.xaml
│ ├── App.xaml.cs
│ ├── ReplyExpectedToStringConverter.cs
│ ├── SecsMessageList.cs
│ ├── SecsMessageTreeView.cs
│ ├── Themes
│ └── Generic.xaml
│ ├── VeiwModelToTreeViewItemConverter.cs
│ ├── ViewModel
│ ├── SecsItemViewModel.cs
│ ├── SecsMessageCollectionViewModel.cs
│ ├── SecsMessageViewModel.cs
│ └── TreeViewItemViewModel.cs
│ ├── Window1.xaml
│ ├── Window1.xaml.cs
│ ├── WpfVisualizer.csproj
│ └── common.json
├── secs4net.sln
├── src
├── Secs4Net.Json
│ ├── ItemJsonConverter.cs
│ ├── JsonReader.cs
│ ├── JsonWriter.cs
│ └── Secs4Net.Json.csproj
├── Secs4Net.Sml
│ ├── MemoryExtensions.cs
│ ├── Secs4Net.Sml.csproj
│ ├── SmlReader.cs
│ └── SmlWriter.cs
└── Secs4Net
│ ├── ConnectionState.cs
│ ├── Extensions
│ ├── ExtensionHelper.cs
│ └── ReverseEndiannessHelper.cs
│ ├── HsmsConnection.cs
│ ├── ISecsConnection.cs
│ ├── ISecsGemLogger.cs
│ ├── Item.Decode.cs
│ ├── Item.Encode.cs
│ ├── Item.Factory.cs
│ ├── Item.List.cs
│ ├── Item.Memory.cs
│ ├── Item.MemoryOwner.cs
│ ├── Item.String.cs
│ ├── Item.cs
│ ├── MessageHeader.cs
│ ├── MessageIdGenerator.cs
│ ├── MessageType.cs
│ ├── PipeDecoder.cs
│ ├── PrimaryMessageWrapper.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Secs4Net.csproj
│ ├── SecsException.cs
│ ├── SecsFormat.cs
│ ├── SecsGem.cs
│ ├── SecsGemOptions.cs
│ ├── SecsMessage.cs
│ └── Usings.cs
└── test
├── Benchmarks
├── BenchmarkConfig.cs
├── BenchmarkDotNet.Artifacts
│ └── results
│ │ ├── Benchmarks.ComplexItemEncodeDecode-report-github.md
│ │ ├── Benchmarks.ItemEncodeDecode-report-github.md
│ │ ├── Benchmarks.JsonSerialization-report-github.md
│ │ ├── Benchmarks.PipeDecoding-report-github.md
│ │ ├── Benchmarks.RequestResponse-report-github.md
│ │ ├── Benchmarks.ReverseEndianness-report-github.md
│ │ └── Benchmarks.SmlSerialization-report-github.md
├── Benchmarks.csproj
├── ComplexItemEncodeDecode.cs
├── Directory.Build.props
├── ItemEncodeDecode.cs
├── JsonSerialization.cs
├── PipeDecoding.cs
├── Program.cs
├── RequestResponse.cs
├── ReverseEndianness.cs
└── SmlSerialization.cs
├── Secs4Net.Json.UnitTests
├── Json.UnitTest.cs
└── Secs4Net.Json.UnitTests.csproj
├── Secs4Net.Sml.UnitTests
├── Secs4Net.Sml.UnitTests.csproj
└── Sml.UnitTest.cs
└── Secs4Net.UnitTests
├── ChunkedMemory.cs
├── Item.UnitTest.cs
├── MessageHeader.UnitTests.cs
├── PipeConnection.cs
├── PipeDecoder.UnitTests.cs
├── Secs4Net.UnitTests.csproj
└── SecsGem.UnitTests.cs
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **To Reproduce**
11 | Steps to reproduce the behavior:
12 | 1. Go to '...'
13 | 2. Click on '....'
14 | 3. Scroll down to '....'
15 | 4. See error
16 |
17 | **Expected behavior**
18 | A clear and concise description of what you expected to happen.
19 |
20 | **Screenshots**
21 | If applicable, add screenshots to help explain your problem.
22 |
23 | **Desktop (please complete the following information):**
24 | - OS: [e.g. iOS]
25 | - Browser [e.g. chrome, safari]
26 | - Version [e.g. 22]
27 |
28 | **Smartphone (please complete the following information):**
29 | - Device: [e.g. iPhone6]
30 | - OS: [e.g. iOS8.1]
31 | - Browser [e.g. stock browser, safari]
32 | - Version [e.g. 22]
33 |
34 | **Additional context**
35 | Add any other context about the problem here.
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a .NET project
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
3 |
4 | name: .NET
5 |
6 | on:
7 | push:
8 | branches: [ "base" ]
9 | pull_request:
10 | branches: [ "base" ]
11 |
12 | jobs:
13 | build:
14 | permissions:
15 | contents: read
16 |
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - uses: actions/checkout@v3
21 | - name: Setup .NET
22 | uses: actions/setup-dotnet@v4
23 | with:
24 | dotnet-version: 8.0.x
25 | - name: Restore dependencies
26 | run: dotnet restore
27 | - name: Build
28 | run: dotnet build --no-restore
29 | - name: Test
30 | run: dotnet test -f net8.0 --no-build --verbosity normal --collect "Code Coverage"
31 | - name: Install dotnet-coverage
32 | run: dotnet tool install -g dotnet-coverage
33 | - name: Convert .coverage report to cobertura
34 | run: dotnet-coverage merge $GITHUB_WORKSPACE/test/**/TestResults/**/*.coverage -f cobertura -o $GITHUB_WORKSPACE/report.cobertura.xml
35 | - name: Upload coverage reports to Codecov
36 | uses: codecov/codecov-action@v3
37 | env:
38 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
39 | - name: Pack
40 | run: dotnet pack -c Release
41 | - name: Push
42 | run: dotnet nuget push "**/*.nupkg" --skip-duplicate -k ${{ secrets.NUGET }} -s https://api.nuget.org/v3/index.json
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 | .vs/
10 |
11 | # Build results
12 | [Dd]ebug/
13 | [Dd]ebugPublic/
14 | [Rr]elease/
15 | [Rr]eleases/
16 | x64/
17 | x86/
18 | build/
19 | bld/
20 | [Bb]in/
21 | [Oo]bj/
22 |
23 | # Roslyn cache directories
24 | *.ide/
25 |
26 | # MSTest test Results
27 | [Tt]est[Rr]esult*/
28 | [Bb]uild[Ll]og.*
29 |
30 | #NUNIT
31 | *.VisualState.xml
32 | TestResult.xml
33 |
34 | # Build Results of an ATL Project
35 | [Dd]ebugPS/
36 | [Rr]eleasePS/
37 | dlldata.c
38 |
39 | *_i.c
40 | *_p.c
41 | *_i.h
42 | *.ilk
43 | *.meta
44 | *.obj
45 | *.pch
46 | *.pdb
47 | *.pgc
48 | *.pgd
49 | *.rsp
50 | *.sbr
51 | *.tlb
52 | *.tli
53 | *.tlh
54 | *.tmp
55 | *.tmp_proj
56 | *.log
57 | *.vspscc
58 | *.vssscc
59 | .builds
60 | *.pidb
61 | *.svclog
62 | *.scc
63 |
64 | # Chutzpah Test files
65 | _Chutzpah*
66 |
67 | # Visual C++ cache files
68 | ipch/
69 | *.aps
70 | *.ncb
71 | *.opensdf
72 | *.sdf
73 | *.cachefile
74 |
75 | # Visual Studio profiler
76 | *.psess
77 | *.vsp
78 | *.vspx
79 |
80 | # TFS 2012 Local Workspace
81 | $tf/
82 |
83 | # Guidance Automation Toolkit
84 | *.gpState
85 |
86 | # ReSharper is a .NET coding add-in
87 | _ReSharper*/
88 | *.[Rr]e[Ss]harper
89 | *.DotSettings.user
90 |
91 | # JustCode is a .NET coding addin-in
92 | .JustCode
93 |
94 | # TeamCity is a build add-in
95 | _TeamCity*
96 |
97 | # DotCover is a Code Coverage Tool
98 | *.dotCover
99 |
100 | # NCrunch
101 | _NCrunch_*
102 | .*crunch*.local.xml
103 |
104 | # MightyMoose
105 | *.mm.*
106 | AutoTest.Net/
107 |
108 | # Web workbench (sass)
109 | .sass-cache/
110 |
111 | # Installshield output folder
112 | [Ee]xpress/
113 |
114 | # DocProject is a documentation generator add-in
115 | DocProject/buildhelp/
116 | DocProject/Help/*.HxT
117 | DocProject/Help/*.HxC
118 | DocProject/Help/*.hhc
119 | DocProject/Help/*.hhk
120 | DocProject/Help/*.hhp
121 | DocProject/Help/Html2
122 | DocProject/Help/html
123 |
124 | # Click-Once directory
125 | publish/
126 |
127 | # Publish Web Output
128 | *.[Pp]ublish.xml
129 | *.azurePubxml
130 | # TODO: Comment the next line if you want to checkin your web deploy settings
131 | # but database connection strings (with potential passwords) will be unencrypted
132 | *.pubxml
133 | *.publishproj
134 |
135 | # NuGet Packages
136 | *.nupkg
137 | # The packages folder can be ignored because of Package Restore
138 | **/packages/*
139 | # except build/, which is used as an MSBuild target.
140 | !**/packages/build/
141 | # If using the old MSBuild-Integrated Package Restore, uncomment this:
142 | #!**/packages/repositories.config
143 |
144 | # Windows Azure Build Output
145 | csx/
146 | *.build.csdef
147 |
148 | # Windows Store app package directory
149 | AppPackages/
150 |
151 | # Others
152 | sql/
153 | *.Cache
154 | ClientBin/
155 | [Ss]tyle[Cc]op.*
156 | ~$*
157 | *~
158 | *.dbmdl
159 | *.dbproj.schemaview
160 | *.pfx
161 | *.publishsettings
162 | node_modules/
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 | # =========================
188 | # Operating System Files
189 | # =========================
190 |
191 | # OSX
192 | # =========================
193 |
194 | .DS_Store
195 | .AppleDouble
196 | .LSOverride
197 |
198 | # Thumbnails
199 | ._*
200 |
201 | # Files that might appear on external disk
202 | .Spotlight-V100
203 | .Trashes
204 |
205 | # Directories potentially created on remote AFP share
206 | .AppleDB
207 | .AppleDesktop
208 | Network Trash Folder
209 | Temporary Items
210 | .apdisk
211 |
212 | # Windows
213 | # =========================
214 |
215 | # Windows image file caches
216 | Thumbs.db
217 | ehthumbs.db
218 |
219 | # Folder config file
220 | Desktop.ini
221 |
222 | # Recycle Bin used on file shares
223 | $RECYCLE.BIN/
224 |
225 | # Windows Installer files
226 | *.cab
227 | *.msi
228 | *.msm
229 | *.msp
230 |
231 | # Windows shortcuts
232 | *.lnk
233 | yarn.lock
234 | **/wwwroot/app/
235 | *.etl
236 |
--------------------------------------------------------------------------------
/.idea/.idea.secs4net/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Rider ignored files
5 | /modules.xml
6 | /contentModel.xml
7 | /projectSettingsUpdater.xml
8 | /.idea.secs4net.iml
9 | # Editor-based HTTP Client requests
10 | /httpRequests/
11 | # Datasource local storage ignored files
12 | /dataSources/
13 | /dataSources.local.xml
14 |
--------------------------------------------------------------------------------
/.idea/.idea.secs4net/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/.idea.secs4net/.idea/indexLayout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/.idea.secs4net/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.nuget/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2.4.4
4 | https://github.com/mkjeff/secs4net
5 | mkjeff
6 | MIT
7 | enable
8 | 12.0
9 | true
10 | true
11 | true
12 | true
13 | snupkg
14 | true
15 | true
16 | true
17 | Copyright © secs4net 2024
18 | https://mkjeff.github.io/secs4net/
19 |
20 |
21 |
22 |
23 | all
24 | runtime; build; native; contentfiles; analyzers; buildtransitive
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Hsin-chang He
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LINQPad.md:
--------------------------------------------------------------------------------
1 | ## LINQPad
2 | If you like to use LINQPad to dump `Item` object in a simplified format, you probably need a [custom dump method](http://www.linqpad.net/CustomizingDump.aspx) method. Adjust your LINQPad **My Extensions** file as follow
3 | ```cs
4 | static object ToDump(object obj)
5 | {
6 | var objType = obj.GetType();
7 | if (Type.GetType("Secs4Net.Item,Secs4Net") is { } itemType && objType.IsSubclassOf(itemType))
8 | {
9 | return Dump(obj);
10 | }
11 | return obj;
12 | }
13 |
14 | static object Dump(dynamic item)
15 | {
16 | return (int)item.Format switch
17 | {
18 | (int)SecsFormat.List => new { item.Format, Values = OnDemandList(item), },
19 | (int)SecsFormat.ASCII => new { item.Format, Values = item.GetString() },
20 | (int)SecsFormat.JIS8 => new { item.Format, Values = item.GetString() },
21 | (int)SecsFormat.Binary => new { item.Format, Values = OnDemandMemory(item) },
22 | (int)SecsFormat.Boolean => new { item.Format, Values = OnDemandMemory(item) },
23 | (int)SecsFormat.I8 => new { item.Format, Values = OnDemandMemory(item) },
24 | (int)SecsFormat.I1 => new { item.Format, Values = OnDemandMemory(item) },
25 | (int)SecsFormat.I2 => new { item.Format, Values = OnDemandMemory(item) },
26 | (int)SecsFormat.I4 => new { item.Format, Values = OnDemandMemory(item) },
27 | (int)SecsFormat.F8 => new { item.Format, Values = OnDemandMemory(item)},
28 | (int)SecsFormat.F4 => new { item.Format, Values = OnDemandMemory(item) },
29 | (int)SecsFormat.U8 => new { item.Format, Values = OnDemandMemory(item) },
30 | (int)SecsFormat.U1 => new { item.Format, Values = OnDemandMemory(item) },
31 | (int)SecsFormat.U2 => new { item.Format, Values = OnDemandMemory(item)},
32 | (int)SecsFormat.U4 => new { item.Format, Values = OnDemandMemory(item) },
33 | _ => $"Unsupported Format: {item.Format}",
34 | };
35 |
36 | static object OnDemandList(dynamic item) => Util.OnDemand($"[{item.Count}]", () => item.Items);
37 | static object OnDemandMemory(dynamic item) => Util.OnDemand($"[{item.Count}]", () => item.GetMemory());
38 | }
39 |
40 | // You can also define namespaces, non-static classes, enums, etc.
41 | enum SecsFormat
42 | {
43 | List = 0b_0000_00,
44 | Binary = 0b_0010_00,
45 | Boolean = 0b_0010_01,
46 | ASCII = 0b_0100_00,
47 | JIS8 = 0b_0100_01,
48 | I8 = 0b_0110_00,
49 | I1 = 0b_0110_01,
50 | I2 = 0b_0110_10,
51 | I4 = 0b_0111_00,
52 | F8 = 0b_1000_00,
53 | F4 = 0b_1001_00,
54 | U8 = 0b_1010_00,
55 | U1 = 0b_1010_01,
56 | U2 = 0b_1010_10,
57 | U4 = 0b_1011_00,
58 | }
59 | ```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # secs4net
2 |
3 | [](https://github.com/mkjeff/secs4net/actions/workflows/dotnet.yml) [](https://www.nuget.org/stats/packages/Secs4Net?groupby=Version) [](https://www.nuget.org/packages/Secs4Net) [](https://codecov.io/gh/mkjeff/secs4net)
4 |
5 | **Project Description**
6 |
7 | SECS-II/HSMS-SS/GEM implementation on .NET. This library provides an easy way to communicate with SEMI-standard compatible devices.
8 |
9 | **Getting started**
10 |
11 | ## Install Nuget package
12 | > dotnet add package Secs4Net
13 |
14 | ## Configure via .NET dependency injection
15 | [Sample code reference](https://github.com/mkjeff/secs4net/blob/base/samples/DeviceWorkerService/ServiceProvider.cs)
16 | ```cs
17 | public void ConfigureServices(IServiceCollection services)
18 | {
19 | // "secs4net" configuration section in the appsettings.json
20 | // "secs4net": {
21 | // "DeviceId": 0,
22 | // "IsActive": true,
23 | // "IpAddress": "127.0.0.1",
24 | // "Port": 5000
25 | // }
26 | services.AddSecs4Net(Configuration);
27 | }
28 |
29 | class DeviceLogger : ISecsGemLogger
30 | {
31 | // implement ISecsGemLogger methods
32 | }
33 | ```
34 |
35 | ## Basic usage
36 | ```cs
37 | try
38 | {
39 | var s3f17 = new SecsMessage(3, 17)
40 | {
41 | Name = "CreateProcessJob",
42 | SecsItem = L(
43 | U4(0),
44 | L(
45 | L(
46 | A("Id"),
47 | B(0x0D),
48 | L(
49 | A("carrier id"),
50 | L(
51 | U1(1)),
52 | L(
53 | U1(1),
54 | A("recipe"),
55 | L()),
56 | Boolean(true),
57 | L()))))
58 | };
59 |
60 | //access list
61 | s3f17.SecsItem[1][0][0] == A("Id");
62 |
63 | foreach(var item in s3f17.SecsItem[1][0][2].Items)
64 | {
65 |
66 | }
67 |
68 | //access an unmanaged array item
69 | byte b2 = s3f17.SecsItem[0].FirstValue(); // with different type
70 | s3f17.SecsItem[0].FirstValue() = 0; // change original value
71 | byte b3 = s3f17.SecsItem[0].GetFirstValueOrDefault(fallbackValueWhenItemIsEmpty);
72 | Memory bytes = s3f17.SecsItem[0].GetMemory();
73 |
74 | // access string item
75 | string str = s3f17.SecsItem[1][0][0].GetString(); // str = "Id"
76 |
77 | //await the secondary message
78 | var s3f18 = await secsGem.SendAsync(s3f17);
79 |
80 | // process message with LINQ
81 | var query =
82 | from a in s3f18.SecsItem[3]
83 | select new {
84 | num = a.FirstValue(),
85 | };
86 | }
87 | catch(SecsException)
88 | {
89 | // exception when
90 | // T3 timeout
91 | // device reply SxF0
92 | // device reply S9Fx
93 | }
94 | ```
95 |
96 | ## Handle primary messages
97 | ```cs
98 | await foreach (var e in secsGem.GetPrimaryMessageAsync(cancellationToken))
99 | {
100 | using var primaryMsg = e.PrimaryMessage;
101 | //do something for primary message
102 |
103 | // reply secondary message to device
104 | using var secondaryMsg = new SecsMessage(...);
105 | await e.TryReplyAsync(secondaryMsg);
106 | };
107 | ```
108 |
109 | ## Creates `Item` via LINQ
110 | ```cs
111 | using static Secs4Net.Item;
112 |
113 | var s16f15 =
114 | new SecsMessage(16, 15)
115 | {
116 | Name = "CreateProcessJob",
117 | SecsItem = L(
118 | U4(0),
119 | L(
120 | from pj in tx.ProcessJobs
121 | select
122 | L(
123 | A(pj.Id),
124 | B(0x0D),
125 | L(
126 | from carrier in pj.Carriers
127 | select
128 | L(
129 | A(carrier.Id),
130 | L(
131 | from slotInfo in carrier.SlotMap
132 | select
133 | U1(slotInfo.SlotNo)))),
134 | L(
135 | U1(1),
136 | A(pj.RecipeId),
137 | L()),
138 | Boolean(true),
139 | L()))));
140 | ```
141 |
142 | ## Change the `Item` value (restricted)
143 | > Basic rule: The `Item.Count` has been fixed while the item was created.
144 |
145 | You can only overwrite values on existing memory. String Item is immutable, coz C# `string` is immutable as well.
146 |
147 | ## Reuse array for large item values
148 | All unmanaged data Item can created from `IMemoryOwner` or `Memory`.
149 |
150 | The following sample uses the implementation of `IMemoryOwner` from [`Microsoft.Toolkit.HighPerformance`](https://docs.microsoft.com/en-us/windows/communitytoolkit/high-performance/memoryowner) that has been referenced internally by secs4net..
151 |
152 | ```cs
153 | var largeArrayOwner = MemoryOwner.Allocate(size: 65535);
154 |
155 | // feed the value into largeArrayOwner.Memory or largeArrayOwner.Span
156 | FillLargeArray(largeArrayOwner.Memory);
157 |
158 | using var s6f11 = new SecsMessage(6, 11, replyExpected: false)
159 | {
160 | Name = "LargeDataEvent",
161 | SecsItem = L(
162 | L(
163 | I2(1121),
164 | A(""),
165 | I4(largeArrayOwner))), // create Item from largeArrayOwner
166 | };
167 |
168 | // apply using on received message as well. coz the item decoded by PipeDecoder also uses MemoryOwner when the data array is big.
169 | using var s6f12 = await secsGem.SendAsync(s6f11);
170 | ```
171 | > `IMemoryOwner`, `Item`, and `SecsMessage` have implemented `IDisposable` don't forget to `Dispose` it when they don't need anymore.
172 | Otherwise, the array will not return to the pool till GC collects.
173 |
174 | > Since the length of the max encoded bytes in a single non-List Item was `16,777,215`(3 bytes), we split raw data into separated items.
175 | In that case, creating the Items from sliced `Memory` is more efficient.
176 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | remote_theme: pages-themes/leap-day@v0.2.0
2 | plugins:
3 | - jekyll-remote-theme # add this line to the plugins list if you already have one
4 |
--------------------------------------------------------------------------------
/common/Channel.Extensions.cs:
--------------------------------------------------------------------------------
1 | #if !NET
2 | using System.Collections.Generic;
3 | using System.Runtime.CompilerServices;
4 |
5 | namespace System.Threading.Channels
6 | {
7 | internal static class ChannelExtensions
8 | {
9 | public static async IAsyncEnumerable ReadAllAsync(this ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default)
10 | {
11 | while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false))
12 | {
13 | while (reader.TryRead(out var item))
14 | {
15 | yield return item;
16 | }
17 | }
18 | }
19 | }
20 | }
21 | #endif
22 |
--------------------------------------------------------------------------------
/common/Encoding.Extensions.cs:
--------------------------------------------------------------------------------
1 | #if !NET
2 | using CommunityToolkit.HighPerformance.Buffers;
3 | using System.Buffers;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace System.Text
7 | {
8 | internal static class EncodingExtenstions
9 | {
10 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
11 | internal static unsafe int GetBytes(this Encoding encoding, string str, Span span)
12 | {
13 | fixed (char* chars = str.AsSpan())
14 | {
15 | fixed (byte* bytes = span)
16 | {
17 | return encoding.GetBytes(chars, str.Length, bytes, span.Length);
18 | }
19 | }
20 | }
21 |
22 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
23 | internal static unsafe int GetCharCount(this Encoding encoding, ReadOnlySpan span)
24 | {
25 | fixed (byte* bytes = span)
26 | {
27 | return encoding.GetCharCount(bytes, span.Length);
28 | }
29 | }
30 |
31 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
32 | internal static unsafe string GetString(this Encoding encoding, ReadOnlySpan bytes)
33 | {
34 | using var spanOwner = SpanOwner.Allocate((int)bytes.Length);
35 | bytes.CopyTo(spanOwner.Span);
36 |
37 | fixed (byte* spanBytes = spanOwner.Span)
38 | {
39 | return encoding.GetString(spanBytes, spanOwner.Length);
40 | }
41 | }
42 |
43 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
44 | internal static unsafe string GetString(this Encoding encoding, in ReadOnlySequence bytes)
45 | {
46 | using var spanOwner = SpanOwner.Allocate((int)bytes.Length);
47 | bytes.CopyTo(spanOwner.Span);
48 |
49 | fixed (byte* spanBytes = spanOwner.Span)
50 | {
51 | return encoding.GetString(spanBytes, spanOwner.Length);
52 | }
53 | }
54 | }
55 | }
56 | #endif
57 |
--------------------------------------------------------------------------------
/common/EnumerableExtensions.cs:
--------------------------------------------------------------------------------
1 | #if !NET
2 | using System.Collections.Generic;
3 |
4 | namespace System.Linq
5 | {
6 | public static class EnumerableExt
7 | {
8 | public static IEnumerable Chunk(this IEnumerable items, int maxItems)
9 | {
10 | return items.Select((item, inx) => new { item, inx })
11 | .GroupBy(x => x.inx / maxItems)
12 | .Select(g => g.Select(x => x.item).ToArray());
13 | }
14 | }
15 | }
16 | #endif
17 |
--------------------------------------------------------------------------------
/common/ItemAssertions.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using FluentAssertions.Execution;
3 | using FluentAssertions.Primitives;
4 | using System;
5 |
6 | namespace Secs4Net;
7 |
8 | internal static class ItemExtensions
9 | {
10 | public static ItemAssertions Should(this Item? instance) => new(instance);
11 | }
12 |
13 | internal sealed class ItemAssertions : ReferenceTypeAssertions-
14 | {
15 | public ItemAssertions(Item? instance) : base(instance) { }
16 |
17 | protected override string Identifier => "item";
18 |
19 |
20 | public AndConstraint BeEquivalentTo(Item? expectation, string because = "", params object[] becauseArgs)
21 | {
22 | if (Subject?.Equals(expectation) != true)
23 | {
24 | new ItemValidator().IsEquals(new ItemEquivalencyValidationContext
25 | {
26 | Reason = new Reason(because, becauseArgs),
27 | Subject = Subject,
28 | Expectation = expectation,
29 | });
30 | }
31 |
32 | return new AndConstraint(this);
33 | }
34 |
35 | public AndConstraint NotBeEquivalentTo(Item expectation, string because = "", params object[] becauseArgs)
36 | {
37 | if (expectation.Equals(Subject))
38 | {
39 | Execute.Assertion
40 | .BecauseOf(because, becauseArgs)
41 | .FailWith("Expected item not be equivalent, but they are.");
42 | }
43 |
44 | return new AndConstraint(this);
45 | }
46 | }
47 |
48 | internal sealed class ItemEquivalencyValidationContext
49 | {
50 | public Reason Reason { get; init; } = default!;
51 | public Item? Subject { get; init; } = default!;
52 | public Item? Expectation { get; init; } = default!;
53 | }
54 |
55 | internal sealed class ItemValidator
56 | {
57 | public bool IsEquals(ItemEquivalencyValidationContext context)
58 | {
59 | if (context.Subject is not Item subjectValue ||
60 | context.Expectation is not Item expectationValue)
61 | {
62 | return false;
63 | }
64 |
65 | return IsMatch(path: "item", subjectValue, expectationValue, context);
66 | }
67 |
68 | private static bool IsMatch(string path, Item subject, Item expectation, ItemEquivalencyValidationContext context)
69 | {
70 | if (subject.Format != expectation.Format)
71 | {
72 | Execute.Assertion
73 | .BecauseOf(context.Reason)
74 | .FailWith("Expected {0} to be {1}, but found {2}",
75 | path + ".Format", expectation.Format, subject.Format);
76 |
77 | return false;
78 | }
79 |
80 | if (subject.Count != expectation.Count)
81 | {
82 | Execute.Assertion
83 | .BecauseOf(context.Reason)
84 | .FailWith("Expected {0} to be {1}, but found {2}",
85 | path + ".Count", expectation.Count, subject.Count);
86 | return false;
87 | }
88 |
89 | if (subject.Count == 0)
90 | {
91 | return true;
92 | }
93 |
94 | #pragma warning disable CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.
95 | return expectation.Format switch
96 | #pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.
97 | {
98 | SecsFormat.List => IsMatchList(path, subject, expectation, context),
99 | SecsFormat.ASCII or SecsFormat.JIS8 => IsMatchStringItem(path, subject, expectation, context),
100 | SecsFormat.Binary => IsMatchArrayItem(path, subject, expectation, context),
101 | SecsFormat.Boolean => IsMatchArrayItem(path, subject, expectation, context),
102 | SecsFormat.I8 => IsMatchArrayItem(path, subject, expectation, context),
103 | SecsFormat.I1 => IsMatchArrayItem(path, subject, expectation, context),
104 | SecsFormat.I2 => IsMatchArrayItem(path, subject, expectation, context),
105 | SecsFormat.I4 => IsMatchArrayItem(path, subject, expectation, context),
106 | SecsFormat.F8 => IsMatchArrayItem(path, subject, expectation, context),
107 | SecsFormat.F4 => IsMatchArrayItem(path, subject, expectation, context),
108 | SecsFormat.U8 => IsMatchArrayItem(path, subject, expectation, context),
109 | SecsFormat.U1 => IsMatchArrayItem(path, subject, expectation, context),
110 | SecsFormat.U2 => IsMatchArrayItem(path, subject, expectation, context),
111 | SecsFormat.U4 => IsMatchArrayItem(path, subject, expectation, context),
112 | };
113 |
114 | bool IsMatchStringItem(string path, Item subject, Item expectation, ItemEquivalencyValidationContext context)
115 | {
116 | var subjectString = subject.GetString();
117 | var expectationString = expectation.GetString();
118 | if (subjectString.Equals(expectationString, StringComparison.Ordinal))
119 | {
120 | return true;
121 | }
122 |
123 | Execute.Assertion
124 | .BecauseOf(context.Reason)
125 | .FailWith("Expected {0} to be {1}, but found {2}",
126 | path + ".GetString()", expectationString, subjectString);
127 |
128 | return false;
129 | }
130 |
131 | bool IsMatchArrayItem(string path, Item subject, Item expectation, ItemEquivalencyValidationContext context) where T : unmanaged, IEquatable
132 | {
133 | var subjectSpan = subject.GetMemory().Span;
134 | var expectationSpan = expectation.GetMemory().Span;
135 | if (subjectSpan.SequenceEqual(expectationSpan))
136 | {
137 | return true;
138 | }
139 |
140 | Execute.Assertion
141 | .BecauseOf(context.Reason)
142 | .FailWith("Expected {0} to be {1}, but found {2}",
143 | path + $".GetMemory<{typeof(T).Name}>()",
144 | expectationSpan.ToArray(),
145 | subjectSpan.ToArray());
146 | return false;
147 | }
148 |
149 | bool IsMatchList(string path, Item subjectList, Item expectationList, ItemEquivalencyValidationContext context)
150 | {
151 | for (int i = 0, count = subjectList.Count; i < count; i++)
152 | {
153 | if (!IsMatch(path + $"[{i}]", subjectList[i], expectationList[i], context))
154 | {
155 | return false;
156 | }
157 | }
158 |
159 | return true;
160 | }
161 | }
162 |
163 | }
164 |
--------------------------------------------------------------------------------
/common/RuntimeHelpers.cs:
--------------------------------------------------------------------------------
1 | #if !NET
2 | // Licensed to the .NET Foundation under one or more agreements.
3 | // The .NET Foundation licenses this file to you under the MIT license.
4 | // See the LICENSE file in the project root for more information.
5 |
6 | namespace System.Runtime.CompilerServices
7 | {
8 | internal static class RuntimeHelpers
9 | {
10 | ///
11 | /// Slices the specified array using the specified range.
12 | ///
13 | public static T[] GetSubArray(T[] array, Range range)
14 | {
15 | if (array == null)
16 | {
17 | throw new ArgumentNullException();
18 | }
19 |
20 | (int offset, int length) = range.GetOffsetAndLength(array.Length);
21 |
22 | if (default(T)! != null || typeof(T[]) == array.GetType()) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
23 | {
24 | // We know the type of the array to be exactly T[].
25 |
26 | if (length == 0)
27 | {
28 | return [];
29 | }
30 |
31 | var dest = new T[length];
32 | Array.Copy(array, offset, dest, 0, length);
33 | return dest;
34 | }
35 | else
36 | {
37 | // The array is actually a U[] where U:T.
38 | T[] dest = (T[])Array.CreateInstance(array.GetType().GetElementType()!, length);
39 | Array.Copy(array, offset, dest, 0, length);
40 | return dest;
41 | }
42 | }
43 | }
44 | }
45 | #endif
46 |
--------------------------------------------------------------------------------
/common/SecsMessageAssertions.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using FluentAssertions.Execution;
3 | using FluentAssertions.Primitives;
4 | using System.Collections.Generic;
5 |
6 | namespace Secs4Net;
7 |
8 | internal static class SecsMessageExtensions
9 | {
10 | public static SecsMessageAssertions Should(this SecsMessage? instance) => new(instance);
11 | }
12 |
13 | internal sealed class SecsMessageAssertions : ReferenceTypeAssertions
14 | {
15 | public SecsMessageAssertions(SecsMessage? instance) : base(instance) { }
16 |
17 | protected override string Identifier => "message";
18 |
19 | public AndConstraint BeEquivalentTo(SecsMessage expectation, string because = "", params object[] becauseArgs)
20 | {
21 | if (!IsMessageEquals(expectation, Subject))
22 | {
23 | new ObjectAssertions(Subject).BeEquivalentTo(expectation,
24 | options => options.ComparingByMembers()
25 | .Excluding(a => a.SecsItem)
26 | .Excluding(a => a.Name),
27 | because, becauseArgs);
28 |
29 | if (Subject?.SecsItem is not null)
30 | {
31 | Subject.SecsItem.Should().BeEquivalentTo(expectation.SecsItem);
32 | }
33 | else if (expectation.SecsItem is not null)
34 | {
35 | Execute.Assertion
36 | .BecauseOf(because, becauseArgs)
37 | .FailWith("Expected message.SecsItem is not null, but is");
38 | }
39 | }
40 |
41 | return new AndConstraint(this);
42 | }
43 |
44 | public AndConstraint NotBeEquivalentTo(SecsMessage expectation, string because = "", params object[] becauseArgs)
45 | {
46 | if (IsMessageEquals(expectation, Subject))
47 | {
48 | Execute.Assertion
49 | .BecauseOf(because, becauseArgs)
50 | .FailWith("Expected message not be equivalent, but they are.");
51 | }
52 |
53 | return new AndConstraint(this);
54 | }
55 |
56 | private static bool IsMessageEquals(SecsMessage left, SecsMessage? right)
57 | {
58 | if (right is null)
59 | {
60 | return false;
61 | }
62 |
63 | // exclude Name property
64 | return left.S == right.S
65 | && left.F == right.F
66 | && left.ReplyExpected == right.ReplyExpected
67 | && EqualityComparer
- .Default.Equals(left.SecsItem!, right.SecsItem!);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/common/Usings.cs:
--------------------------------------------------------------------------------
1 | global using static global::Secs4Net.Item;
2 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "8.0.0",
4 | "rollForward": "latestMajor",
5 | "allowPrerelease": false
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/samples/DeviceWorkerService/DeviceLogger.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using Secs4Net;
3 | using Secs4Net.Sml;
4 | using System;
5 |
6 | namespace DeviceWorkerService;
7 |
8 | internal sealed class DeviceLogger(ILogger logger) : ISecsGemLogger
9 | {
10 | public void MessageIn(SecsMessage msg, int id) => logger.LogTrace($"<-- [0x{id:X8}] {msg.ToSml()}");
11 | public void MessageOut(SecsMessage msg, int id) => logger.LogTrace($"--> [0x{id:X8}] {msg.ToSml()}");
12 | public void Debug(string msg) => logger.LogDebug(msg);
13 | public void Info(string msg) => logger.LogInformation(msg);
14 | public void Warning(string msg) => logger.LogWarning(msg);
15 | public void Error(string msg, SecsMessage? message, Exception? ex) => logger.LogError(ex, $"{msg} {message}\n");
16 | }
17 |
--------------------------------------------------------------------------------
/samples/DeviceWorkerService/DeviceWorker.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Hosting;
2 | using Microsoft.Extensions.Logging;
3 | using Secs4Net;
4 | using System;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace DeviceWorkerService;
9 |
10 | internal sealed class DeviceWorker : BackgroundService
11 | {
12 | private readonly ILogger _logger;
13 | private readonly ISecsConnection _hsmsConnection;
14 | private readonly ISecsGem _secsGem;
15 |
16 | public DeviceWorker(ILogger logger, ISecsConnection hsmsConnection, ISecsGem secsGem)
17 | {
18 | _logger = logger;
19 | _hsmsConnection = hsmsConnection;
20 | _secsGem = secsGem;
21 |
22 | _hsmsConnection.ConnectionChanged += delegate
23 | {
24 | switch (_hsmsConnection.State)
25 | {
26 | case ConnectionState.Retry:
27 | _logger.LogError($"Connection loss, try to reconnect.");
28 | break;
29 | case ConnectionState.Connecting:
30 | case ConnectionState.Connected:
31 | _logger.LogWarning(_hsmsConnection.State.ToString());
32 | break;
33 | default:
34 | _logger.LogInformation($"Connection state = {_hsmsConnection.State}");
35 | break;
36 | }
37 | };
38 | }
39 |
40 | protected override async Task ExecuteAsync(CancellationToken stoppingToken)
41 | {
42 | try
43 | {
44 | _hsmsConnection.Start(stoppingToken);
45 | await foreach (var e in _secsGem.GetPrimaryMessageAsync(stoppingToken))
46 | {
47 | using var primaryMessage = e.PrimaryMessage;
48 | _logger.LogInformation($"Received primary message: {primaryMessage}");
49 | try
50 | {
51 | using var secondaryMessage = new SecsMessage(primaryMessage.S, (byte)(primaryMessage.F + 1))
52 | {
53 | SecsItem = primaryMessage.SecsItem,
54 | };
55 | await e.TryReplyAsync(secondaryMessage, stoppingToken);
56 | }
57 | catch (Exception ex)
58 | {
59 | _logger.LogError(ex, "Exception occurred when processing primary message");
60 | }
61 | }
62 | }
63 | catch (Exception ex)
64 | {
65 | if (stoppingToken.IsCancellationRequested)
66 | {
67 | return;
68 | }
69 | _logger.LogError(ex, "Unhandled exception occurred on primary messages processing");
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/samples/DeviceWorkerService/DeviceWorkerService.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | true
6 | false
7 | true
8 | dotnet-DeviceWorkerService-D257E021-F238-499C-8ACD-858186E73C0A
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/samples/DeviceWorkerService/Program.cs:
--------------------------------------------------------------------------------
1 | using DeviceWorkerService;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Microsoft.Extensions.Hosting;
4 |
5 | Host.CreateDefaultBuilder(args)
6 | .ConfigureServices((hostContext, services) =>
7 | {
8 | services.AddSecs4Net(hostContext.Configuration);
9 | services.AddHostedService();
10 | }).Build().Run();
11 |
--------------------------------------------------------------------------------
/samples/DeviceWorkerService/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "DeviceWorkerService": {
4 | "commandName": "Project",
5 | "dotnetRunMessages": true,
6 | "environmentVariables": {
7 | "DOTNET_ENVIRONMENT": "Development"
8 | }
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/samples/DeviceWorkerService/ServiceProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Secs4Net;
4 | using System.Diagnostics.CodeAnalysis;
5 |
6 | namespace DeviceWorkerService;
7 |
8 | public static class ServiceProvider
9 | {
10 | public static IServiceCollection AddSecs4Net<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TLogger>(this IServiceCollection services, IConfiguration configuration)
11 | where TLogger : class, ISecsGemLogger
12 | {
13 | var configSection = configuration.GetSection("secs4net");
14 | services.Configure(configSection);
15 | services.AddSingleton();
16 | services.AddSingleton();
17 | services.AddSingleton();
18 | return services;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/samples/DeviceWorkerService/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information",
7 | "DeviceWorkerService": "Trace"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/samples/DeviceWorkerService/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "secs4net": {
10 | "DeviceId": 1,
11 | "IsActive": false,
12 | "IpAddress": "127.0.0.1",
13 | "Port": 5002
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/samples/SecsDevice/Form1.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | text/microsoft-resx
50 |
51 |
52 | 2.0
53 |
54 |
55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
56 |
57 |
58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
59 |
60 |
61 | False
62 |
63 |
64 | False
65 |
66 |
67 | False
68 |
69 |
70 | False
71 |
72 |
73 | False
74 |
75 |
76 | False
77 |
78 |
79 | DVLA:'S2F45' W
80 | <L[2]
81 | <U1[1] 0>
82 | <L[1]
83 | <L[2]
84 | <U1[0]>
85 | <L[1]
86 | <L[2]
87 | <B[0]>
88 | <L[2]
89 | <U1[0]>
90 | <U1[0]>
91 | >
92 | >
93 | >
94 | >
95 | >
96 | >
97 | .
98 |
99 |
100 | False
101 |
102 |
103 | False
104 |
105 |
106 | 17, 17
107 |
108 |
109 | False
110 |
111 |
112 | False
113 |
114 |
115 | False
116 |
117 |
--------------------------------------------------------------------------------
/samples/SecsDevice/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using System.Windows.Forms;
4 |
5 | namespace SecsDevice;
6 |
7 | static class Program
8 | {
9 | ///
10 | /// The main entry point for the application.
11 | ///
12 | [STAThread]
13 | static void Main()
14 | {
15 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
16 | Secs4Net.Item.JIS8Encoding = Encoding.GetEncoding(50222);
17 | #if NET
18 | Application.SetHighDpiMode(HighDpiMode.SystemAware);
19 | #endif
20 | Application.EnableVisualStyles();
21 | Application.SetCompatibleTextRenderingDefault(false);
22 | Application.Run(new Form1());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/samples/SecsDevice/Properties/DataSources/RecvMessage.datasource:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 | SecsDevice.RecvMessage, SecsDevice, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
10 |
--------------------------------------------------------------------------------
/samples/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 | .
--------------------------------------------------------------------------------
/samples/SecsDevice/SecsDevice.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net8.0-windows;net472
6 | true
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/samples/WpfVisualizer/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/WpfVisualizer/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 |
--------------------------------------------------------------------------------
/samples/WpfVisualizer/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 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
10 | => ((bool)value) ? "'W'" : string.Empty;
11 |
12 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
13 | {
14 | throw new NotImplementedException();
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/samples/WpfVisualizer/SecsMessageList.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Collections.ObjectModel;
4 | using System.IO;
5 | using Secs4Net.Json;
6 |
7 | namespace Secs4Net
8 | {
9 | public sealed class SecsMessageList : ObservableCollection {
10 | public SecsMessageList(string jsonFile) : base(File.OpenRead(jsonFile).ToSecsMessages()) { }
11 |
12 | public SecsMessage? this[byte s, byte f, string name] => this[s, f].FirstOrDefault(m => m.Name == name);
13 |
14 | public IEnumerable this[byte s, byte f] => this.Where(m => m.S == s && m.F == f);
15 |
16 | public SecsMessage? this[string name] => this.FirstOrDefault(m => m.Name == name);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/samples/WpfVisualizer/SecsMessageTreeView.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 |
4 | namespace SecsMessageVisuallizer
5 | {
6 | ///
7 | /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
8 | ///
9 | /// Step 1a) Using this custom control in a XAML file that exists in the current project.
10 | /// Add this XmlNamespace attribute to the root element of the markup file where it is
11 | /// to be used:
12 | ///
13 | /// xmlns:MyNamespace="clr-namespace:SecsMessageVisuallizer"
14 | ///
15 | ///
16 | /// Step 1b) Using this custom control in a XAML file that exists in a different project.
17 | /// Add this XmlNamespace attribute to the root element of the markup file where it is
18 | /// to be used:
19 | ///
20 | /// xmlns:MyNamespace="clr-namespace:SecsMessageVisuallizer;assembly=SecsMessageVisuallizer"
21 | ///
22 | /// You will also need to add a project reference from the project where the XAML file lives
23 | /// to this project and Rebuild to avoid compilation errors:
24 | ///
25 | /// Right click on the target project in the Solution Explorer and
26 | /// "Add Reference"->"Projects"->[Browse to and select this project]
27 | ///
28 | ///
29 | /// Step 2)
30 | /// Go ahead and use your control in the XAML file.
31 | ///
32 | ///
33 | ///
34 | ///
35 | public class SecsMessageTreeView : TreeView
36 | {
37 | static SecsMessageTreeView()
38 | {
39 | DefaultStyleKeyProperty.OverrideMetadata(typeof(SecsMessageTreeView), new FrameworkPropertyMetadata(typeof(SecsMessageTreeView)));
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/samples/WpfVisualizer/Themes/Generic.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
19 |
20 |
--------------------------------------------------------------------------------
/samples/WpfVisualizer/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 | if (value is TreeView tv)
12 | {
13 | if (tv.ItemContainerGenerator.ContainerFromItem(tv.SelectedItem) is TreeViewItem tvItem)
14 | return tvItem.IsFocused;
15 | }
16 | return false;
17 | }
18 |
19 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
20 | throw new NotImplementedException();
21 | }
22 |
23 | #endregion
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/samples/WpfVisualizer/ViewModel/SecsItemViewModel.cs:
--------------------------------------------------------------------------------
1 | using Secs4Net;
2 | using Secs4Net.Json;
3 | using System.Text.Json;
4 |
5 | namespace SecsMessageVisuallizer.ViewModel
6 | {
7 | public class SecsItemViewModel : TreeViewItemViewModel
8 | {
9 | readonly Item _secsItem;
10 | public SecsItemViewModel(Item item, SecsMessageViewModel secsMsg)
11 | : base(secsMsg, lazyLoadChildren: item.Format == SecsFormat.List && item.Count > 0)
12 | {
13 | _secsItem = item;
14 | }
15 |
16 | public SecsItemViewModel(Item item, SecsItemViewModel parentItem)
17 | : base(parentItem, lazyLoadChildren: item.Format == SecsFormat.List && item.Count > 0)
18 | {
19 | _secsItem = item;
20 | }
21 |
22 | protected override void LoadChildren()
23 | {
24 | foreach (Item item in _secsItem.Items)
25 | {
26 | base.Children.Add(new SecsItemViewModel(item, this));
27 | }
28 | }
29 |
30 | public string Name
31 | => _secsItem.ToString();
32 |
33 | private static readonly JsonSerializerOptions JsonOptions = new()
34 | {
35 | WriteIndented = true,
36 | Converters =
37 | {
38 | new ItemJsonConverter(),
39 | }
40 | };
41 |
42 | public override string ToString()
43 | => JsonSerializer.Serialize(_secsItem, JsonOptions);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/samples/WpfVisualizer/ViewModel/SecsMessageCollectionViewModel.cs:
--------------------------------------------------------------------------------
1 | using Secs4Net;
2 | using System.Collections.Generic;
3 |
4 | namespace SecsMessageVisuallizer.ViewModel
5 | {
6 | public class SecsMessageCollectionViewModel
7 | {
8 | readonly SecsMessageList _msgList;
9 |
10 | public SecsMessageCollectionViewModel(SecsMessageList secsMsgList)
11 | {
12 | _msgList = secsMsgList;
13 | }
14 |
15 | public IEnumerable SecsMessages
16 | {
17 | get
18 | {
19 | foreach (SecsMessage msg in _msgList)
20 | yield return new SecsMessageViewModel(msg);
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/samples/WpfVisualizer/ViewModel/SecsMessageViewModel.cs:
--------------------------------------------------------------------------------
1 | using Secs4Net;
2 | using Secs4Net.Json;
3 | using System.Text.Json;
4 |
5 | namespace SecsMessageVisuallizer.ViewModel
6 | {
7 | public class SecsMessageViewModel : TreeViewItemViewModel
8 | {
9 | readonly SecsMessage _secsMsg;
10 | public SecsMessageViewModel(SecsMessage secsMsg)
11 | : base(null, false)
12 | {
13 | _secsMsg = secsMsg;
14 | if (secsMsg.SecsItem != null)
15 | base.Children.Add(new SecsItemViewModel(secsMsg.SecsItem, this));
16 | }
17 |
18 | public byte StreamNumber => _secsMsg.S;
19 | public byte FunctionNumber => _secsMsg.F;
20 | public string? Name
21 | {
22 | get { return _secsMsg.Name; }
23 | set
24 | {
25 | if (value != null && value != _secsMsg.Name)
26 | {
27 | _secsMsg.Name = value;
28 | base.OnPropertyChanged();
29 | }
30 | }
31 | }
32 | public bool ReplyExpected => _secsMsg.ReplyExpected;
33 |
34 | private static readonly JsonSerializerOptions JsonOptions = new()
35 | {
36 | WriteIndented = true,
37 | Converters =
38 | {
39 | new ItemJsonConverter(),
40 | }
41 | };
42 |
43 | public override string ToString()
44 | => JsonSerializer.Serialize(_secsMsg, JsonOptions);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/samples/WpfVisualizer/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 | {
10 | static readonly TreeViewItemViewModel DummyChild = new TreeViewItemViewModel();
11 |
12 | readonly ObservableCollection _children = new();
13 | readonly TreeViewItemViewModel? _parent;
14 |
15 | bool _isExpanded;
16 | bool _isSelected;
17 |
18 | protected TreeViewItemViewModel(TreeViewItemViewModel? parent, bool lazyLoadChildren)
19 | {
20 | _parent = parent;
21 | if (lazyLoadChildren)
22 | _children.Add(DummyChild);
23 | }
24 |
25 | // This is used to create the DummyChild instance.
26 | private TreeViewItemViewModel()
27 | {
28 | }
29 |
30 | ///
31 | /// Returns the logical child items of this object.
32 | ///
33 | public ObservableCollection Children => _children;
34 |
35 | ///
36 | /// Returns true if this object's Children have not yet been populated.
37 | ///
38 | public bool HasDummyChild => Children.Count == 1 && Children[0] == DummyChild;
39 |
40 | ///
41 | /// Gets/sets whether the TreeViewItem
42 | /// associated with this object is expanded.
43 | ///
44 | public bool IsExpanded
45 | {
46 | get { return _isExpanded; }
47 | set
48 | {
49 | if (value != _isExpanded)
50 | {
51 | _isExpanded = value;
52 | OnPropertyChanged();
53 | }
54 |
55 | // Expand all the way up to the root.
56 | if (_isExpanded && _parent != null)
57 | _parent.IsExpanded = true;
58 |
59 | // Lazy load the child items, if necessary.
60 | if (HasDummyChild)
61 | {
62 | Children.Remove(DummyChild);
63 | LoadChildren();
64 | }
65 | }
66 | }
67 |
68 | ///
69 | /// Gets/sets whether the TreeViewItem
70 | /// associated with this object is selected.
71 | ///
72 | public bool IsSelected
73 | {
74 | get { return _isSelected; }
75 | set { SetField(ref _isSelected, value); }
76 | }
77 |
78 | ///
79 | /// Invoked when the child items need to be loaded on demand.
80 | /// Subclasses can override this to populate the Children collection.
81 | ///
82 | protected virtual void LoadChildren()
83 | {
84 | }
85 |
86 | public TreeViewItemViewModel? Parent => _parent;
87 |
88 | public event PropertyChangedEventHandler? PropertyChanged;
89 |
90 | protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
91 | {
92 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
93 | }
94 |
95 | protected bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null)
96 | {
97 | if (EqualityComparer.Default.Equals(field, value))
98 | return false;
99 | field = value;
100 | OnPropertyChanged(propertyName);
101 | return true;
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/samples/WpfVisualizer/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 |
--------------------------------------------------------------------------------
/samples/WpfVisualizer/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 | DataContext = new SecsMessageCollectionViewModel(new SecsMessageList("common.json"));
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/samples/WpfVisualizer/WpfVisualizer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net8.0-windows;net472
6 | true
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Always
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/Secs4Net.Json/ItemJsonConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.Json;
3 | using System.Text.Json.Serialization;
4 |
5 | namespace Secs4Net.Json;
6 |
7 | public sealed class ItemJsonConverter : JsonConverter
-
8 | {
9 | public override Item? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
10 | {
11 | return JsonElement.TryParseValue(ref reader, out var element)
12 | ? element.GetValueOrDefault().ToItem()
13 | : null;
14 | }
15 |
16 | public override void Write(Utf8JsonWriter writer, Item item, JsonSerializerOptions options)
17 | {
18 | item.WriteTo(writer);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Secs4Net.Json/JsonReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Runtime.CompilerServices;
6 | using System.Text.Json;
7 | using static Secs4Net.Item;
8 |
9 | namespace Secs4Net.Json;
10 |
11 | public static class JsonReader
12 | {
13 | public static Item ToItem(this JsonElement jsonObject)
14 | {
15 | var json = jsonObject.EnumerateObject().First();
16 | #if NET
17 | var format = Enum.Parse(json.Name);
18 | #else
19 | Enum.TryParse(json.Name, out var format);
20 | #endif
21 | var value = json.Value;
22 | return format switch
23 | {
24 | SecsFormat.List => CreateList(value),
25 | SecsFormat.ASCII => A(value.GetString()),
26 | SecsFormat.JIS8 => J(value.GetString()),
27 | SecsFormat.Binary => CreateBinary(value),
28 | SecsFormat.Boolean => CreateBoolean(value),
29 | SecsFormat.F4 => CreateF4(value),
30 | SecsFormat.F8 => CreateF8(value),
31 | SecsFormat.I1 => CreateI1(value),
32 | SecsFormat.I2 => CreateI2(value),
33 | SecsFormat.I4 => CreateI4(value),
34 | SecsFormat.I8 => CreateI8(value),
35 | SecsFormat.U1 => CreateU1(value),
36 | SecsFormat.U2 => CreateU2(value),
37 | SecsFormat.U4 => CreateU4(value),
38 | SecsFormat.U8 => CreateU8(value),
39 | _ => ThrowHelper(format),
40 | };
41 |
42 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
43 | static Item CreateList(JsonElement jsonArray)
44 | {
45 | var items = new Item[jsonArray.GetArrayLength()];
46 | int i = 0;
47 | foreach (var a in jsonArray.EnumerateArray())
48 | {
49 | items[i++] = a.ToItem();
50 | }
51 | return L(items);
52 | }
53 |
54 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
55 | static Item CreateBinary(JsonElement jsonArray)
56 | {
57 | var values = new byte[jsonArray.GetArrayLength()];
58 | int i = 0;
59 | foreach (var a in jsonArray.EnumerateArray())
60 | {
61 | values[i++] = a.GetByte();
62 | }
63 | return B(values);
64 | }
65 |
66 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
67 | static Item CreateBoolean(JsonElement jsonArray)
68 | {
69 | var values = new bool[jsonArray.GetArrayLength()];
70 | int i = 0;
71 | foreach (var a in jsonArray.EnumerateArray())
72 | {
73 | values[i++] = a.GetBoolean();
74 | }
75 | return Boolean(values);
76 | }
77 |
78 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
79 | static Item CreateF4(JsonElement jsonArray)
80 | {
81 | var values = new float[jsonArray.GetArrayLength()];
82 | int i = 0;
83 | foreach (var a in jsonArray.EnumerateArray())
84 | {
85 | values[i++] = a.GetSingle();
86 | }
87 | return F4(values);
88 | }
89 |
90 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
91 | static Item CreateF8(JsonElement jsonArray)
92 | {
93 | var values = new double[jsonArray.GetArrayLength()];
94 | int i = 0;
95 | foreach (var a in jsonArray.EnumerateArray())
96 | {
97 | values[i++] = a.GetDouble();
98 | }
99 | return F8(values);
100 | }
101 |
102 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
103 | static Item CreateI1(JsonElement jsonArray)
104 | {
105 | var values = new sbyte[jsonArray.GetArrayLength()];
106 | int i = 0;
107 | foreach (var a in jsonArray.EnumerateArray())
108 | {
109 | values[i++] = a.GetSByte();
110 | }
111 | return I1(values);
112 | }
113 |
114 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
115 | static Item CreateI2(JsonElement jsonArray)
116 | {
117 | var values = new short[jsonArray.GetArrayLength()];
118 | int i = 0;
119 | foreach (var a in jsonArray.EnumerateArray())
120 | {
121 | values[i++] = a.GetInt16();
122 | }
123 | return I2(values);
124 | }
125 |
126 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
127 | static Item CreateI4(JsonElement jsonArray)
128 | {
129 | var values = new int[jsonArray.GetArrayLength()];
130 | int i = 0;
131 | foreach (var a in jsonArray.EnumerateArray())
132 | {
133 | values[i++] = a.GetInt32();
134 | }
135 | return I4(values);
136 | }
137 |
138 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
139 | static Item CreateI8(JsonElement jsonArray)
140 | {
141 | var values = new long[jsonArray.GetArrayLength()];
142 | int i = 0;
143 | foreach (var a in jsonArray.EnumerateArray())
144 | {
145 | values[i++] = a.GetInt64();
146 | }
147 | return I8(values);
148 | }
149 |
150 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
151 | static Item CreateU1(JsonElement jsonArray)
152 | {
153 | var values = new byte[jsonArray.GetArrayLength()];
154 | int i = 0;
155 | foreach (var a in jsonArray.EnumerateArray())
156 | {
157 | values[i++] = a.GetByte();
158 | }
159 | return U1(values);
160 | }
161 |
162 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
163 | static Item CreateU2(JsonElement jsonArray)
164 | {
165 | var values = new ushort[jsonArray.GetArrayLength()];
166 | int i = 0;
167 | foreach (var a in jsonArray.EnumerateArray())
168 | {
169 | values[i++] = a.GetUInt16();
170 | }
171 | return U2(values);
172 | }
173 |
174 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
175 | static Item CreateU4(JsonElement jsonArray)
176 | {
177 | var values = new uint[jsonArray.GetArrayLength()];
178 | int i = 0;
179 | foreach (var a in jsonArray.EnumerateArray())
180 | {
181 | values[i++] = a.GetUInt32();
182 | }
183 | return U4(values);
184 | }
185 |
186 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
187 | static Item CreateU8(JsonElement jsonArray)
188 | {
189 | var values = new ulong[jsonArray.GetArrayLength()];
190 | int i = 0;
191 | foreach (var a in jsonArray.EnumerateArray())
192 | {
193 | values[i++] = a.GetUInt64();
194 | }
195 | return U8(values);
196 | }
197 |
198 | static Item ThrowHelper(SecsFormat format) => throw new ArgumentOutOfRangeException($"Unknown item format: {format}");
199 | }
200 |
201 | public static IEnumerable ToSecsMessages(this Stream stream)
202 | {
203 | var options = new JsonSerializerOptions
204 | {
205 | Converters = {
206 | new ItemJsonConverter()
207 | }
208 | };
209 |
210 | return JsonSerializer.Deserialize>(stream, options)!;
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/src/Secs4Net.Json/JsonWriter.cs:
--------------------------------------------------------------------------------
1 | using Secs4Net.Extensions;
2 | using System;
3 | using System.IO;
4 | using System.Runtime.CompilerServices;
5 | using System.Text;
6 | using System.Text.Json;
7 |
8 | namespace Secs4Net.Json;
9 |
10 | public static class JsonWriter
11 | {
12 | public static string ToJson(this Item item, JsonWriterOptions options = default)
13 | {
14 | using var mem = new MemoryStream();
15 | using var jwtr = new Utf8JsonWriter(mem, options);
16 | item.WriteTo(jwtr);
17 | jwtr.Flush();
18 | return Encoding.UTF8.GetString(mem.ToArray());
19 | }
20 |
21 | public static void WriteTo(this Item item, Utf8JsonWriter writer)
22 | {
23 | writer.WriteStartObject();
24 |
25 | writer.WritePropertyName(item.Format.GetName());
26 |
27 | if (item.Format == SecsFormat.ASCII || item.Format == SecsFormat.JIS8)
28 | {
29 | writer.WriteStringValue(item.GetString());
30 | }
31 | else
32 | {
33 | writer.WriteStartArray();
34 | switch (item.Format)
35 | {
36 | case SecsFormat.List:
37 | foreach (var a in item.Items)
38 | {
39 | a.WriteTo(writer);
40 | }
41 | break;
42 | case SecsFormat.Boolean:
43 | WriteArrayValue(writer, item, static (writer, value) => writer.WriteBooleanValue(value));
44 | break;
45 | case SecsFormat.I8:
46 | WriteArrayValue(writer, item, static (writer, value) => writer.WriteNumberValue(value));
47 | break;
48 | case SecsFormat.I1:
49 | WriteArrayValue(writer, item, static (writer, value) => writer.WriteNumberValue(value));
50 | break;
51 | case SecsFormat.I2:
52 | WriteArrayValue(writer, item, static (writer, value) => writer.WriteNumberValue(value));
53 | break;
54 | case SecsFormat.I4:
55 | WriteArrayValue(writer, item, static (writer, value) => writer.WriteNumberValue(value));
56 | break;
57 | case SecsFormat.F4:
58 | WriteArrayValue(writer, item, static (writer, value) => writer.WriteNumberValue(value));
59 | break;
60 | case SecsFormat.F8:
61 | WriteArrayValue(writer, item, static (writer, value) => writer.WriteNumberValue(value));
62 | break;
63 | case SecsFormat.U8:
64 | WriteArrayValue(writer, item, static (writer, value) => writer.WriteNumberValue(value));
65 | break;
66 | case SecsFormat.U1:
67 | case SecsFormat.Binary:
68 | WriteArrayValue(writer, item, static (writer, value) => writer.WriteNumberValue(value));
69 | break;
70 | case SecsFormat.U2:
71 | WriteArrayValue(writer, item, static (writer, value) => writer.WriteNumberValue(value));
72 | break;
73 | case SecsFormat.U4:
74 | WriteArrayValue(writer, item, static (writer, value) => writer.WriteNumberValue(value));
75 | break;
76 | }
77 | writer.WriteEndArray();
78 | }
79 |
80 | writer.WriteEndObject();
81 |
82 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
83 | static void WriteArrayValue(Utf8JsonWriter writer, Item item, Action write) where T : unmanaged, IEquatable
84 | {
85 | var span = item.GetMemory().Span;
86 | foreach (ref readonly var value in span)
87 | {
88 | write.Invoke(writer, value);
89 | }
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/Secs4Net.Json/Secs4Net.Json.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0;net8.0;netstandard2.0
5 | false
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | all
16 | runtime; build; native; contentfiles; analyzers; buildtransitive
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/Secs4Net.Sml/MemoryExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace System;
6 |
7 | internal delegate TResult SpanParser(ReadOnlySpan span);
8 |
9 | //https://github.com/bbartels/coreclr/blob/master/src/System.Private.CoreLib/shared/System/MemoryExtensions.Split.cs
10 | // https://github.com/dotnet/runtime/pull/295
11 |
12 | internal static partial class MemoryExtensions
13 | {
14 | ///
15 | /// Returns an enumerator that iterates through a ,
16 | /// which is split by separator .
17 | ///
18 | /// The source span which should be iterated over.
19 | /// The separator used to separate the .
20 | /// The which should be applied with this operation.
21 | /// Returns an enumerator for the specified sequence.
22 | public static SpanSplitEnumerator Split(in this ReadOnlySpan span, char separator, StringSplitOptions options = StringSplitOptions.None)
23 | => new(span, separator, options == StringSplitOptions.RemoveEmptyEntries);
24 |
25 | public static bool IsEmpty(this SpanSplitEnumerator source) where T : unmanaged, IEquatable
26 | => !source.MoveNext();
27 |
28 | public static TResult[] ToArray(ref this SpanSplitEnumerator source, SpanParser selector, int? size)
29 | {
30 | if (size.HasValue)
31 | {
32 | var list = new TResult[size.GetValueOrDefault()];
33 | ref var r0 = ref MemoryMarshal.GetReference(list.AsSpan());
34 | uint i = 0;
35 | foreach (var span in source)
36 | {
37 | Unsafe.Add(ref r0, i++) = selector.Invoke(span);
38 | if (i == list.Length)
39 | {
40 | break;
41 | }
42 | }
43 |
44 | return list;
45 | }
46 | else
47 | {
48 | var list = new List();
49 | foreach (var span in source)
50 | {
51 | list.Add(selector.Invoke(span));
52 | }
53 |
54 | return list.ToArray();
55 | }
56 | }
57 | }
58 |
59 | internal ref struct SpanSplitEnumerator where T : unmanaged, IEquatable
60 | {
61 | private ReadOnlySpan _sequence;
62 | private readonly T _separator;
63 | private SpanSplitInfo _spanSplitInfo;
64 |
65 | private bool ShouldRemoveEmptyEntries => _spanSplitInfo.HasFlag(SpanSplitInfo.RemoveEmptyEntries);
66 | private bool IsFinished => _spanSplitInfo.HasFlag(SpanSplitInfo.FinishedEnumeration);
67 |
68 | ///
69 | /// Gets the element at the current position of the enumerator.
70 | ///
71 | public ReadOnlySpan Current { get; private set; }
72 |
73 | ///
74 | /// Returns the current enumerator.
75 | ///
76 | /// Returns the current enumerator.
77 | public SpanSplitEnumerator GetEnumerator() => this;
78 |
79 | internal SpanSplitEnumerator(ReadOnlySpan span, T separator, bool removeEmptyEntries)
80 | {
81 | Current = default;
82 | _sequence = span;
83 | _separator = separator;
84 | _spanSplitInfo = default(SpanSplitInfo) | (removeEmptyEntries ? SpanSplitInfo.RemoveEmptyEntries : 0);
85 | }
86 |
87 | ///
88 | /// Advances the enumerator to the next element in the .
89 | ///
90 | /// Returns whether there is another item in the enumerator.
91 | public bool MoveNext()
92 | {
93 | if (IsFinished) { return false; }
94 |
95 | do
96 | {
97 | int index = _sequence.IndexOf(_separator);
98 | if (index < 0)
99 | {
100 | Current = _sequence;
101 | _spanSplitInfo |= SpanSplitInfo.FinishedEnumeration;
102 | return !(ShouldRemoveEmptyEntries && Current.IsEmpty);
103 | }
104 |
105 | Current = _sequence[..index];
106 | _sequence = _sequence[(index + 1)..];
107 | } while (Current.IsEmpty && ShouldRemoveEmptyEntries);
108 |
109 | return true;
110 | }
111 |
112 | [Flags]
113 | private enum SpanSplitInfo : byte
114 | {
115 | RemoveEmptyEntries = 0x1,
116 | FinishedEnumeration = 0x2
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/Secs4Net.Sml/Secs4Net.Sml.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0;net8.0;netstandard2.0
5 | true
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | all
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/Secs4Net/ConnectionState.cs:
--------------------------------------------------------------------------------
1 | namespace Secs4Net;
2 |
3 | public enum ConnectionState
4 | {
5 | Connecting,
6 | Connected,
7 | Selected,
8 | Retry,
9 | }
10 |
--------------------------------------------------------------------------------
/src/Secs4Net/Extensions/ExtensionHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace Secs4Net.Extensions;
5 |
6 | public static class SecsExtension
7 | {
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static string GetName(this SecsFormat format)
10 | {
11 | return format switch
12 | {
13 | SecsFormat.List => nameof(SecsFormat.List),
14 | SecsFormat.Binary => nameof(SecsFormat.Binary),
15 | SecsFormat.Boolean => nameof(SecsFormat.Boolean),
16 | SecsFormat.ASCII => nameof(SecsFormat.ASCII),
17 | SecsFormat.JIS8 => nameof(SecsFormat.JIS8),
18 | SecsFormat.I8 => nameof(SecsFormat.I8),
19 | SecsFormat.I1 => nameof(SecsFormat.I1),
20 | SecsFormat.I2 => nameof(SecsFormat.I2),
21 | SecsFormat.I4 => nameof(SecsFormat.I4),
22 | SecsFormat.F8 => nameof(SecsFormat.F8),
23 | SecsFormat.F4 => nameof(SecsFormat.F4),
24 | SecsFormat.U8 => nameof(SecsFormat.U8),
25 | SecsFormat.U1 => nameof(SecsFormat.U1),
26 | SecsFormat.U2 => nameof(SecsFormat.U2),
27 | SecsFormat.U4 => nameof(SecsFormat.U4),
28 | _ => ThrowHelper(format),
29 | };
30 |
31 | [DoesNotReturn]
32 | static string ThrowHelper(SecsFormat format) => throw new ArgumentOutOfRangeException(nameof(format), (int)format, "Invalid enum value");
33 | }
34 |
35 | #if NET
36 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
37 | internal static unsafe Span AsBytes(this scoped ref T value) where T : unmanaged
38 | => MemoryMarshal.CreateSpan(ref Unsafe.As(ref value), sizeof(T));
39 |
40 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
41 | internal static unsafe ReadOnlySpan AsReadOnlyBytes(this scoped ref T value) where T : unmanaged
42 | => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref value), sizeof(T));
43 | #else
44 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
45 | internal static unsafe Span AsBytes(this scoped ref T value) where T : unmanaged
46 | => new(Unsafe.AsPointer(ref value), sizeof(T));
47 |
48 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
49 | internal static unsafe ReadOnlySpan AsReadOnlyBytes(this scoped ref T value) where T : unmanaged
50 | => new(Unsafe.AsPointer(ref value), sizeof(T));
51 | #endif
52 |
53 | #if !NET
54 | internal static async Task WithCancellation(this Task task, CancellationToken cancellationToken)
55 | {
56 | var tcs = new TaskCompletionSource