├── .github
└── workflows
│ └── dotnet.yml
├── .gitignore
├── AdsClient.sln
├── LICENSE
├── README.md
├── icon.png
├── mdsnippets.json
├── samples
└── Samples
│ ├── Program.cs
│ ├── Samples.cs
│ └── Samples.csproj
├── src
├── AdsClient
│ ├── AdsClient.cs
│ ├── Ams.cs
│ ├── AmsSocket.cs
│ ├── AmsSocketConnection.cs
│ ├── CommandResponse
│ │ ├── AdsAddDeviceNotificationCommandResponse.cs
│ │ ├── AdsCommandResponse.cs
│ │ ├── AdsDeleteDeviceNotificationCommandResponse.cs
│ │ ├── AdsReadCommandResponse.cs
│ │ ├── AdsReadDeviceInfoCommandResponse.cs
│ │ ├── AdsReadStateCommandResponse.cs
│ │ ├── AdsWriteCommandResponse.cs
│ │ └── AdsWriteReadCommandResponse.cs
│ ├── Commands
│ │ ├── AdsAddDeviceNotificationCommand.cs
│ │ ├── AdsCommand.cs
│ │ ├── AdsDeleteDeviceNotificationCommand.cs
│ │ ├── AdsReadCommand.cs
│ │ ├── AdsReadDeviceInfoCommand.cs
│ │ ├── AdsReadStateCommand.cs
│ │ ├── AdsWriteCommand.cs
│ │ └── AdsWriteReadCommand.cs
│ ├── Common
│ │ ├── AdsArrayRange.cs
│ │ ├── AdsCommandEnum.cs
│ │ ├── AdsCommandId.cs
│ │ ├── AdsConnectionSettings.cs
│ │ ├── AdsDataType.cs
│ │ ├── AdsDataTypeDto.cs
│ │ ├── AdsDataTypeFlags.cs
│ │ ├── AdsDeviceInfo.cs
│ │ ├── AdsError.cs
│ │ ├── AdsException.cs
│ │ ├── AdsNotification.cs
│ │ ├── AdsNotificationArgs.cs
│ │ ├── AdsReservedIndexGroup.cs
│ │ ├── AdsReservedPort.cs
│ │ ├── AdsState.cs
│ │ ├── AdsSymbol.cs
│ │ ├── AdsTransmissionMode.cs
│ │ ├── AmsNetId.cs
│ │ ├── AmsSocketResponseArgs.cs
│ │ ├── Date.cs
│ │ ├── IAdsConnectionSettings.cs
│ │ └── Time.cs
│ ├── Conversation
│ │ ├── AdsReadRequest.cs
│ │ ├── CreateVariableHandles
│ │ │ ├── AdsCreateVariableHandlesConversation.cs
│ │ │ └── AdsCreateVariableHandlesRequest.cs
│ │ ├── IAdsConversation.cs
│ │ ├── IAdsRequest.cs
│ │ ├── ReadDataTypes
│ │ │ ├── AdsDataTypeParser.cs
│ │ │ └── AdsReadDataTypesConversation.cs
│ │ ├── ReadMultiple
│ │ │ ├── AdsReadMultipleConversation.cs
│ │ │ ├── AdsReadMultipleRequest.cs
│ │ │ ├── AdsReadVariableException.cs
│ │ │ ├── ArrayMultiReadResult.cs
│ │ │ ├── ArrayMultiReadResultFactory.cs
│ │ │ ├── IMultiReadResult.cs
│ │ │ ├── IReadResultFactory.cs
│ │ │ ├── PooledArrayMultiReadResult.cs
│ │ │ ├── PooledArrayMultiReadResultFactory.cs
│ │ │ └── ReadMultipleException.cs
│ │ ├── ReadSymbols
│ │ │ ├── AdsReadSymbolsConversation.cs
│ │ │ └── AdsSymbolParser.cs
│ │ ├── ReadUploadInfo
│ │ │ ├── AdsReadUploadInfoConversation.cs
│ │ │ └── AdsUploadInfoDto.cs
│ │ └── WriteMultiple
│ │ │ ├── AdsWriteMultipleConversation.cs
│ │ │ ├── AdsWriteMultipleRequest.cs
│ │ │ ├── AdsWriteVariableException.cs
│ │ │ └── WriteMultipleException.cs
│ ├── Framework
│ │ ├── EncodingExtensions.cs
│ │ └── IsExternalInit.cs
│ ├── Helpers
│ │ ├── AdsAttribute.cs
│ │ ├── AdsSerializableAttribute.cs
│ │ ├── AmsHeaderHelper.cs
│ │ ├── AmsMessageBuilder.cs
│ │ ├── ByteArrayHelper.cs
│ │ ├── Extensions.cs
│ │ ├── GenericHelper.cs
│ │ ├── IdGenerator.cs
│ │ ├── MemberInfoHelper.cs
│ │ ├── Signal.cs
│ │ └── StringHelper.cs
│ ├── IAmsSocket.cs
│ ├── IIncomingMessageHandler.cs
│ ├── Internal
│ │ ├── Assertions.cs
│ │ ├── EnumExtensions.cs
│ │ ├── LittleEndianDeserializer.cs
│ │ ├── LittleEndianSerializer.cs
│ │ ├── SocketAwaitable.cs
│ │ ├── SocketExtensions.cs
│ │ ├── TypeExtensions.cs
│ │ └── WireFormatting.cs
│ ├── Special
│ │ └── AdsSpecial.cs
│ ├── Variables
│ │ ├── ArraySegmentVariableData.cs
│ │ ├── ArrayVariableData.cs
│ │ ├── IVariableAddress.cs
│ │ ├── IVariableAddressAndSize.cs
│ │ ├── IVariableData.cs
│ │ ├── MemoryVariableData.cs
│ │ ├── VariableAddress.cs
│ │ └── VariableAddressAndSize.cs
│ └── Viscon.Communication.Ads.csproj
└── AdsDumper
│ ├── AdsDumper.csproj
│ └── Program.cs
└── test
└── AdsClient
├── AdsCommandsAsyncTest.cs
└── Viscon.Communication.Ads.Test.csproj
/.github/workflows/dotnet.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
2 |
3 | name: .NET
4 | on:
5 | workflow_dispatch: # Allow running the workflow manually from the GitHub UI
6 | push:
7 | branches:
8 | - 'main' # Run the workflow when pushing to the main branch
9 | pull_request:
10 | branches:
11 | - '*' # Run the workflow for all pull requests
12 | release:
13 | types:
14 | - published # Run the workflow when a new GitHub release is published
15 |
16 | env:
17 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
18 | DOTNET_NOLOGO: true
19 | NuGetDirectory: ${{ github.workspace}}/nuget
20 |
21 | defaults:
22 | run:
23 | shell: pwsh
24 |
25 | jobs:
26 | create_nuget:
27 | runs-on: ubuntu-latest
28 | steps:
29 | - uses: actions/checkout@v3
30 | with:
31 | fetch-depth: 0 # Get all history to allow automatic versioning
32 |
33 | - name: Install GitVersion
34 | uses: gittools/actions/gitversion/setup@v0
35 | with:
36 | versionSpec: '6.x'
37 | includePrerelease: true
38 | preferLatestVersion: true
39 |
40 | - name: Determine Version
41 | id: gitversion
42 | uses: gittools/actions/gitversion/execute@v0
43 |
44 | - name: Setup .NET
45 | uses: actions/setup-dotnet@v3
46 |
47 | - run: >
48 | dotnet pack
49 | --configuration Release
50 | /p:AssemblyVersion=${{ steps.gitversion.outputs.assemblySemVer }}
51 | /p:FileVersion=${{ steps.gitversion.outputs.assemblySemFileVer }}
52 | /p:InformationalVersion=${{ steps.gitversion.outputs.informationalVersion }}
53 | /p:PackageVersion=${{ steps.gitversion.outputs.semVer }}
54 | --output ${{ env.NuGetDirectory }}
55 |
56 | - uses: actions/upload-artifact@v3
57 | with:
58 | name: nuget
59 | if-no-files-found: error
60 | retention-days: 7
61 | path: |
62 | ${{ env.NuGetDirectory }}/*.nupkg
63 | ${{ env.NuGetDirectory }}/*.snupkg
64 |
65 | run_test:
66 | runs-on: ubuntu-latest
67 | steps:
68 | - uses: actions/checkout@v3
69 | - name: Setup .NET
70 | uses: actions/setup-dotnet@v3
71 | - name: Run tests
72 | run: dotnet test --configuration Release
73 |
74 | deploy:
75 | # Publish only when creating a GitHub Release
76 | # https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository
77 | # You can update this logic if you want to manage releases differently
78 | if: github.event_name == 'release'
79 | runs-on: ubuntu-latest
80 | needs: [ create_nuget, run_test ]
81 | steps:
82 | - uses: actions/download-artifact@v3
83 | with:
84 | name: nuget
85 | path: ${{ env.NuGetDirectory }}
86 |
87 | - name: Setup .NET Core
88 | uses: actions/setup-dotnet@v3
89 |
90 | # Publish all NuGet packages to NuGet.org
91 | # Use --skip-duplicate to prevent errors if a package with the same version already exists.
92 | # If you retry a failed workflow, already published packages will be skipped without error.
93 | - name: Publish NuGet package
94 | run: |
95 | foreach($file in (Get-ChildItem "${{ env.NuGetDirectory }}" -Recurse -Include *.nupkg)) {
96 | dotnet nuget push $file --api-key "${{ secrets.NUGET_APIKEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate
97 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | obj
2 | bin
3 | TestResults
4 | *.suo
5 | *.pdb
6 | *.csproj.user
7 | *.userprefs
8 | temp
9 | TestResult.xml
10 | testrunner
11 |
12 | .vs
13 | *.DotSettings.user
14 |
--------------------------------------------------------------------------------
/AdsClient.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{FAE628B5-BAFA-4F21-8174-7E47DCC63E29}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{42A63E66-DB8D-48F6-AA8A-5943080A0202}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdsDumper", "src\AdsDumper\AdsDumper.csproj", "{DA8FC5FD-E7B9-49F4-9B5D-2C6E9EE5DA7F}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Viscon.Communication.Ads", "src\AdsClient\Viscon.Communication.Ads.csproj", "{EC644FCA-386D-431B-AD82-922081D0DA45}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Viscon.Communication.Ads.Test", "test\AdsClient\Viscon.Communication.Ads.Test.csproj", "{C32CE653-CDB2-4354-9174-4EDBD82E769A}"
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{E40E9B0E-4921-4B81-B38A-E17D374E9986}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples", "samples\Samples\Samples.csproj", "{4F4C2736-8474-4738-9228-9FB30D11107E}"
19 | EndProject
20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0568D3C3-8A19-4342-B046-FB961D6DCA57}"
21 | ProjectSection(SolutionItems) = preProject
22 | README.md = README.md
23 | EndProjectSection
24 | EndProject
25 | Global
26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
27 | Debug|Any CPU = Debug|Any CPU
28 | Release|Any CPU = Release|Any CPU
29 | EndGlobalSection
30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
31 | {DA8FC5FD-E7B9-49F4-9B5D-2C6E9EE5DA7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {DA8FC5FD-E7B9-49F4-9B5D-2C6E9EE5DA7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {DA8FC5FD-E7B9-49F4-9B5D-2C6E9EE5DA7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {DA8FC5FD-E7B9-49F4-9B5D-2C6E9EE5DA7F}.Release|Any CPU.Build.0 = Release|Any CPU
35 | {EC644FCA-386D-431B-AD82-922081D0DA45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {EC644FCA-386D-431B-AD82-922081D0DA45}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {EC644FCA-386D-431B-AD82-922081D0DA45}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {EC644FCA-386D-431B-AD82-922081D0DA45}.Release|Any CPU.Build.0 = Release|Any CPU
39 | {C32CE653-CDB2-4354-9174-4EDBD82E769A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40 | {C32CE653-CDB2-4354-9174-4EDBD82E769A}.Debug|Any CPU.Build.0 = Debug|Any CPU
41 | {C32CE653-CDB2-4354-9174-4EDBD82E769A}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {C32CE653-CDB2-4354-9174-4EDBD82E769A}.Release|Any CPU.Build.0 = Release|Any CPU
43 | {4F4C2736-8474-4738-9228-9FB30D11107E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44 | {4F4C2736-8474-4738-9228-9FB30D11107E}.Debug|Any CPU.Build.0 = Debug|Any CPU
45 | {4F4C2736-8474-4738-9228-9FB30D11107E}.Release|Any CPU.ActiveCfg = Release|Any CPU
46 | {4F4C2736-8474-4738-9228-9FB30D11107E}.Release|Any CPU.Build.0 = Release|Any CPU
47 | EndGlobalSection
48 | GlobalSection(SolutionProperties) = preSolution
49 | HideSolutionNode = FALSE
50 | EndGlobalSection
51 | GlobalSection(NestedProjects) = preSolution
52 | {DA8FC5FD-E7B9-49F4-9B5D-2C6E9EE5DA7F} = {FAE628B5-BAFA-4F21-8174-7E47DCC63E29}
53 | {EC644FCA-386D-431B-AD82-922081D0DA45} = {FAE628B5-BAFA-4F21-8174-7E47DCC63E29}
54 | {C32CE653-CDB2-4354-9174-4EDBD82E769A} = {42A63E66-DB8D-48F6-AA8A-5943080A0202}
55 | {4F4C2736-8474-4738-9228-9FB30D11107E} = {E40E9B0E-4921-4B81-B38A-E17D374E9986}
56 | EndGlobalSection
57 | GlobalSection(ExtensibilityGlobals) = postSolution
58 | SolutionGuid = {0528BDEE-A5EE-4879-B57E-35AC1826F92B}
59 | EndGlobalSection
60 | EndGlobal
61 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Inando
2 | Copyright (c) 2023 Viscon Factory Intelligence B.V.
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a
5 | copy of this software and associated documentation files (the "Software"),
6 | to deal in the Software without restriction, including without limitation
7 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 | and/or sell copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included
12 | in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
20 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/VisconFactoryIntelligence/AdsClient/blob/main/LICENSE)
2 | [](https://github.com/VisconFactoryIntelligence/AdsClient/actions/workflows/dotnet.yml)
3 | [](https://www.nuget.org/packages/Viscon.Communication.Ads)
4 |
5 | This is the client implementation of the [Twincat](http://www.beckhoff.com/english.asp?twincat/default.htm) Ads protocol from [Beckhoff](http://http://www.beckhoff.com/).
6 |
7 | The implementation is in C# and targets .NET Framework 4.6.2, .NET Standard 2.0 and .NET Standard 2.1.
8 |
9 | All communication methods are async.
10 |
11 | Contributors
12 | ============
13 | - [Inando](https://github.com/inando)
14 | - [MrCircuit](https://github.com/MrCircuit)
15 | - [mycroes](https://github.com/mycroes)
16 | - [Viscon Factory Intelligence](https://github.com/VisconFactoryIntelligence)
17 |
18 | Getting started
19 | ===============
20 |
21 | Ads Route
22 | ---------
23 |
24 | First you have to give your device/machine the permission to communicate with the Twincat Ads server by adding a route.
25 |
26 | There are different ways of doing this depending on the device.
27 | You can use the Twincat Remote Manager for example.
28 | On a CX9001 device you can connect with cerhost.exe and add a route with
29 | \Hard Disk\System\TcAmsRemoteMgr.exe
30 | (You may not to reboot after this!)
31 |
32 | *If the library is not working, an incorrect/missing route may be the problem!.*
33 |
34 | Installation
35 | ------------
36 | You only need this library.
37 | Twincat is _not_ needed.
38 | It will not work if you have programs like system manager or PLC control running.
39 |
40 | The package is available from [NuGet](https://www.nuget.org/packages/Viscon.Communication.Ads).
41 |
42 | Examples
43 | ========
44 |
45 | ## Connect to the PLC
46 |
47 |
48 |
49 | ```cs
50 | using var client = new AdsClient(amsNetIdSource: "10.0.0.120.1.1", ipTarget: "10.0.0.2",
51 | amsNetIdTarget: "10.0.0.2.1.1");
52 |
53 | await client.Ams.ConnectAsync();
54 | ```
55 | snippet source | anchor
56 |
57 |
58 | ## Read device info
59 |
60 |
61 |
62 | ```cs
63 | AdsDeviceInfo deviceInfo = await client.ReadDeviceInfoAsync();
64 | Console.WriteLine(deviceInfo.ToString());
65 | ```
66 | snippet source | anchor
67 |
68 |
69 | ## Read/Write a variable by name
70 |
71 |
72 |
73 | ```cs
74 | var varHandle = await client.GetSymhandleByNameAsync(".TestVar");
75 | await client.WriteAsync(varHandle, 0);
76 | var value = await client.ReadAsync(varHandle);
77 | await client.ReleaseSymhandleAsync(varHandle);
78 | ```
79 | snippet source | anchor
80 |
81 |
82 | You can also use the AdsCommands directly if you need to write directly with IndexGroup/IndexOffset
83 |
84 | ## Working with notifications
85 |
86 |
87 |
88 | ```cs
89 | client.OnNotification += (sender, e) => { Console.WriteLine(e.Notification.ToString()); };
90 | var varHandle1 = await client.GetSymhandleByNameAsync(".VarTest1");
91 | var varHandle2 = await client.GetSymhandleByNameAsync(".VarTest2");
92 | var notificationHandle1 = await client.AddNotificationAsync(varHandle1, AdsTransmissionMode.Cyclic, 2000, null);
93 | var notificationHandle2 = await client.AddNotificationAsync(varHandle2, AdsTransmissionMode.OnChange, 10, null);
94 | ```
95 | snippet source | anchor
96 |
97 |
98 | ## Simple example with most basic functions
99 |
100 | Here is a sample which shows usage of most basic functions.
101 |
102 |
103 |
104 | ```cs
105 | using Viscon.Communication.Ads;
106 | using Viscon.Communication.Ads.Common;
107 |
108 | namespace Samples;
109 |
110 | public static class Program
111 | {
112 | static async Task Main()
113 | {
114 | var timeout = Task.Delay(10000);
115 | var task = await Task.WhenAny(RunTestAsync(), timeout);
116 | if (task == timeout)
117 | {
118 | Console.Error.WriteLine("Operation timed out!");
119 | }
120 | else
121 | {
122 | Console.WriteLine("Done!");
123 | }
124 | }
125 |
126 | private static async Task RunTestAsync()
127 | {
128 | using var client = new AdsClient(
129 | amsNetIdSource:"192.168.5.6.1.1",
130 | ipTarget:"192.168.3.4",
131 | amsNetIdTarget:"192.168.3.4.1.1");
132 |
133 | await client.Ams.ConnectAsync();
134 |
135 | var deviceInfo = await client.ReadDeviceInfoAsync();
136 | Console.WriteLine($"Device info: {deviceInfo}");
137 |
138 | var state = await client.ReadStateAsync();
139 | Console.WriteLine($"State: {state}");
140 |
141 | client.OnNotification += (sender,e) => {
142 | Console.WriteLine(e.Notification.ToString());
143 | };
144 |
145 | var varHandle1 = await client.GetSymhandleByNameAsync(".VariableName1");
146 | Console.WriteLine($"Variable1 handle: {varHandle1}");
147 |
148 | var varHandle2 = await client.GetSymhandleByNameAsync(".VariableName2");
149 | Console.WriteLine($"Variable2 handle: {varHandle2}");
150 |
151 | var notification1Handle = await client.AddNotificationAsync(
152 | varHandle1, AdsTransmissionMode.Cyclic, 5000, null);
153 | var notification2Handle = await client.AddNotificationAsync(
154 | varHandle2, AdsTransmissionMode.OnChange, 10, null);
155 |
156 | var value = await client.ReadAsync(varHandle1);
157 | Console.WriteLine($"Value before write: {value}");
158 |
159 | await client.WriteAsync(varHandle1, 1);
160 | Console.WriteLine("I turned something on");
161 |
162 | value = await client.ReadAsync(varHandle1);
163 | Console.WriteLine($"Value after write: {value}");
164 |
165 | await Task.Delay(5000);
166 |
167 | await client.WriteAsync(varHandle1, 0);
168 | Console.WriteLine("I turned something off");
169 |
170 | Console.WriteLine("Deleting active notifications...");
171 | await client.DeleteActiveNotificationsAsync();
172 | }
173 | }
174 | ```
175 | snippet source | anchor
176 |
177 |
178 | ## Using commands directly
179 |
180 |
181 |
182 | ```cs
183 | var stateCmd = new AdsReadStateCommand();
184 | var state = (await stateCmd.RunAsync(client.Ams, CancellationToken.None)).AdsState.ToString();
185 | Console.WriteLine($"State: {state}");
186 | ```
187 | snippet source | anchor
188 |
189 |
190 | ## Serialize to class
191 |
192 | It's possible to read directly to a class or write from a class.
193 | You need to set the AdsSerializable attribute on the class and the Ads attribute on the fields/properties you need.
194 | The fields without the Ads attribute are ignored.
195 |
196 |
197 |
198 | ```cs
199 | [AdsSerializable]
200 | public class TestClass
201 | {
202 | [Ads]
203 | public ushort Var1 { get; set; }
204 |
205 | [Ads]
206 | public byte Var2 { get; set; }
207 | }
208 | ```
209 | snippet source | anchor
210 |
211 |
212 |
213 |
214 | ```cs
215 | var handle = await client.GetSymhandleByNameAsync(".Test");
216 | var testInstance = await client.ReadAsync(handle);
217 | await client.WriteAsync(handle, testInstance);
218 | ```
219 | snippet source | anchor
220 |
221 |
222 | This is an example struct in Twincat:
223 | ```
224 | TYPE TestStruct :
225 | STRUCT
226 | Var1 : INT;
227 | Var2 : BYTE;
228 | END_STRUCT
229 | END_TYPE
230 | ```
231 |
232 | ## Special functions
233 |
234 | These functions aren't documented by Beckhoff:
235 |
236 | ### Get target description
237 |
238 |
239 |
240 | ```cs
241 | var xml = await client.Special.GetTargetDescAsync();
242 | xml = XDocument.Parse(xml).ToString();
243 | ```
244 | snippet source | anchor
245 |
246 |
247 | Credits, sources and inspiration
248 | ================================
249 | * [ads-client NodeJS library by Jussi Isotalo](https://github.com/jisotalo/ads-client)
250 | * [RabbitMQ .NET Client](https://github.com/rabbitmq/rabbitmq-dotnet-client)
251 | * [Sally7](https://github.com/mycroes/Sally7)
252 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VisconFactoryIntelligence/AdsClient/16abe22dc9c2067dbd9bf94de469c874c141fcfc/icon.png
--------------------------------------------------------------------------------
/mdsnippets.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json",
3 | "Convention": "InPlaceOverwrite"
4 | }
--------------------------------------------------------------------------------
/samples/Samples/Program.cs:
--------------------------------------------------------------------------------
1 | // begin-snippet: Program
2 | using Viscon.Communication.Ads;
3 | using Viscon.Communication.Ads.Common;
4 |
5 | namespace Samples;
6 |
7 | public static class Program
8 | {
9 | static async Task Main()
10 | {
11 | var timeout = Task.Delay(10000);
12 | var task = await Task.WhenAny(RunTestAsync(), timeout);
13 | if (task == timeout)
14 | {
15 | Console.Error.WriteLine("Operation timed out!");
16 | }
17 | else
18 | {
19 | Console.WriteLine("Done!");
20 | }
21 | }
22 |
23 | private static async Task RunTestAsync()
24 | {
25 | using var client = new AdsClient(
26 | amsNetIdSource:"192.168.5.6.1.1",
27 | ipTarget:"192.168.3.4",
28 | amsNetIdTarget:"192.168.3.4.1.1");
29 |
30 | await client.Ams.ConnectAsync();
31 |
32 | var deviceInfo = await client.ReadDeviceInfoAsync();
33 | Console.WriteLine($"Device info: {deviceInfo}");
34 |
35 | var state = await client.ReadStateAsync();
36 | Console.WriteLine($"State: {state}");
37 |
38 | client.OnNotification += (sender,e) => {
39 | Console.WriteLine(e.Notification.ToString());
40 | };
41 |
42 | var varHandle1 = await client.GetSymhandleByNameAsync(".VariableName1");
43 | Console.WriteLine($"Variable1 handle: {varHandle1}");
44 |
45 | var varHandle2 = await client.GetSymhandleByNameAsync(".VariableName2");
46 | Console.WriteLine($"Variable2 handle: {varHandle2}");
47 |
48 | var notification1Handle = await client.AddNotificationAsync(
49 | varHandle1, AdsTransmissionMode.Cyclic, 5000, null);
50 | var notification2Handle = await client.AddNotificationAsync(
51 | varHandle2, AdsTransmissionMode.OnChange, 10, null);
52 |
53 | var value = await client.ReadAsync(varHandle1);
54 | Console.WriteLine($"Value before write: {value}");
55 |
56 | await client.WriteAsync(varHandle1, 1);
57 | Console.WriteLine("I turned something on");
58 |
59 | value = await client.ReadAsync(varHandle1);
60 | Console.WriteLine($"Value after write: {value}");
61 |
62 | await Task.Delay(5000);
63 |
64 | await client.WriteAsync(varHandle1, 0);
65 | Console.WriteLine("I turned something off");
66 |
67 | Console.WriteLine("Deleting active notifications...");
68 | await client.DeleteActiveNotificationsAsync();
69 | }
70 | }
71 | // end-snippet
--------------------------------------------------------------------------------
/samples/Samples/Samples.cs:
--------------------------------------------------------------------------------
1 | using System.Xml.Linq;
2 | using Viscon.Communication.Ads;
3 | using Viscon.Communication.Ads.Commands;
4 | using Viscon.Communication.Ads.Common;
5 | using Viscon.Communication.Ads.Helpers;
6 |
7 | namespace Samples;
8 |
9 | public class Samples
10 | {
11 | public async Task Sample()
12 | {
13 | // begin-snippet: Connect
14 | using var client = new AdsClient(amsNetIdSource: "10.0.0.120.1.1", ipTarget: "10.0.0.2",
15 | amsNetIdTarget: "10.0.0.2.1.1");
16 |
17 | await client.Ams.ConnectAsync();
18 | // end-snippet
19 |
20 | // begin-snippet: ReadDeviceInfoAsync
21 | AdsDeviceInfo deviceInfo = await client.ReadDeviceInfoAsync();
22 | Console.WriteLine(deviceInfo.ToString());
23 | // end-snippet
24 |
25 | // begin-snippet: ReadWriteVariableByName
26 | var varHandle = await client.GetSymhandleByNameAsync(".TestVar");
27 | await client.WriteAsync(varHandle, 0);
28 | var value = await client.ReadAsync(varHandle);
29 | await client.ReleaseSymhandleAsync(varHandle);
30 | // end-snippet
31 |
32 | // begin-snippet: WorkingWithNotifications
33 | client.OnNotification += (sender, e) => { Console.WriteLine(e.Notification.ToString()); };
34 | var varHandle1 = await client.GetSymhandleByNameAsync(".VarTest1");
35 | var varHandle2 = await client.GetSymhandleByNameAsync(".VarTest2");
36 | var notificationHandle1 = await client.AddNotificationAsync(varHandle1, AdsTransmissionMode.Cyclic, 2000, null);
37 | var notificationHandle2 = await client.AddNotificationAsync(varHandle2, AdsTransmissionMode.OnChange, 10, null);
38 | // end-snippet
39 |
40 | // begin-snippet: UsingCommands
41 | var stateCmd = new AdsReadStateCommand();
42 | var state = (await stateCmd.RunAsync(client.Ams, CancellationToken.None)).AdsState.ToString();
43 | Console.WriteLine($"State: {state}");
44 | // end-snippet
45 |
46 | // begin-snippet: ReadTestClass
47 | var handle = await client.GetSymhandleByNameAsync(".Test");
48 | var testInstance = await client.ReadAsync(handle);
49 | await client.WriteAsync(handle, testInstance);
50 | // end-snippet
51 |
52 | // begin-snippet: GetTargetDesc
53 | var xml = await client.Special.GetTargetDescAsync();
54 | xml = XDocument.Parse(xml).ToString();
55 | // end-snippet
56 | }
57 |
58 | // begin-snippet: TestClass
59 | [AdsSerializable]
60 | public class TestClass
61 | {
62 | [Ads]
63 | public ushort Var1 { get; set; }
64 |
65 | [Ads]
66 | public byte Var2 { get; set; }
67 | }
68 | // end-snippet
69 | }
--------------------------------------------------------------------------------
/samples/Samples/Samples.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | exe
6 | enable
7 | enable
8 | false
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | runtime; build; native; contentfiles; analyzers; buildtransitive
18 | all
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/AdsClient/Ams.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 | using System.Collections.Concurrent;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using System.Linq;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using Viscon.Communication.Ads.Common;
10 | using Viscon.Communication.Ads.Conversation;
11 | using Viscon.Communication.Ads.Helpers;
12 | using Viscon.Communication.Ads.Internal;
13 |
14 | namespace Viscon.Communication.Ads
15 | {
16 |
17 | public class Ams : IDisposable
18 | {
19 | private readonly IdGenerator invokeIdGenerator = new();
20 | private readonly Signal sendSignal = new();
21 |
22 | private readonly ConcurrentDictionary> pendingInvocations = new();
23 |
24 | public Ams(IAmsSocket amsSocket)
25 | {
26 | AmsSocket = amsSocket;
27 | NotificationRequests = new List();
28 | }
29 |
30 | public IAmsSocket AmsSocket { get; }
31 |
32 | internal ushort AmsPortTarget { get; set; }
33 | internal AmsNetId AmsNetIdTarget { get; set; }
34 | internal AmsNetId AmsNetIdSource { get; set; }
35 | internal List NotificationRequests;
36 |
37 |
38 | ///
39 | /// Ams source port. Default of 32905
40 | ///
41 | public ushort AmsPortSource { get; set; } = 32905;
42 |
43 | public Task ConnectAsync(CancellationToken cancellationToken = default)
44 | {
45 | return AmsSocket.ConnectAsync(new MessageHandler(this), cancellationToken);
46 | }
47 |
48 | public void Dispose()
49 | {
50 | CloseConnection();
51 | (AmsSocket as IDisposable)?.Dispose();
52 | sendSignal.Dispose();
53 | }
54 |
55 | private void CloseConnection()
56 | {
57 | AmsSocket.Close();
58 | }
59 |
60 | internal event AdsNotificationDelegate OnNotification;
61 |
62 | internal Task PerformRequestAsync(IAdsConversation conversation,
63 | CancellationToken cancellationToken) where TRequest : struct, IAdsRequest =>
64 | PerformRequestAsync(conversation, AmsPortTarget, cancellationToken);
65 |
66 | internal async Task PerformRequestAsync(
67 | IAdsConversation conversation, ushort amsPortTarget, CancellationToken cancellationToken) where TRequest : struct, IAdsRequest
68 | {
69 | static void WriteRequest(Span span, ref TRequest request, int requestedLength, Ams ams, ushort amsPortTarget, uint invokeId)
70 | {
71 | var len = request.BuildRequest(span.Slice(AmsHeaderHelper.AmsTcpHeaderSize + AmsHeaderHelper.AmsHeaderSize));
72 | Debug.Assert(len == requestedLength, $"Serialized to wrong size, expect {requestedLength}, actual {len}");
73 |
74 | AmsMessageBuilder.WriteHeader(span, ams, request.Command, amsPortTarget, invokeId, len);
75 | }
76 |
77 | static TResult HandleResponse(IAdsConversation conversation, ReadOnlySpan span)
78 | {
79 | var offset = WireFormatting.ReadUInt32(span.Slice(AmsHeaderHelper.AmsHeaderSize), out var errorCode);
80 |
81 | // Needs some work on returning the buffer in case of exception.
82 |
83 | if (errorCode != 0) throw new AdsException(errorCode);
84 |
85 | WireFormatting.ReadInt32(span.Slice(AmsHeaderHelper.AmsDataLengthOffset), out var dataLength);
86 |
87 | // Error is already processed, so omit it from returned data.
88 | return conversation.ParseResponse(span.Slice(AmsHeaderHelper.AmsHeaderSize + offset,
89 | dataLength - offset));
90 | }
91 |
92 | if (!AmsSocket.Connected) throw new InvalidOperationException("Not connected to PLC.");
93 |
94 | var tcs = new TaskCompletionSource();
95 | using var cancelTcs = cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken));
96 |
97 | uint invokeId;
98 | do
99 | {
100 | invokeId = invokeIdGenerator.Next();
101 | } while (!pendingInvocations.TryAdd(invokeId, tcs));
102 |
103 | using var cancelPendingInvocation =
104 | cancellationToken.Register(() => pendingInvocations.TryRemove(invokeId, out _));
105 |
106 | try
107 | {
108 | // Avoid message building if already cancelled.
109 | cancellationToken.ThrowIfCancellationRequested();
110 |
111 | var request = conversation.BuildRequest();
112 | var requestedLength = request.GetRequestLength();
113 | var buffer = ArrayPool.Shared.Rent(AmsHeaderHelper.AmsTcpHeaderSize +
114 | AmsHeaderHelper.AmsHeaderSize + requestedLength);
115 | try
116 | {
117 | WriteRequest(buffer, ref request, requestedLength, this, amsPortTarget, invokeId);
118 |
119 | _ = await sendSignal.WaitAsync(cancellationToken).ConfigureAwait(false);
120 | try
121 | {
122 | // Avoid request sending if already cancelled. Some time might have elapsed waiting for the signal.
123 | cancellationToken.ThrowIfCancellationRequested();
124 | await AmsSocket.SendAsync(new ArraySegment(buffer, 0,
125 | AmsHeaderHelper.AmsTcpHeaderSize + AmsHeaderHelper.AmsHeaderSize + requestedLength))
126 | .ConfigureAwait(false);
127 | }
128 | finally
129 | {
130 | if (!sendSignal.TryRelease())
131 | {
132 | throw new Exception("Failed to release the send signal.");
133 | }
134 | }
135 | }
136 | finally
137 | {
138 | ArrayPool.Shared.Return(buffer);
139 | }
140 | }
141 | catch
142 | {
143 | pendingInvocations.TryRemove(invokeId, out _);
144 | throw;
145 | }
146 |
147 | var responseBytes = await tcs.Task.ConfigureAwait(false);
148 | cancellationToken.ThrowIfCancellationRequested();
149 |
150 | return HandleResponse(conversation, responseBytes);
151 | }
152 |
153 | private void HandleException(Exception exception)
154 | {
155 | foreach (var id in pendingInvocations.Keys)
156 | {
157 | if (pendingInvocations.TryRemove(id, out var tcs))
158 | {
159 | tcs.TrySetException(exception);
160 | }
161 | }
162 |
163 | CloseConnection();
164 | }
165 |
166 | private void HandleMessage(byte[] message)
167 | {
168 | uint amsErrorCode = AmsHeaderHelper.GetErrorCode(message);
169 | uint invokeId = AmsHeaderHelper.GetInvokeId(message);
170 | bool isNotification = (AmsHeaderHelper.GetCommandId(message) == AdsCommandId.DeviceNotification);
171 |
172 | if (AmsPortTarget != AmsHeaderHelper.GetAmsPortSource(message)) return;
173 | if (!AmsNetIdTarget.Bytes.SequenceEqual(AmsHeaderHelper.GetAmsNetIdSource(message))) return;
174 |
175 | //If notification then just start the callback
176 | if (isNotification && (OnNotification != null))
177 | {
178 | var notifications = AdsNotification.GetNotifications(message);
179 | foreach (var notification in notifications)
180 | {
181 | var notificationRequest = NotificationRequests.FirstOrDefault(n => n.NotificationHandle == notification.NotificationHandle);
182 | if (notificationRequest != null)
183 | {
184 | notificationRequest.ByteValue = notification.ByteValue;
185 |
186 | OnNotification(null, new AdsNotificationArgs(notificationRequest));
187 | }
188 | }
189 | }
190 |
191 | //If not a notification then find the original command and call async callback
192 | if (!isNotification)
193 | {
194 | if (!pendingInvocations.TryRemove(invokeId, out var adsCommandResult))
195 | {
196 | // Unknown or timed-out request
197 | return;
198 | }
199 |
200 | if (amsErrorCode > 0)
201 | {
202 | adsCommandResult.TrySetException(new AdsException(amsErrorCode));
203 | }
204 | else
205 | {
206 | adsCommandResult.TrySetResult(message);
207 | }
208 | }
209 | }
210 |
211 | private class MessageHandler : IIncomingMessageHandler
212 | {
213 | private readonly Ams ams;
214 |
215 | public MessageHandler(Ams ams) => this.ams = ams;
216 |
217 | public void HandleException(Exception exception)
218 | {
219 | ams.HandleException(exception);
220 | }
221 |
222 | public void HandleMessage(byte[] message)
223 | {
224 | ams.HandleMessage(message);
225 | }
226 | }
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/src/AdsClient/AmsSocket.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Sockets;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Viscon.Communication.Ads.Internal;
6 |
7 | namespace Viscon.Communication.Ads
8 | {
9 | internal class AmsSocket : IAmsSocket, IDisposable
10 | {
11 | public AmsSocket(string host, int port = 48898)
12 | {
13 | Host = host;
14 | Port = port;
15 |
16 | TcpClient = new TcpClient { NoDelay = true };
17 | }
18 |
19 | public TcpClient TcpClient { get; }
20 | public Socket Socket => TcpClient.Client;
21 |
22 | public string Host { get; }
23 | public int Port { get; }
24 |
25 | ///
26 | public bool Connected => TcpClient.Connected;
27 |
28 | private AmsSocketConnection connection;
29 | private SocketAwaitable socketAwaitable;
30 |
31 | public void Close()
32 | {
33 | connection?.Close();
34 | }
35 |
36 | public async Task ConnectAsync(IIncomingMessageHandler messageHandler, CancellationToken cancellationToken = default)
37 | {
38 | if (connection is not null) throw new InvalidOperationException("Connection was already established.");
39 |
40 | using (cancellationToken.Register(state => ((Socket)state).Close(), Socket))
41 | {
42 | try
43 | {
44 | await TcpClient.ConnectAsync(Host, Port).ConfigureAwait(false);
45 | }
46 | catch (Exception) when (cancellationToken.IsCancellationRequested)
47 | {
48 | // The exception handling is quite generic, but exceptions thrown differ across target frameworks.
49 | // (See https://stackoverflow.com/a/66656805/1085457)
50 | // This is probably not something to worry about, since apparently cancellation was requested anyway.
51 | cancellationToken.ThrowIfCancellationRequested();
52 | }
53 | }
54 |
55 | connection = new AmsSocketConnection(TcpClient.Client, messageHandler);
56 | }
57 |
58 | public async Task SendAsync(byte[] message)
59 | {
60 | var sa = Interlocked.Exchange(ref socketAwaitable, null) ?? new SocketAwaitable();
61 | sa.SetBuffer(message, 0, message.Length);
62 |
63 | await Socket.SendAwaitable(sa);
64 |
65 | if (Interlocked.CompareExchange(ref socketAwaitable, sa, null) != null)
66 | {
67 | sa.Dispose();
68 | }
69 | }
70 |
71 | public async Task SendAsync(ArraySegment buffer)
72 | {
73 | var sa = Interlocked.Exchange(ref socketAwaitable, null) ?? new SocketAwaitable();
74 | sa.SetBuffer(buffer.Array, buffer.Offset, buffer.Count);
75 |
76 | await Socket.SendAwaitable(sa);
77 |
78 | if (Interlocked.CompareExchange(ref socketAwaitable, sa, null) != null)
79 | {
80 | sa.Dispose();
81 | }
82 | }
83 |
84 | void IDisposable.Dispose()
85 | {
86 | Close();
87 | TcpClient?.Dispose();
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/AdsClient/AmsSocketConnection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Sockets;
3 | using System.Threading.Tasks;
4 | using Viscon.Communication.Ads.Helpers;
5 | using Viscon.Communication.Ads.Internal;
6 |
7 | namespace Viscon.Communication.Ads;
8 |
9 | internal sealed class AmsSocketConnection
10 | {
11 | private const int ReceiveTaskTimeout = 3000;
12 |
13 | private readonly Socket socket;
14 | private readonly IIncomingMessageHandler messageHandler;
15 | private readonly Task receiveTask;
16 | private readonly SocketAwaitable socketAwaitable = new SocketAwaitable();
17 | private readonly byte[] headerBuffer = new byte[AmsHeaderHelper.AmsTcpHeaderSize];
18 |
19 | public AmsSocketConnection(Socket socket, IIncomingMessageHandler messageHandler)
20 | {
21 | this.socket = socket;
22 | this.messageHandler = messageHandler;
23 | receiveTask = ReceiveLoop();
24 | }
25 |
26 | private volatile bool closed;
27 |
28 | public void Close()
29 | {
30 | closed = true;
31 | socket.Shutdown(SocketShutdown.Both);
32 | socket.Close();
33 |
34 | receiveTask.Wait(ReceiveTaskTimeout);
35 | socketAwaitable.Dispose();
36 | }
37 |
38 | private async Task GetAmsMessage(byte[] tcpHeader)
39 | {
40 | uint responseLength = AmsHeaderHelper.GetResponseLength(tcpHeader);
41 | byte[] response = new byte[responseLength];
42 |
43 | await GetMessage(response);
44 |
45 | return response;
46 | }
47 |
48 | private Task GetMessage(byte[] response)
49 | {
50 | return ReceiveAsync(response);
51 | }
52 |
53 | private async Task Listen()
54 | {
55 | try
56 | {
57 | var buffer = await ListenForHeader();
58 | // If a ams header is received, then read the rest
59 | try
60 | {
61 | byte[] response = await GetAmsMessage(buffer);
62 |
63 | #if DEBUG_AMS
64 | Debug.WriteLine("Received bytes: " + ByteArrayHelper.ByteArrayToTestString(buffer) + ',' +
65 | ByteArrayHelper.ByteArrayToTestString(response));
66 | #endif
67 |
68 | messageHandler.HandleMessage(response);
69 | }
70 | catch (Exception ex)
71 | {
72 | messageHandler.HandleException(ex);
73 | }
74 | }
75 | catch (Exception ex)
76 | {
77 | if (!ReferenceEquals(ex.GetType(), typeof(ObjectDisposedException))) throw;
78 | }
79 | }
80 |
81 | private async Task ListenForHeader()
82 | {
83 | await ReceiveAsync(headerBuffer);
84 | return headerBuffer;
85 | }
86 |
87 | private async Task ReceiveAsync(byte[] buffer)
88 | {
89 | var sa = socketAwaitable;
90 | sa.SetBuffer(buffer, 0, buffer.Length);
91 |
92 | do
93 | {
94 | sa.SetBuffer(sa.Offset + sa.BytesTransferred, sa.Count - sa.BytesTransferred);
95 | await socket.ReceiveAwaitable(sa);
96 |
97 | if (sa.BytesTransferred == 0)
98 | {
99 | messageHandler.HandleException(new Exception("Remote host closed the connection."));
100 | Close();
101 | }
102 | } while (socketAwaitable.BytesTransferred != buffer.Length);
103 | }
104 |
105 | private async Task ReceiveLoop()
106 | {
107 | await Task.Yield();
108 |
109 | while (!closed)
110 | {
111 | await Listen();
112 | }
113 | }
114 | }
--------------------------------------------------------------------------------
/src/AdsClient/CommandResponse/AdsAddDeviceNotificationCommandResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.CommandResponse
4 | {
5 | public class AdsAddDeviceNotificationCommandResponse : AdsCommandResponse
6 | {
7 | private uint notificationHandle;
8 | public uint NotificationHandle
9 | {
10 | get { return notificationHandle; }
11 | }
12 |
13 |
14 | protected override void AdsResponseIsChanged()
15 | {
16 | notificationHandle = BitConverter.ToUInt32(this.AdsResponse, 4);
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/AdsClient/CommandResponse/AdsCommandResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.CommandResponse
4 | {
5 | public class AdsCommandResponse
6 | {
7 | public AdsCommandResponse()
8 | {
9 | }
10 |
11 | internal byte[] AdsResponse { get; set; }
12 |
13 | internal uint CommandInvokeId { get; set; }
14 |
15 | internal void SetResponse(byte[] adsresponseInclAmsHeader)
16 | {
17 | //32 amsheader + data
18 | int datalength = BitConverter.ToInt32(adsresponseInclAmsHeader, 20);
19 |
20 | this.AdsResponse = new byte[datalength];
21 | Array.Copy(adsresponseInclAmsHeader, 32, this.AdsResponse, 0, datalength);
22 |
23 | errorCode = GetErrorCode();
24 |
25 | AdsResponseIsChanged();
26 | }
27 |
28 | protected virtual void AdsResponseIsChanged()
29 | {
30 | }
31 |
32 | public void ProcessResponse() => AdsResponseIsChanged();
33 |
34 | private uint errorCode;
35 | public uint AdsErrorCode
36 | {
37 | get { return errorCode; }
38 | }
39 |
40 | internal uint AmsErrorCode
41 | {
42 | get { return errorCode; }
43 | set { errorCode = value; }
44 | }
45 |
46 | protected virtual uint GetErrorCode()
47 | {
48 | return BitConverter.ToUInt32(AdsResponse, 0);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/AdsClient/CommandResponse/AdsDeleteDeviceNotificationCommandResponse.cs:
--------------------------------------------------------------------------------
1 | namespace Viscon.Communication.Ads.CommandResponse
2 | {
3 | public class AdsDeleteDeviceNotificationCommandResponse : AdsCommandResponse
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/AdsClient/CommandResponse/AdsReadCommandResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.CommandResponse
4 | {
5 | public class AdsReadCommandResponse : AdsCommandResponse
6 | {
7 | private byte[] data;
8 | public byte[] Data
9 | {
10 | get { return data; }
11 | }
12 |
13 |
14 | protected override void AdsResponseIsChanged()
15 | {
16 | uint dataLength = BitConverter.ToUInt32(this.AdsResponse, 4);
17 | data = new byte[dataLength];
18 | Array.Copy(AdsResponse, 8, data, 0, (int)dataLength);
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/AdsClient/CommandResponse/AdsReadDeviceInfoCommandResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Viscon.Communication.Ads.Common;
3 | using Viscon.Communication.Ads.Helpers;
4 |
5 | namespace Viscon.Communication.Ads.CommandResponse
6 | {
7 | public class AdsReadDeviceInfoCommandResponse : AdsCommandResponse
8 | {
9 | private AdsDeviceInfo adsDeviceInfo;
10 | public AdsDeviceInfo AdsDeviceInfo
11 | {
12 | get { return adsDeviceInfo; }
13 | }
14 |
15 | protected override void AdsResponseIsChanged()
16 | {
17 | adsDeviceInfo = new AdsDeviceInfo();
18 | adsDeviceInfo.MajorVersion = this.AdsResponse[4];
19 | adsDeviceInfo.MinorVersion = this.AdsResponse[5];
20 | adsDeviceInfo.VersionBuild = BitConverter.ToUInt16(this.AdsResponse, 6);
21 | var deviceNameArray = new byte[16];
22 | Array.Copy(this.AdsResponse, 8, deviceNameArray, 0, 16);
23 | adsDeviceInfo.DeviceName = ByteArrayHelper.ByteArrayToString(deviceNameArray);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/AdsClient/CommandResponse/AdsReadStateCommandResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Viscon.Communication.Ads.Common;
3 |
4 | namespace Viscon.Communication.Ads.CommandResponse
5 | {
6 | public class AdsReadStateCommandResponse : AdsCommandResponse
7 | {
8 | private AdsState adsState;
9 | public AdsState AdsState
10 | {
11 | get { return adsState; }
12 | }
13 |
14 | protected override void AdsResponseIsChanged()
15 | {
16 | adsState = new AdsState();
17 | adsState.State = BitConverter.ToUInt16(this.AdsResponse, 4);
18 | adsState.DeviceState = BitConverter.ToUInt16(this.AdsResponse, 6);
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/AdsClient/CommandResponse/AdsWriteCommandResponse.cs:
--------------------------------------------------------------------------------
1 | namespace Viscon.Communication.Ads.CommandResponse
2 | {
3 | public class AdsWriteCommandResponse : AdsCommandResponse
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/AdsClient/CommandResponse/AdsWriteReadCommandResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.CommandResponse
4 | {
5 | public class AdsWriteReadCommandResponse : AdsCommandResponse
6 | {
7 | private byte[] data;
8 | public byte[] Data
9 | {
10 | get { return data; }
11 | }
12 |
13 |
14 | protected override void AdsResponseIsChanged()
15 | {
16 | uint dataLength = BitConverter.ToUInt16(this.AdsResponse, 4);
17 | data = new byte[dataLength];
18 | Array.Copy(AdsResponse, 8, data, 0, (int)dataLength);
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/AdsClient/Commands/AdsAddDeviceNotificationCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Viscon.Communication.Ads.CommandResponse;
5 | using Viscon.Communication.Ads.Common;
6 |
7 | namespace Viscon.Communication.Ads.Commands
8 | {
9 | public class AdsAddDeviceNotificationCommand : AdsCommand
10 | {
11 | public AdsAddDeviceNotificationCommand(uint indexGroup, uint indexOffset, uint readLength, AdsTransmissionMode transmissionMode)
12 | : base(AdsCommandId.AddDeviceNotification)
13 | {
14 | this.readLength = readLength;
15 | this.indexGroup = indexGroup;
16 | this.indexOffset = indexOffset;
17 | this.transmissionMode = transmissionMode;
18 | this.notification = new AdsNotification();
19 |
20 | }
21 |
22 | private AdsNotification notification;
23 | public AdsNotification Notification
24 | {
25 | get { return notification; }
26 | }
27 |
28 | public object UserData
29 | {
30 | get { return notification.UserData; }
31 | set { notification.UserData = value; }
32 | }
33 |
34 | public Type TypeOfValue
35 | {
36 | get { return notification.TypeOfValue; }
37 | set { notification.TypeOfValue = value; }
38 | }
39 |
40 | private AdsTransmissionMode transmissionMode;
41 | private uint readLength;
42 | private uint indexOffset;
43 | private uint indexGroup;
44 |
45 | public uint MaxDelay { get; set; }
46 |
47 | private uint cycleTime;
48 | public uint CycleTime
49 | {
50 | get { return cycleTime/10000; }
51 | set { cycleTime = value*10000; }
52 | }
53 |
54 | internal override IEnumerable GetBytes()
55 | {
56 | IEnumerable data = BitConverter.GetBytes(indexGroup);
57 | data = data.Concat(BitConverter.GetBytes(indexOffset));
58 | data = data.Concat(BitConverter.GetBytes(readLength));
59 | data = data.Concat(BitConverter.GetBytes((uint)transmissionMode));
60 | data = data.Concat(BitConverter.GetBytes(MaxDelay));
61 | data = data.Concat(BitConverter.GetBytes(cycleTime));
62 | data = data.Concat(BitConverter.GetBytes((UInt64)0));
63 | data = data.Concat(BitConverter.GetBytes((UInt64)0));
64 | return data;
65 | }
66 |
67 | protected override void RunBefore(Ams ams)
68 | {
69 | ams.NotificationRequests.Add(Notification);
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/AdsClient/Commands/AdsCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using Viscon.Communication.Ads.CommandResponse;
7 | using Viscon.Communication.Ads.Common;
8 | using Viscon.Communication.Ads.Conversation;
9 |
10 | namespace Viscon.Communication.Ads.Commands
11 | {
12 | public abstract class AdsCommand where TResponse : AdsCommandResponse, new()
13 | {
14 | public AdsCommand(ushort commandId)
15 | {
16 | this.commandId = commandId;
17 | }
18 |
19 |
20 | private ushort commandId;
21 | public ushort CommandId
22 | {
23 | get { return commandId; }
24 | }
25 |
26 | public ushort? AmsPortTargetOverride { get; set; }
27 |
28 | protected virtual void RunBefore(Ams ams)
29 | {
30 | }
31 |
32 | protected virtual void RunAfter(Ams ams)
33 | {
34 | }
35 |
36 | public async Task RunAsync(Ams ams, CancellationToken cancellationToken)
37 | {
38 | RunBefore(ams);
39 | var result = await ams.PerformRequestAsync(new AdsCommandConversation(this),
40 | AmsPortTargetOverride ?? ams.AmsPortTarget, cancellationToken).ConfigureAwait(false);
41 | RunAfter(ams);
42 |
43 | return result;
44 | }
45 |
46 | internal abstract IEnumerable GetBytes();
47 |
48 | private readonly struct AdsCommandRequest : IAdsRequest
49 | {
50 | private readonly AdsCommand command;
51 | private readonly Lazy> data;
52 |
53 | public AdsCommandRequest(AdsCommand command)
54 | {
55 | this.command = command;
56 | data = new Lazy>(command.GetBytes);
57 | }
58 |
59 |
60 | public AdsCommandEnum Command => (AdsCommandEnum)command.CommandId;
61 |
62 | public int GetRequestLength()
63 | {
64 | return data.Value.Count();
65 | }
66 |
67 | public int BuildRequest(Span span)
68 | {
69 | var i = 0;
70 | foreach (var b in data.Value)
71 | {
72 | span[i] = b;
73 | i++;
74 | }
75 |
76 | return i;
77 | }
78 | }
79 |
80 | private class AdsCommandConversation : IAdsConversation
81 | {
82 | public AdsCommand Command { get; }
83 |
84 | public AdsCommandConversation(AdsCommand command)
85 | {
86 | Command = command;
87 | }
88 |
89 | public AdsCommandRequest BuildRequest()
90 | {
91 | return new AdsCommandRequest(Command);
92 | }
93 |
94 | public TResponse ParseResponse(ReadOnlySpan buffer)
95 | {
96 | var response = new TResponse();
97 |
98 | // AdsCommandResponse expects [uint error, ...data]
99 | // Error is already checked by PerformRequestAsync, leaving 0 here.
100 | var target = new byte[4 + buffer.Length];
101 | var targetSpan = target.AsSpan();
102 | buffer.CopyTo(targetSpan.Slice(4));
103 |
104 | response.AdsResponse = target;
105 | response.ProcessResponse();
106 |
107 | return response;
108 | }
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/AdsClient/Commands/AdsDeleteDeviceNotificationCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Viscon.Communication.Ads.CommandResponse;
5 | using Viscon.Communication.Ads.Common;
6 |
7 | namespace Viscon.Communication.Ads.Commands
8 | {
9 | public class AdsDeleteDeviceNotificationCommand : AdsCommand
10 | {
11 | public AdsDeleteDeviceNotificationCommand(uint notificationHandle)
12 | : base(AdsCommandId.DeleteDeviceNotification)
13 | {
14 |
15 | this.notificationHandle = notificationHandle;
16 | }
17 |
18 | private uint notificationHandle;
19 |
20 | internal override IEnumerable GetBytes()
21 | {
22 | IEnumerable data = BitConverter.GetBytes(notificationHandle);
23 | return data;
24 | }
25 |
26 | protected override void RunAfter(Ams ams)
27 | {
28 | var notification = ams.NotificationRequests.FirstOrDefault(n => n.NotificationHandle == notificationHandle);
29 | if (notification != null)
30 | ams.NotificationRequests.Remove(notification);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/AdsClient/Commands/AdsReadCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Viscon.Communication.Ads.CommandResponse;
5 | using Viscon.Communication.Ads.Common;
6 |
7 | namespace Viscon.Communication.Ads.Commands
8 | {
9 | internal class AdsReadCommand : AdsCommand
10 | {
11 | public AdsReadCommand(uint indexGroup, uint indexOffset, uint readLength)
12 | : base(AdsCommandId.Read)
13 | {
14 | this.readLength = readLength;
15 | this.indexGroup = indexGroup;
16 | this.indexOffset = indexOffset;
17 | }
18 |
19 | private uint readLength;
20 | private uint indexOffset;
21 | private uint indexGroup;
22 |
23 | internal override IEnumerable GetBytes()
24 | {
25 | IEnumerable data = BitConverter.GetBytes(indexGroup);
26 | data = data.Concat(BitConverter.GetBytes(indexOffset));
27 | data = data.Concat(BitConverter.GetBytes(readLength));
28 |
29 | return data;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/AdsClient/Commands/AdsReadDeviceInfoCommand.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Viscon.Communication.Ads.CommandResponse;
3 | using Viscon.Communication.Ads.Common;
4 |
5 | namespace Viscon.Communication.Ads.Commands
6 | {
7 | public class AdsReadDeviceInfoCommand : AdsCommand
8 | {
9 |
10 | public AdsReadDeviceInfoCommand()
11 | : base(AdsCommandId.ReadDeviceInfo)
12 | {
13 |
14 | }
15 |
16 | internal override IEnumerable GetBytes()
17 | {
18 | return new List();
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/AdsClient/Commands/AdsReadStateCommand.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Viscon.Communication.Ads.CommandResponse;
3 | using Viscon.Communication.Ads.Common;
4 |
5 | namespace Viscon.Communication.Ads.Commands
6 | {
7 | public class AdsReadStateCommand : AdsCommand
8 | {
9 | public AdsReadStateCommand()
10 | : base(AdsCommandId.ReadState)
11 | {
12 |
13 | }
14 |
15 | internal override IEnumerable GetBytes()
16 | {
17 | return new List();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/AdsClient/Commands/AdsWriteCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Viscon.Communication.Ads.CommandResponse;
5 | using Viscon.Communication.Ads.Common;
6 |
7 | namespace Viscon.Communication.Ads.Commands
8 | {
9 | public class AdsWriteCommand : AdsCommand
10 | {
11 | private IEnumerable varValue;
12 |
13 | public AdsWriteCommand(uint indexGroup, uint indexOffset, IEnumerable varValue)
14 | : base(AdsCommandId.Write)
15 | {
16 | this.varValue = varValue;
17 | this.indexGroup = indexGroup;
18 | this.indexOffset = indexOffset;
19 | }
20 |
21 | private uint indexOffset;
22 | private uint indexGroup;
23 |
24 | internal override IEnumerable GetBytes()
25 | {
26 | IEnumerable data = BitConverter.GetBytes(indexGroup);
27 | data = data.Concat(BitConverter.GetBytes(indexOffset));
28 | data = data.Concat(BitConverter.GetBytes((uint)varValue.Count()));
29 | data = data.Concat(varValue);
30 |
31 | return data;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/AdsClient/Commands/AdsWriteReadCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Viscon.Communication.Ads.CommandResponse;
5 | using Viscon.Communication.Ads.Common;
6 |
7 | namespace Viscon.Communication.Ads.Commands
8 | {
9 | public class AdsWriteReadCommand : AdsCommand
10 | {
11 | private uint readLength;
12 | private byte[] value;
13 |
14 | public AdsWriteReadCommand(uint indexGroup, uint indexOffset, byte[] value, uint readLength)
15 | : base(AdsCommandId.ReadWrite)
16 | {
17 | this.readLength = readLength;
18 | this.value = value;
19 | this.indexGroup = indexGroup;
20 | this.indexOffset = indexOffset;
21 | }
22 |
23 | private uint indexOffset;
24 | private uint indexGroup;
25 |
26 | internal override IEnumerable GetBytes()
27 | {
28 | IEnumerable data = BitConverter.GetBytes(indexGroup);
29 | data = data.Concat(BitConverter.GetBytes(indexOffset));
30 | data = data.Concat(BitConverter.GetBytes(readLength));
31 | data = data.Concat(BitConverter.GetBytes((uint)value.Length));
32 | data = data.Concat(value);
33 | return data;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsArrayRange.cs:
--------------------------------------------------------------------------------
1 | namespace Viscon.Communication.Ads.Common;
2 |
3 | public record AdsArrayRange(uint Start, uint Length);
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsCommandEnum.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable InconsistentNaming
2 | // ReSharper disable IdentifierTypo
3 | // ReSharper disable UnusedMember.Global
4 | // ReSharper disable CommentTypo
5 | namespace Viscon.Communication.Ads.Common;
6 |
7 | ///
8 | /// Specifies the ADS commands.
9 | ///
10 | ///
11 | /// This list is imported from ads-client (https://github.com/jisotalo/ads-client), which sourced
12 | /// most values from the TwinCAT.Ads.dll by Beckhoff.
13 | ///
14 | // Temporary name, until AdsCommand from Commands namespace is gone.
15 | public enum AdsCommandEnum : ushort
16 | {
17 | None = 0,
18 | ReadDeviceInfo = 1,
19 | Read = 2,
20 | Write = 3,
21 | ReadState = 4,
22 | WriteControl = 5,
23 | AddNotification = 6,
24 | DeleteNotification = 7,
25 | Notification = 8,
26 | ReadWrite = 9,
27 | }
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsCommandId.cs:
--------------------------------------------------------------------------------
1 | namespace Viscon.Communication.Ads.Common
2 | {
3 | public static class AdsCommandId
4 | {
5 | public const ushort ReadDeviceInfo = 1;
6 | public const ushort Read = 2;
7 | public const ushort Write = 3;
8 | public const ushort ReadState = 4;
9 | public const ushort WriteControl = 5;
10 | public const ushort AddDeviceNotification = 6;
11 | public const ushort DeleteDeviceNotification = 7;
12 | public const ushort DeviceNotification = 8;
13 | public const ushort ReadWrite = 9;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsConnectionSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Viscon.Communication.Ads.Common
2 | {
3 | public class AdsConnectionSettings : IAdsConnectionSettings
4 | {
5 | public AdsConnectionSettings ()
6 | {
7 | AmsPortTarget = 801;
8 | }
9 |
10 | public string Name { get; set; }
11 | public string AmsNetIdSource { get; set; }
12 | //public string IpTarget { get; set; }
13 | public IAmsSocket AmsSocket {get;set;}
14 | public string AmsNetIdTarget { get; set; }
15 | public ushort AmsPortTarget { get; set; }
16 |
17 | }
18 | }
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsDataType.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable InconsistentNaming
2 | // ReSharper disable IdentifierTypo
3 | // ReSharper disable UnusedMember.Global
4 | // ReSharper disable CommentTypo
5 | namespace Viscon.Communication.Ads.Common;
6 |
7 | ///
8 | /// Specifies the ADS data types.
9 | ///
10 | ///
11 | /// This list is imported from ads-client (https://github.com/jisotalo/ads-client), which sourced
12 | /// most values from the TwinCAT.Ads.dll by Beckhoff.
13 | ///
14 | public enum AdsDataType : uint
15 | {
16 | /// Empty Type
17 | ADST_VOID = 0,
18 | /// Integer 16 Bit
19 | ADST_INT16 = 2,
20 | /// Integer 32 Bit
21 | ADST_INT32 = 3,
22 | /// Real (32 Bit)
23 | ADST_REAL32 = 4,
24 | /// Real 64 Bit
25 | ADST_REAL64 = 5,
26 | /// Integer 8 Bit
27 | ADST_INT8 = 16,
28 | /// Unsigned integer 8 Bit
29 | ADST_UINT8 = 17,
30 | /// Unsigned integer 16 Bit
31 | ADST_UINT16 = 18,
32 | /// Unsigned Integer 32 Bit
33 | ADST_UINT32 = 19,
34 | /// LONG Integer 64 Bit
35 | ADST_INT64 = 20,
36 | /// Unsigned Long integer 64 Bit
37 | ADST_UINT64 = 21,
38 | /// STRING
39 | ADST_STRING = 30,
40 | /// WSTRING
41 | ADST_WSTRING = 31,
42 | /// ADS REAL80
43 | ADST_REAL80 = 32,
44 | /// ADS BIT
45 | ADST_BIT = 33,
46 | /// Internal Only
47 | ADST_MAXTYPES = 34,
48 | /// Blob
49 | ADST_BIGTYPE = 65,
50 | }
51 |
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsDataTypeDto.cs:
--------------------------------------------------------------------------------
1 | namespace Viscon.Communication.Ads.Common;
2 |
3 | public record AdsDataTypeDto(uint Version, uint HashValue, uint BaseTypeHashValue, uint Size, uint Offset,
4 | AdsDataType DataType, AdsDataTypeFlags Flags, string Name, string Type, string Comment, AdsArrayRange[] ArrayRanges,
5 | AdsDataTypeDto[] SubItems);
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsDataTypeFlags.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable InconsistentNaming
2 | // ReSharper disable IdentifierTypo
3 | // ReSharper disable UnusedMember.Global
4 | // ReSharper disable CommentTypo
5 | namespace Viscon.Communication.Ads.Common;
6 |
7 | ///
8 | /// Specifies the ADS data type flags.
9 | ///
10 | ///
11 | /// This list is imported from ads-client (https=//github.com/jisotalo/ads-client), which sourced
12 | /// most values from the TwinCAT.Ads.dll by Beckhoff.
13 | ///
14 | public enum AdsDataTypeFlags : uint
15 | {
16 | None = 0,
17 | DataType = 1 << 0,
18 | DataItem = 1 << 1,
19 | ReferenceTo = 1 << 2,
20 | MethodDeref = 1 << 3,
21 | Oversample = 1 << 4,
22 | BitValues = 1 << 5,
23 | PropItem = 1 << 6,
24 | TypeGuid = 1 << 7,
25 | Persistent = 1 << 8,
26 | CopyMask = 1 << 9,
27 | TComInterfacePtr = 1 << 10,
28 | MethodInfos = 1 << 11,
29 | Attributes = 1 << 12,
30 | EnumInfos = 1 << 13,
31 |
32 | ///
33 | /// Sets whether the datatype is aligned.
34 | ///
35 | Aligned = 1 << 16,
36 |
37 | ///
38 | /// Sets whether the data item is static - do not use offs.
39 | ///
40 | Static = 1 << 17,
41 |
42 | // means "ContainSpLevelss" for DATATYPES and "HasSpLevels" for DATAITEMS
43 | SpLevels = 1 << 18,
44 |
45 | ///
46 | /// Sets whether persistent data is not restored.
47 | ///
48 | IgnorePersist = 1 << 19,
49 |
50 | //Any size array (ADSDATATYPEFLAG_ANYSIZEARRAY)
51 | //
52 | // If the index is exeeded, a value access to this array will return
53 | //
54 | AnySizeArray = 1 << 20,
55 |
56 | ///
57 | /// Sets whether the data type is used for persistent variables.
58 | ///
59 | PersistantDatatype = 1 << 21,
60 |
61 | ///
62 | /// Sets whether persistent data will not be restored after reset (cold).
63 | ///
64 | InitOnResult = 1 << 22,
65 | }
66 |
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsDeviceInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Common
4 | {
5 | public class AdsDeviceInfo
6 | {
7 | public byte MajorVersion { get; set; }
8 | public byte MinorVersion { get; set; }
9 | public ushort VersionBuild { get; set; }
10 | public string DeviceName { get; set; }
11 |
12 | public override string ToString()
13 | {
14 | return String.Format("Version: {0}.{1}.{2} Devicename: {3}", MajorVersion, MinorVersion, VersionBuild, DeviceName);
15 | }
16 |
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsError.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable IdentifierTypo
2 | // ReSharper disable InconsistentNaming
3 | // ReSharper disable UnusedMember.Global
4 |
5 | namespace Viscon.Communication.Ads.Common
6 | {
7 | ///
8 | /// Specifies known ADS errors.
9 | ///
10 | public enum AdsError : uint
11 | {
12 | ///
13 | /// No error
14 | ///
15 | None = 0,
16 |
17 | ///
18 | /// Internal error
19 | ///
20 | InternalError = 1,
21 |
22 | ///
23 | /// No Rtime
24 | ///
25 | NoRTime = 2,
26 |
27 | ///
28 | /// Allocation locked memory error
29 | ///
30 | AllocationLockedMemory = 3,
31 |
32 | ///
33 | /// Insert mailbox error
34 | ///
35 | InsertMailBoxError = 4,
36 |
37 | ///
38 | /// Wrong receive HMSG
39 | ///
40 | WrongReceiveHMSG = 5,
41 |
42 | ///
43 | /// target port not found
44 | ///
45 | TargetPortNotFound = 6,
46 |
47 | ///
48 | /// target machine not found
49 | ///
50 | TargetMachineNotFound = 7,
51 |
52 | ///
53 | /// "Unknown command ID
54 | ///
55 | UnknownCommandId = 8,
56 |
57 | ///
58 | /// Bad task ID
59 | ///
60 | BadTaskId = 9,
61 |
62 | ///
63 | /// No IO
64 | ///
65 | NoIO = 10,
66 |
67 | ///
68 | /// Unknown AMS command
69 | ///
70 | UnknownAmsCommand = 11,
71 |
72 | ///
73 | /// Win 32 error
74 | ///
75 | Win32Error = 12,
76 |
77 | ///
78 | /// Port not connected
79 | ///
80 | PortNotConnected = 13,
81 |
82 | ///
83 | /// Invalid AMS length
84 | ///
85 | InvalidAmsLength = 14,
86 |
87 | ///
88 | /// Invalid AMS Net ID
89 | ///
90 | InvalidAmsNetId = 15,
91 |
92 | ///
93 | /// Low Installation level
94 | ///
95 | LowInstallationLevel = 16,
96 |
97 | ///
98 | /// No debug available
99 | ///
100 | NoDebugAvailable = 17,
101 |
102 | ///
103 | /// Port disabled
104 | ///
105 | PortDisabled = 18,
106 |
107 | ///
108 | /// Port already connected
109 | ///
110 | PortAlreadyConnected = 19,
111 |
112 | ///
113 | /// AMS Sync Win32 error
114 | ///
115 | AmsSyncWin32Error = 20,
116 |
117 | ///
118 | /// AMS Sync Timeout
119 | ///
120 | AmsSyncTimeout = 21,
121 |
122 | ///
123 | /// AMS Sync AMS error
124 | ///
125 | AmsSyncAmsError = 22,
126 |
127 | ///
128 | /// AMS Sync no index map
129 | ///
130 | AmsSyncNoIndexMap = 23,
131 |
132 | ///
133 | /// Invalid AMS port
134 | ///
135 | InvalidAmsPort = 24,
136 |
137 | ///
138 | /// No memory
139 | ///
140 | NoMemory = 25,
141 |
142 | ///
143 | /// TCP send error
144 | ///
145 | TcpSendError = 26,
146 |
147 | ///
148 | /// Host unreachable
149 | ///
150 | HostUnreachable = 27,
151 |
152 | ///
153 | /// error class <device error>
154 | ///
155 | DeviceError = 1792,
156 |
157 | ///
158 | /// Service is not supported by server
159 | ///
160 | ServiceNotSupported = 1793,
161 |
162 | ///
163 | /// invalid index group
164 | ///
165 | InvalidIndexGroup = 1794,
166 |
167 | ///
168 | /// invalid index offset
169 | ///
170 | InvalidIndexOffset = 1795,
171 |
172 | ///
173 | /// reading/writing not permitted
174 | ///
175 | ReadWriteNotPermitted = 1796,
176 |
177 | ///
178 | /// parameter size not correct
179 | ///
180 | ParameterSizeIncorrect = 1797,
181 |
182 | ///
183 | /// invalid parameter value(s)
184 | ///
185 | InvalidParameterValue = 1798,
186 |
187 | ///
188 | /// device is not in a ready state
189 | ///
190 | DeviceNotReady = 1799,
191 |
192 | ///
193 | /// device is busy
194 | ///
195 | DeviceBusy = 1800,
196 |
197 | ///
198 | /// invalid context (must be in Windows)
199 | ///
200 | InvalidContext = 1801,
201 |
202 | ///
203 | /// out of memory
204 | ///
205 | OutOfMemory = 1802,
206 |
207 | ///
208 | /// invalid parameter value(s)
209 | ///
210 | InvalidParameterValue2 = 1803,
211 |
212 | ///
213 | /// not found (files, ...)
214 | ///
215 | NotFound = 1804,
216 |
217 | ///
218 | /// syntax error in command or file
219 | ///
220 | SyntaxError = 1805,
221 |
222 | ///
223 | /// objects do not match
224 | ///
225 | ObjectsDoNotMatch = 1806,
226 |
227 | ///
228 | /// object already exists
229 | ///
230 | ObjectAlreadyExists = 1807,
231 |
232 | ///
233 | /// symbol not found
234 | ///
235 | SymbolNotFound = 1808,
236 |
237 | ///
238 | /// symbol version invalid
239 | ///
240 | SymbolVersionInvalid = 1809,
241 |
242 | ///
243 | /// server is in invalid state
244 | ///
245 | ServerStateInvalid = 1810,
246 |
247 | ///
248 | /// AdsTransMode not supported
249 | ///
250 | AdsTransModeNotSupported = 1811,
251 |
252 | ///
253 | /// Notification handle is invalid
254 | ///
255 | InvalidNotificationHandle = 1812,
256 |
257 | ///
258 | /// Notification client not registered
259 | ///
260 | NotificationClientNotRegistered = 1813,
261 |
262 | ///
263 | /// no more notification handles
264 | ///
265 | NoMoreNotificationHandles = 1814,
266 |
267 | ///
268 | /// size for watch too big
269 | ///
270 | SizeForWatchTooBig = 1815,
271 |
272 | ///
273 | /// device not initialized
274 | ///
275 | DeviceNotInitialized = 1816,
276 |
277 | ///
278 | /// device has a timeout
279 | ///
280 | DeviceTimeout = 1817,
281 |
282 | ///
283 | /// query interface failed
284 | ///
285 | QueryInterfaceFailed = 1818,
286 |
287 | ///
288 | /// wrong interface required
289 | ///
290 | WrongInterfaceRequired = 1819,
291 |
292 | ///
293 | /// class ID is invalid
294 | ///
295 | InvalidClassId = 1820,
296 |
297 | ///
298 | /// object ID is invalid
299 | ///
300 | InvalidObjectId = 1821,
301 |
302 | ///
303 | /// request is pending
304 | ///
305 | RequestIsPending = 1822,
306 |
307 | ///
308 | /// request is aborted
309 | ///
310 | RequestIsAborted = 1823,
311 |
312 | ///
313 | /// signal warning
314 | ///
315 | SignalWarning = 1824,
316 |
317 | ///
318 | /// invalid array index
319 | ///
320 | InvalidArrayIndex = 1825,
321 |
322 | ///
323 | /// symbol not active -> release handle and try again
324 | ///
325 | SymbolNotActive = 1826,
326 |
327 | ///
328 | /// access denied
329 | ///
330 | AccessDenied = 1827,
331 |
332 | ///
333 | /// Error class <client error>
334 | ///
335 | ClientError = 1856,
336 |
337 | ///
338 | /// invalid parameter at service
339 | ///
340 | InvalidServiceParameter = 1857,
341 |
342 | ///
343 | /// polling list is empty
344 | ///
345 | PollingListEmpty = 1858,
346 |
347 | ///
348 | /// var connection already in use
349 | ///
350 | VarConnectionInUse = 1859,
351 |
352 | ///
353 | /// invoke ID in use
354 | ///
355 | InvokeIdInUse = 1860,
356 |
357 | ///
358 | /// timeout elapsed
359 | ///
360 | TimeoutElapsed = 1861,
361 |
362 | ///
363 | /// error in win32 subsystem
364 | ///
365 | Win32SubsystemError = 1862,
366 |
367 | ///
368 | /// Invalid client timeout value
369 | ///
370 | InvalidClientTimeout = 1863,
371 |
372 | ///
373 | /// ads-port not opened
374 | ///
375 | AdsPortNotOpened = 1864,
376 |
377 | ///
378 | /// internal error in ads sync
379 | ///
380 | InternalErrorInAdsSync = 1872,
381 |
382 | ///
383 | /// hash table overflow
384 | ///
385 | HashTableOverflow = 1873,
386 |
387 | ///
388 | /// key not found in hash
389 | ///
390 | KeyNotFoundInHash = 1874,
391 |
392 | ///
393 | /// no more symbols in cache
394 | ///
395 | NoMoreSymbolsInCache = 1875,
396 |
397 | ///
398 | /// invalid response received
399 | ///
400 | InvalidResponseReceived = 1876,
401 |
402 | ///
403 | /// sync port is locked
404 | ///
405 | SyncPortIsLocked = 1877,
406 | }
407 | }
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Common
4 | {
5 | public class TestException : Exception
6 | {
7 | public TestException(string message)
8 | : base(message)
9 | {
10 | System.Diagnostics.Debug.WriteLine("Cause: " + message);
11 | }
12 | }
13 |
14 | public class AdsException : TestException
15 | {
16 | public AdsException(uint errorCode) : base(GetErrorMessage(errorCode))
17 | {
18 | this.errorCode = errorCode;
19 | }
20 |
21 | public AdsException(string message) : base(message)
22 | {
23 | this.errorCode = 0;
24 | }
25 |
26 | private uint errorCode;
27 | public AdsError ErrorCode => (AdsError)errorCode;
28 |
29 | private static string GetErrorMessage(uint errorCode)
30 | {
31 | string msg = "";
32 | switch (errorCode)
33 | {
34 | case 0: msg = "no error"; break;
35 | case 1 : msg = "Internal error"; break;
36 | case 2 : msg = "No Rtime"; break;
37 | case 3 : msg = "Allocation locked memory error"; break;
38 | case 4 : msg = "Insert mailbox error"; break;
39 | case 5 : msg = "Wrong receive HMSG"; break;
40 | case 6 : msg = "target port not found"; break;
41 | case 7 : msg = "target machine not found"; break;
42 | case 8 : msg = "Unknown command ID"; break;
43 | case 9 : msg = "Bad task ID"; break;
44 | case 10: msg = "No IO"; break;
45 | case 11: msg = "Unknown AMS command"; break;
46 | case 12: msg = "Win 32 error"; break;
47 | case 13: msg = "Port not connected"; break;
48 | case 14: msg = "Invalid AMS length"; break;
49 | case 15: msg = "Invalid AMS Net ID"; break;
50 | case 16: msg = "Low Installation level"; break;
51 | case 17: msg = "No debug available"; break;
52 | case 18: msg = "Port disabled"; break;
53 | case 19: msg = "Port already connected"; break;
54 | case 20: msg = "AMS Sync Win32 error"; break;
55 | case 21: msg = "AMS Sync Timeout"; break;
56 | case 22: msg = "AMS Sync AMS error"; break;
57 | case 23: msg = "AMS Sync no index map"; break;
58 | case 24: msg = "Invalid AMS port"; break;
59 | case 25: msg = "No memory"; break;
60 | case 26: msg = "TCP send error"; break;
61 | case 27: msg = "Host unreachable"; break;
62 |
63 | case 1792: msg="error class "; break;
64 | case 1793: msg="Service is not supported by server"; break;
65 | case 1794: msg="invalid index group"; break;
66 | case 1795: msg="invalid index offset"; break;
67 | case 1796: msg="reading/writing not permitted"; break;
68 | case 1797: msg="parameter size not correct"; break;
69 | case 1798: msg="invalid parameter value(s)"; break;
70 | case 1799: msg="device is not in a ready state"; break;
71 | case 1800: msg="device is busy"; break;
72 | case 1801: msg="invalid context (must be in Windows)"; break;
73 | case 1802: msg="out of memory"; break;
74 | case 1803: msg="invalid parameter value(s)"; break;
75 | case 1804: msg="not found (files, ...)"; break;
76 | case 1805: msg="syntax error in command or file"; break;
77 | case 1806: msg="objects do not match"; break;
78 | case 1807: msg="object already exists"; break;
79 | case 1808: msg="symbol not found"; break;
80 | case 1809: msg="symbol version invalid"; break;
81 | case 1810: msg="server is in invalid state"; break;
82 | case 1811: msg="AdsTransMode not supported"; break;
83 | case 1812: msg="Notification handle is invalid"; break;
84 | case 1813: msg="Notification client not registered"; break;
85 | case 1814: msg="no more notification handles"; break;
86 | case 1815: msg="size for watch too big"; break;
87 | case 1816: msg="device not initialized"; break;
88 | case 1817: msg="device has a timeout"; break;
89 | case 1818: msg="query interface failed"; break;
90 | case 1819: msg="wrong interface required"; break;
91 | case 1820: msg="class ID is invalid"; break;
92 | case 1821: msg="object ID is invalid"; break;
93 | case 1822: msg="request is pending"; break;
94 | case 1823: msg="request is aborted"; break;
95 | case 1824: msg="signal warning"; break;
96 | case 1825: msg="invalid array index"; break;
97 | case 1826: msg="symbol not active -> release handle and try again"; break;
98 | case 1827: msg="access denied"; break;
99 | case 1856: msg="Error class "; break;
100 | case 1857: msg="invalid parameter at service"; break;
101 | case 1858: msg="polling list is empty"; break;
102 | case 1859: msg="var connection already in use"; break;
103 | case 1860: msg="invoke ID in use"; break;
104 | case 1861: msg="timeout elapsed"; break;
105 | case 1862: msg="error in win32 subsystem"; break;
106 | case 1863: msg="Invalid client timeout value"; break;
107 | case 1864: msg="ads-port not opened"; break;
108 | case 1872: msg="internal error in ads sync"; break;
109 | case 1873: msg="hash table overflow"; break;
110 | case 1874: msg="key not found in hash"; break;
111 | case 1875: msg="no more symbols in cache"; break;
112 | case 1876: msg="invalid response received"; break;
113 | case 1877: msg = "sync port is locked"; break;
114 |
115 | }
116 |
117 | return msg;
118 | }
119 |
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsNotification.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Viscon.Communication.Ads.Helpers;
4 |
5 | namespace Viscon.Communication.Ads.Common
6 | {
7 | public delegate void AdsNotificationDelegate(object sender, AdsNotificationArgs e);
8 |
9 | public class AdsNotification
10 | {
11 | public AdsNotification()
12 | {
13 | }
14 |
15 | public uint NotificationHandle { get; set; }
16 | public byte[] ByteValue { get; set; }
17 | public Type TypeOfValue { get; set; }
18 | public object UserData { get; set; }
19 |
20 | public object Value { get { return ByteArrayHelper.ByteArrayToTypeValue(ByteValue, TypeOfValue); } }
21 |
22 |
23 | public override string ToString()
24 | {
25 | return String.Format("NotificationHandle: {0} Value: {1}", NotificationHandle, BitConverter.ToString(ByteValue));
26 | }
27 |
28 | //Move this to a helper class?
29 | internal static List GetNotifications(byte[] adsresponseInclAmsHeader)
30 | {
31 | var notifications = new List();
32 |
33 | int pos = 32;
34 | uint stamps = BitConverter.ToUInt32(adsresponseInclAmsHeader, pos + 4);
35 | pos += 8;
36 |
37 | for (int i = 0; i < stamps; i++)
38 | {
39 | uint samples = BitConverter.ToUInt32(adsresponseInclAmsHeader, pos + 8);
40 | pos += 12;
41 |
42 | for (int j = 0; j < samples; j++)
43 | {
44 | var notification = new AdsNotification();
45 | notification.NotificationHandle = BitConverter.ToUInt32(adsresponseInclAmsHeader, pos);
46 | uint length = BitConverter.ToUInt32(adsresponseInclAmsHeader, pos + 4);
47 | pos += 8;
48 | notification.ByteValue = new byte[length];
49 | Array.Copy(adsresponseInclAmsHeader, pos, notification.ByteValue, 0, (int)length);
50 | notifications.Add(notification);
51 | pos += (int)length;
52 | }
53 | }
54 |
55 | return notifications;
56 | }
57 |
58 |
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsNotificationArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Common
4 | {
5 | public class AdsNotificationArgs : EventArgs
6 | {
7 | public AdsNotificationArgs(AdsNotification notification)
8 | {
9 | this.Notification = notification;
10 | }
11 |
12 | public AdsNotification Notification { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsReservedIndexGroup.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable InconsistentNaming
2 | // ReSharper disable IdentifierTypo
3 | // ReSharper disable UnusedMember.Global
4 | // ReSharper disable CommentTypo
5 | namespace Viscon.Communication.Ads.Common;
6 |
7 | ///
8 | /// Specifies the reserved index groups.
9 | ///
10 | ///
11 | /// This list is imported from ads-client (https://github.com/jisotalo/ads-client), which sourced
12 | /// most values from the TwinCAT.Ads.dll by Beckhoff.
13 | ///
14 | public enum AdsReservedIndexGroup : uint
15 | {
16 | PlcRWIB = 0x4000,
17 | PlcRWOB = 0x4010,
18 | PlcRWMB = 0x4020,
19 | PlcRWRB = 0x4030,
20 | PlcRWDB = 0x4040,
21 | SymbolTable = 0xf000,
22 | SymbolName = 0xf001,
23 | SymbolValue = 0xf002,
24 | SymbolHandleByName = 0xf003,
25 | SymbolValueByName = 0xf004,
26 | SymbolValueByHandle = 0xf005,
27 | SymbolReleaseHandle = 0xf006,
28 | SymbolInfoByName = 0xf007,
29 | SymbolVersion = 0xf008,
30 | SymbolInfoByNameEx = 0xf009,
31 | SymbolDownload = 0xf00a,
32 | SymbolUpload = 0xf00b,
33 | SymbolUploadInfo = 0xf00c,
34 | SymbolDownload2 = 0xf00d,
35 | SymbolDataTypeUpload = 0xf00e,
36 | SymbolUploadInfo2 = 0xf00f, // 24 bytes of info, uploadinfo3 would contain 64 bytes
37 | SymbolNote = 0xf010,
38 | DataDataTypeInfoByNameEx = 0xf011,
39 | IOImageRWIB = 0xf020,
40 | IOImageRWIX = 0xf021,
41 | IOImageRWOB = 0xf030,
42 | IOImageRWOX = 0xf031,
43 | IOImageClearI = 0xf040,
44 | IOImageClearO = 0xf050,
45 | //
46 | //ADS Sum Read Command (ADSIGRP_SUMUP_READ)
47 | //
48 | SumCommandRead = 0xf080,
49 | //
50 | //ADS Sum Write Command (ADSIGRP_SUMUP_WRITE)
51 | //
52 | SumCommandWrite = 0xf081,
53 | //
54 | //ADS sum Read/Write command (ADSIGRP_SUMUP_READWRITE)
55 | //
56 | SumCommandReadWrite = 0xf082,
57 | //
58 | //ADS sum ReadEx command (ADSIGRP_SUMUP_READEX)
59 | //AdsRW IOffs list size
60 | //W= {list of IGrp, IOffs, Length}
61 | //R= {list of results, Length} followed by {list of data (expepted lengths)}
62 | //
63 | SumCommandReadEx = 0xf083,
64 | //
65 | //ADS sum ReadEx2 command (ADSIGRP_SUMUP_READEX2)
66 | //AdsRW IOffs list size
67 | //W= {list of IGrp, IOffs, Length}
68 | //R= {list of results, Length} followed by {list of data (returned lengths)}
69 | //
70 | SumCommandReadEx2 = 0xf084,
71 | //
72 | //ADS sum AddDevNote command (ADSIGRP_SUMUP_ADDDEVNOTE)
73 | //AdsRW IOffs list size
74 | //W= {list of IGrp, IOffs, Attrib}
75 | //R= {list of results, handles}
76 | //
77 | SumCommandAddDevNote = 0xf085,
78 | //
79 | //ADS sum DelDevNot command (ADSIGRP_SUMUP_DELDEVNOTE)
80 | //AdsRW IOffs list size
81 | //W= {list of handles}
82 | //R= {list of results}
83 | //
84 | SumCommandDelDevNote = 0xf086,
85 | DeviceData = 0xf100,
86 | }
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsReservedPort.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable InconsistentNaming
2 | // ReSharper disable IdentifierTypo
3 | // ReSharper disable UnusedMember.Global
4 | // ReSharper disable CommentTypo
5 | namespace Viscon.Communication.Ads.Common;
6 |
7 | ///
8 | /// Specifies the reserved ports for ADS routing.
9 | ///
10 | ///
11 | /// This list is imported from ads-client (https://github.com/jisotalo/ads-client), which sourced
12 | /// most values from the TwinCAT.Ads.dll by Beckhoff.
13 | ///
14 | public enum AdsReservedPort : ushort
15 | {
16 | None = 0,
17 | ///
18 | /// AMS Router
19 | ///
20 | Router = 1,
21 | ///
22 | /// AMS Debugger
23 | ///
24 | Debugger = 2,
25 | ///
26 | /// The TCom Server. Dpc or passive level.
27 | ///
28 | R0_TComServer = 10,
29 | ///
30 | /// TCom Server Task. RT context.
31 | ///
32 | R0_TComServerTask = 11,
33 | ///
34 | /// TCom Serve Task. Passive level.
35 | ///
36 | R0_TComServer_PL = 12,
37 | ///
38 | /// TwinCAT Debugger
39 | ///
40 | R0_TcDebugger = 20,
41 | ///
42 | /// TwinCAT Debugger Task
43 | ///
44 | R0_TcDebuggerTask = 21,
45 | ///
46 | /// The License Server
47 | ///
48 | R0_LicenseServer = 30,
49 | ///
50 | /// Logger
51 | ///
52 | Logger = 100,
53 | ///
54 | /// Event Logger
55 | ///
56 | EventLog = 110,
57 | ///
58 | /// application for coupler (EK), gateway (EL), etc.
59 | ///
60 | DeviceApplication = 120,
61 | ///
62 | /// Event Logger UM
63 | ///
64 | EventLog_UM = 130,
65 | ///
66 | /// Event Logger RT
67 | ///
68 | EventLog_RT = 131,
69 | ///
70 | /// Event Logger Publisher
71 | ///
72 | EventLogPublisher = 132,
73 | ///
74 | /// R0 Realtime
75 | ///
76 | R0_Realtime = 200,
77 | ///
78 | /// R0 Trace
79 | ///
80 | R0_Trace = 290,
81 | ///
82 | /// R0 IO
83 | ///
84 | R0_IO = 300,
85 | ///
86 | /// NC (R0)
87 | ///
88 | R0_NC = 500,
89 | ///
90 | /// R0 Satzausführung
91 | ///
92 | R0_NCSAF = 501,
93 | ///
94 | /// R0 Satzvorbereitung
95 | ///
96 | R0_NCSVB = 511,
97 | ///
98 | /// Preconfigured Nc2-Nc3-Instance
99 | ///
100 | R0_NCINSTANCE = 520,
101 | ///
102 | /// R0 ISG
103 | ///
104 | R0_ISG = 550,
105 | ///
106 | /// R0 CNC
107 | ///
108 | R0_CNC = 600,
109 | ///
110 | /// R0 Line
111 | ///
112 | R0_LINE = 700,
113 | ///
114 | /// R0 PLC
115 | ///
116 | R0_PLC = 800,
117 | ///
118 | /// Tc2 PLC RuntimeSystem 1
119 | ///
120 | Tc2_Plc1 = 801,
121 | ///
122 | /// Tc2 PLC RuntimeSystem 2
123 | ///
124 | Tc2_Plc2 = 811,
125 | ///
126 | /// Tc2 PLC RuntimeSystem 3
127 | ///
128 | Tc2_Plc3 = 821,
129 | ///
130 | /// Tc2 PLC RuntimeSystem 4
131 | ///
132 | Tc2_Plc4 = 831,
133 | ///
134 | /// R0 RTS
135 | ///
136 | R0_RTS = 850,
137 | ///
138 | /// Tc3 PLC RuntimeSystem 1
139 | ///
140 | Tc3_Plc1 = 851,
141 | ///
142 | /// Tc3 PLC RuntimeSystem 2
143 | ///
144 | Tc3_Plc2 = 852,
145 | ///
146 | /// Tc3 PLC RuntimeSystem 3
147 | ///
148 | Tc3_Plc3 = 853,
149 | ///
150 | /// Tc3 PLC RuntimeSystem 4
151 | ///
152 | Tc3_Plc4 = 854,
153 | ///
154 | /// Tc3 PLC RuntimeSystem 5
155 | ///
156 | Tc3_Plc5 = 855,
157 | ///
158 | /// Camshaft Controller (R0)
159 | ///
160 | CamshaftController = 900,
161 | ///
162 | /// R0 CAM Tool
163 | ///
164 | R0_CAMTOOL = 950,
165 | ///
166 | /// R0 User
167 | ///
168 | R0_USER = 2000,
169 | R3_CTRLPROG = 10000,
170 | ///
171 | /// System Service (AMSPORT_R3_SYSSERV)
172 | ///
173 | SystemService = 10000,
174 | R3_SYSCTRL = 10001,
175 | R3_SYSSAMPLER = 10100,
176 | R3_TCPRAWCONN = 10200,
177 | R3_TCPIPSERVER = 10201,
178 | R3_SYSMANAGER = 10300,
179 | R3_SMSSERVER = 10400,
180 | R3_MODBUSSERVER = 10500,
181 | R3_AMSLOGGER = 10502,
182 | R3_XMLDATASERVER = 10600,
183 | R3_AUTOCONFIG = 10700,
184 | R3_PLCCONTROL = 10800,
185 | R3_FTPCLIENT = 10900,
186 | R3_NCCTRL = 11000,
187 | R3_NCINTERPRETER = 11500,
188 | R3_GSTINTERPRETER = 11600,
189 | R3_STRECKECTRL = 12000,
190 | R3_CAMCTRL = 13000,
191 | R3_SCOPE = 14000,
192 | R3_CONDITIONMON = 14100,
193 | R3_SINECH1 = 15000,
194 | R3_CONTROLNET = 16000,
195 | R3_OPCSERVER = 17000,
196 | R3_OPCCLIENT = 17500,
197 | R3_MAILSERVER = 18000,
198 | R3_EL60XX = 19000,
199 | R3_MANAGEMENT = 19100,
200 | R3_MIELEHOME = 19200,
201 | R3_CPLINK3 = 19300,
202 | R3_VNSERVICE = 19500,
203 | ///
204 | /// Multiuser
205 | ///
206 | R3_MULTIUSER = 19600,
207 | ///
208 | /// Default (AMS router assigns)
209 | ///
210 | USEDEFAULT = 65535,
211 | }
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsState.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Common
4 | {
5 | public class AdsState
6 | {
7 | const byte ADSSTATE_INVALID = 0; //ADS Status: invalid
8 | const byte ADSSTATE_IDLE = 1; //ADS Status: idle
9 | const byte ADSSTATE_RESET = 2; //ADS Status: reset.
10 | const byte ADSSTATE_INIT = 3; //ADS Status: init
11 | const byte ADSSTATE_START = 4; //ADS Status: start
12 | const byte ADSSTATE_RUN = 5; //ADS Status: run
13 | const byte ADSSTATE_STOP = 6; //ADS Status: stop
14 | const byte ADSSTATE_SAVECFG = 7; //ADS Status: save configuration
15 | const byte ADSSTATE_LOADCFG = 8; //ADS Status: load configuration
16 | const byte ADSSTATE_POWERFAILURE = 9; //ADS Status: Power failure
17 | const byte ADSSTATE_POWERGOOD = 10; //ADS Status: Power good
18 | const byte ADSSTATE_ERROR = 11; //ADS Status: Error
19 | const byte ADSSTATE_SHUTDOWN = 12; //ADS Status: Shutdown
20 | const byte ADSSTATE_SUSPEND = 13; //ADS Status: Suspend
21 | const byte ADSSTATE_RESUME = 14; //ADS Status: Resume
22 | const byte ADSSTATE_CONFIG = 15; //ADS Status: Configuration
23 | const byte ADSSTATE_RECONFIG = 16; //ADS Status: Reconfiguration
24 |
25 | public ushort State { get; set; }
26 | public ushort DeviceState { get; set; }
27 |
28 | public override string ToString()
29 | {
30 | return String.Format("Ads state: {0} ({1}) Device state: {2}", State, AdsStateDescripton, DeviceState);
31 | }
32 |
33 | public string AdsStateDescripton
34 | {
35 | get
36 | {
37 | string desc = "";
38 | switch (State)
39 | {
40 | case ADSSTATE_INVALID: desc = "Invalid"; break;
41 | case ADSSTATE_IDLE: desc = "Idle"; break;
42 | case ADSSTATE_RESET: desc = "Reset"; break;
43 | case ADSSTATE_INIT: desc = "Init"; break;
44 | case ADSSTATE_START: desc = "Start"; break;
45 | case ADSSTATE_RUN: desc = "Run"; break;
46 | case ADSSTATE_STOP: desc = "Stop"; break;
47 | case ADSSTATE_SAVECFG: desc = "Save configuration"; break;
48 | case ADSSTATE_LOADCFG: desc = "Load configuration"; break;
49 | case ADSSTATE_POWERFAILURE: desc = "Power failure"; break;
50 | case ADSSTATE_POWERGOOD: desc = "Power good"; break;
51 | case ADSSTATE_ERROR: desc = "Error"; break;
52 | case ADSSTATE_SHUTDOWN: desc = "Shutdown"; break;
53 | case ADSSTATE_SUSPEND: desc = "Suspend"; break;
54 | case ADSSTATE_RESUME: desc = "Resume"; break;
55 | case ADSSTATE_CONFIG: desc = "Configuration"; break;
56 | case ADSSTATE_RECONFIG: desc = "Reconfiguration"; break;
57 | }
58 | return desc;
59 | }
60 | }
61 |
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsSymbol.cs:
--------------------------------------------------------------------------------
1 | using Viscon.Communication.Ads.Variables;
2 |
3 | namespace Viscon.Communication.Ads.Common;
4 |
5 | public record AdsSymbol(uint IndexGroup, uint IndexOffset, uint Size, string Name, string TypeName, string Comment) : IVariableAddressAndSize;
--------------------------------------------------------------------------------
/src/AdsClient/Common/AdsTransmissionMode.cs:
--------------------------------------------------------------------------------
1 | namespace Viscon.Communication.Ads.Common
2 | {
3 | public enum AdsTransmissionMode : uint
4 | {
5 | None = 0,
6 | ClientCycle = 1,
7 | ClientOnChange = 2,
8 |
9 | ///
10 | /// Cyclic transmission.
11 | ///
12 | Cyclic = 3,
13 |
14 | ///
15 | /// Transmission on value change.
16 | ///
17 | OnChange = 4,
18 | CyclicInContext = 5,
19 | OnChangeInContext = 6,
20 | }
21 | }
--------------------------------------------------------------------------------
/src/AdsClient/Common/AmsNetId.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Viscon.Communication.Ads.Common
5 | {
6 | public class AmsNetId
7 | {
8 | public IList Bytes { get; set; }
9 |
10 | internal AmsNetId(string amsNetId)
11 | {
12 | ParseString(amsNetId);
13 | }
14 |
15 | private void ParseString(string amsNetId)
16 | {
17 | Bytes = new List();
18 |
19 | string[] byteStrings = amsNetId.Split('.');
20 | foreach (string byteString in byteStrings)
21 | {
22 | byte b = Convert.ToByte(byteString);
23 | Bytes.Add(b);
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/AdsClient/Common/AmsSocketResponseArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Common
4 | {
5 | public class AmsSocketResponseArgs : EventArgs
6 | {
7 | public Exception Error { get; set; }
8 | public byte[] Response { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/AdsClient/Common/Date.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Common
4 | {
5 | public class Date
6 | {
7 | private uint intValue;
8 | private DateTime dateValue;
9 |
10 | public Date(uint value)
11 | {
12 | this.intValue = value;
13 | this.dateValue = new DateTime(1970, 1, 1).AddSeconds(intValue);
14 | }
15 |
16 | public Date(DateTime value)
17 | {
18 | this.dateValue = value;
19 | this.intValue = (uint)((value - new DateTime(1970, 1, 1)).TotalSeconds);
20 | }
21 |
22 | public bool ToBoolean(IFormatProvider provider)
23 | {
24 | return Convert.ToBoolean(intValue);
25 | }
26 |
27 | public byte ToByte(IFormatProvider provider)
28 | {
29 | return Convert.ToByte(intValue);
30 | }
31 |
32 | public char ToChar(IFormatProvider provider)
33 | {
34 | return Convert.ToChar(intValue);
35 | }
36 |
37 | public DateTime ToDateTime(IFormatProvider provider)
38 | {
39 | return dateValue;
40 | }
41 |
42 | public decimal ToDecimal(IFormatProvider provider)
43 | {
44 | return Convert.ToDecimal(intValue); ;
45 | }
46 |
47 | public double ToDouble(IFormatProvider provider)
48 | {
49 | return Convert.ToDouble(intValue);
50 | }
51 |
52 | public short ToInt16(IFormatProvider provider)
53 | {
54 | return Convert.ToInt16(intValue);
55 | }
56 |
57 | public int ToInt32(IFormatProvider provider)
58 | {
59 | return Convert.ToInt32(intValue);
60 | }
61 |
62 | public long ToInt64(IFormatProvider provider)
63 | {
64 | return Convert.ToInt64(intValue);
65 | }
66 |
67 | public sbyte ToSByte(IFormatProvider provider)
68 | {
69 | return Convert.ToSByte(intValue);
70 | }
71 |
72 | public float ToSingle(IFormatProvider provider)
73 | {
74 | return Convert.ToSingle(intValue);
75 | }
76 |
77 | public string ToString(IFormatProvider provider)
78 | {
79 | return Convert.ToString(dateValue, provider);
80 | }
81 |
82 | public override string ToString()
83 | {
84 | return Convert.ToString(dateValue);
85 | }
86 |
87 | public object ToType(Type conversionType, IFormatProvider provider)
88 | {
89 | return Convert.ChangeType(intValue, conversionType, provider);
90 | }
91 |
92 | public ushort ToUInt16(IFormatProvider provider)
93 | {
94 | return Convert.ToUInt16(intValue);
95 | }
96 |
97 | public uint ToUInt32(IFormatProvider provider)
98 | {
99 | return Convert.ToUInt32(intValue);
100 | }
101 |
102 | public ulong ToUInt64(IFormatProvider provider)
103 | {
104 | return Convert.ToUInt64(intValue);
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/AdsClient/Common/IAdsConnectionSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Viscon.Communication.Ads.Common
2 | {
3 | public interface IAdsConnectionSettings
4 | {
5 | string Name { get; set; }
6 | string AmsNetIdSource { get; set; }
7 | //string IpTarget { get; set; }
8 | IAmsSocket AmsSocket { get; set; }
9 | string AmsNetIdTarget { get; set; }
10 | ushort AmsPortTarget { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/AdsClient/Common/Time.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Common
4 | {
5 | public class Time
6 | {
7 | private uint intValue;
8 | private TimeSpan timeSpanValue;
9 |
10 | public Time(uint value)
11 | {
12 | this.intValue = value;
13 | this.timeSpanValue = TimeSpan.FromMilliseconds(intValue);
14 | }
15 |
16 | public Time(TimeSpan value)
17 | {
18 | this.timeSpanValue = value;
19 | this.intValue = (uint)value.TotalMilliseconds;
20 | }
21 |
22 | public TimeSpan ToTimeSpan()
23 | {
24 | return this.timeSpanValue;
25 | }
26 |
27 | public bool ToBoolean(IFormatProvider provider)
28 | {
29 | return Convert.ToBoolean(intValue);
30 | }
31 |
32 | public byte ToByte(IFormatProvider provider)
33 | {
34 | return Convert.ToByte(intValue);
35 | }
36 |
37 | public char ToChar(IFormatProvider provider)
38 | {
39 | return Convert.ToChar(intValue);
40 | }
41 |
42 | public DateTime ToDateTime(IFormatProvider provider)
43 | {
44 | return new DateTime(1900, 1, 1).AddMilliseconds(intValue);
45 | }
46 |
47 | public decimal ToDecimal(IFormatProvider provider)
48 | {
49 | return Convert.ToDecimal(intValue); ;
50 | }
51 |
52 | public double ToDouble(IFormatProvider provider)
53 | {
54 | return Convert.ToDouble(intValue);
55 | }
56 |
57 | public short ToInt16(IFormatProvider provider)
58 | {
59 | return Convert.ToInt16(intValue);
60 | }
61 |
62 | public int ToInt32(IFormatProvider provider)
63 | {
64 | return Convert.ToInt32(intValue);
65 | }
66 |
67 | public long ToInt64(IFormatProvider provider)
68 | {
69 | return Convert.ToInt64(intValue);
70 | }
71 |
72 | public sbyte ToSByte(IFormatProvider provider)
73 | {
74 | return Convert.ToSByte(intValue);
75 | }
76 |
77 | public float ToSingle(IFormatProvider provider)
78 | {
79 | return Convert.ToSingle(intValue);
80 | }
81 |
82 | public string ToString(IFormatProvider provider)
83 | {
84 | return Convert.ToString(timeSpanValue, provider);
85 | }
86 |
87 | public override string ToString()
88 | {
89 | return Convert.ToString(timeSpanValue);
90 | }
91 |
92 | public object ToType(Type conversionType, IFormatProvider provider)
93 | {
94 | return Convert.ChangeType(intValue, conversionType, provider);
95 | }
96 |
97 | public ushort ToUInt16(IFormatProvider provider)
98 | {
99 | return Convert.ToUInt16(intValue);
100 | }
101 |
102 | public uint ToUInt32(IFormatProvider provider)
103 | {
104 | return Convert.ToUInt32(intValue);
105 | }
106 |
107 | public ulong ToUInt64(IFormatProvider provider)
108 | {
109 | return Convert.ToUInt64(intValue);
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/AdsReadRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Viscon.Communication.Ads.Common;
3 | using Viscon.Communication.Ads.Internal;
4 |
5 | namespace Viscon.Communication.Ads.Conversation;
6 |
7 | public readonly struct AdsReadRequest : IAdsRequest
8 | {
9 | public AdsReadRequest(uint indexGroup, uint indexOffset, uint size)
10 | {
11 | IndexGroup = indexGroup;
12 | IndexOffset = indexOffset;
13 | Size = size;
14 | }
15 |
16 | public readonly uint IndexGroup;
17 | public readonly uint IndexOffset;
18 | public readonly uint Size;
19 |
20 | public AdsCommandEnum Command => AdsCommandEnum.Read;
21 |
22 | public int GetRequestLength()
23 | {
24 | return 12;
25 | }
26 |
27 | public int BuildRequest(Span span)
28 | {
29 | var offset = WireFormatting.WriteUInt32(ref span.GetStart(), IndexGroup);
30 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), IndexOffset);
31 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), Size);
32 |
33 | return offset;
34 | }
35 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/CreateVariableHandles/AdsCreateVariableHandlesConversation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Viscon.Communication.Ads.Common;
3 | using Viscon.Communication.Ads.Internal;
4 |
5 | namespace Viscon.Communication.Ads.Conversation.CreateVariableHandles
6 | {
7 | internal class AdsCreateVariableHandlesConversation : IAdsConversation
8 | {
9 | public string[] VariableNames { get; }
10 |
11 | public AdsCreateVariableHandlesConversation(string[] variableNames)
12 | {
13 | VariableNames = variableNames;
14 | }
15 |
16 | public AdsCreateVariableHandlesRequest BuildRequest()
17 | {
18 | return new AdsCreateVariableHandlesRequest(VariableNames);
19 | }
20 |
21 | public uint[] ParseResponse(ReadOnlySpan buffer)
22 | {
23 | var offset = WireFormatting.ReadInt32(buffer, out var dataLength);
24 | if (dataLength != buffer.Length - 4)
25 | {
26 | throw new Exception(
27 | $"Received {buffer.Length} bytes of data while length indicates length should be {dataLength} bytes.");
28 | }
29 |
30 | foreach (var _ in VariableNames)
31 | {
32 | offset += WireFormatting.ReadUInt32(buffer.Slice(offset), out var errorCode);
33 | if (errorCode != 0) throw new AdsException(errorCode);
34 |
35 | offset += WireFormatting.ReadUInt32(buffer.Slice(offset), out var length);
36 | if (length != sizeof(uint))
37 | throw new AdsException(
38 | $"Received handle response of length {length}, expected length {sizeof(uint)}.");
39 | }
40 |
41 | var handles = new uint[VariableNames.Length];
42 | for (var i = 0; i < VariableNames.Length; i++)
43 | {
44 | offset += WireFormatting.ReadUInt32(buffer.Slice(offset), out handles[i]);
45 | }
46 |
47 | return handles;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/CreateVariableHandles/AdsCreateVariableHandlesRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using Viscon.Communication.Ads.Common;
4 | using Viscon.Communication.Ads.Internal;
5 |
6 | namespace Viscon.Communication.Ads.Conversation.CreateVariableHandles;
7 |
8 | internal readonly struct AdsCreateVariableHandlesRequest : IAdsRequest
9 | {
10 | public readonly string[] VariableNames;
11 |
12 | public AdsCreateVariableHandlesRequest(string[] variableNames)
13 | {
14 | VariableNames = variableNames;
15 | }
16 |
17 | public AdsCommandEnum Command => AdsCommandEnum.ReadWrite;
18 |
19 | private int GetRequestDataLength()
20 | {
21 | // 16 bytes per sub-request + encoded name lengths
22 | return VariableNames.Length * 16 + VariableNames.Sum(x => x.Length + 1);
23 | }
24 |
25 | public int GetRequestLength()
26 | {
27 | // 16 bytes command + request data length
28 | return 16 + GetRequestDataLength();
29 | }
30 |
31 | public int BuildRequest(Span span)
32 | {
33 | var offset = WireFormatting.WriteUInt32(ref span.GetStart(), AdsReservedIndexGroup.SumCommandReadWrite.ToUInt32());
34 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), (uint) VariableNames.Length);
35 | // Response data length
36 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), (uint)(12 * VariableNames.Length));
37 | // Request data length
38 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), GetRequestDataLength());
39 |
40 | foreach (var variableName in VariableNames)
41 | {
42 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset),
43 | AdsReservedIndexGroup.SymbolHandleByName.ToUInt32());
44 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), (uint) 0);
45 | // Size of the requested data, which is sizeof(Symbol.IndexOffset)
46 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), (uint) sizeof(uint));
47 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), (uint) variableName.Length + 1);
48 | }
49 |
50 | foreach (var variableName in VariableNames)
51 | {
52 | offset += WireFormatting.WriteString(span.Slice(offset), variableName);
53 | }
54 |
55 | return offset;
56 | }
57 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/IAdsConversation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Conversation;
4 |
5 | public interface IAdsConversation where TRequest : struct, IAdsRequest
6 | {
7 | TRequest BuildRequest();
8 | TResponse ParseResponse(ReadOnlySpan buffer);
9 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/IAdsRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Viscon.Communication.Ads.Common;
3 |
4 | namespace Viscon.Communication.Ads.Conversation;
5 |
6 | public interface IAdsRequest
7 | {
8 | AdsCommandEnum Command { get; }
9 | int GetRequestLength();
10 | int BuildRequest(Span span);
11 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadDataTypes/AdsDataTypeParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Viscon.Communication.Ads.Common;
3 | using Viscon.Communication.Ads.Internal;
4 |
5 | namespace Viscon.Communication.Ads.Conversation.ReadDataTypes;
6 |
7 | internal static class AdsDataTypeParser
8 | {
9 | public static AdsDataTypeDto ParseDataType(ReadOnlySpan buffer)
10 | {
11 | var offset = WireFormatting.ReadUInt32(buffer, out var version);
12 | offset += WireFormatting.ReadUInt32(buffer.Slice(offset), out var hashValue);
13 | offset += WireFormatting.ReadUInt32(buffer.Slice(offset), out var baseTypeHashValue);
14 | offset += WireFormatting.ReadUInt32(buffer.Slice(offset), out var size);
15 | offset += WireFormatting.ReadUInt32(buffer.Slice(offset), out var typeOffset);
16 | offset += WireFormatting.ReadUInt32(buffer.Slice(offset), out var adsDataType);
17 | offset += WireFormatting.ReadUInt32(buffer.Slice(offset), out var flags);
18 | offset += WireFormatting.ReadUInt16(buffer.Slice(offset), out var nameLength);
19 | offset += WireFormatting.ReadUInt16(buffer.Slice(offset), out var typeLength);
20 | offset += WireFormatting.ReadUInt16(buffer.Slice(offset), out var commentLength);
21 | offset += WireFormatting.ReadUInt16(buffer.Slice(offset), out var arrayDimensions);
22 | offset += WireFormatting.ReadUInt16(buffer.Slice(offset), out var subItemCount);
23 | offset += WireFormatting.ReadString(buffer.Slice(offset), nameLength, out var name);
24 | offset += WireFormatting.ReadString(buffer.Slice(offset), typeLength, out var type);
25 | offset += WireFormatting.ReadString(buffer.Slice(offset), commentLength, out var comment);
26 | offset += ReadArrayRanges(buffer.Slice(offset), arrayDimensions, out var arrayRanges);
27 | offset += ReadSubItems(buffer.Slice(offset), subItemCount, out var subItems);
28 |
29 | // End here for now, there's more data available.
30 |
31 | return new AdsDataTypeDto(version, hashValue, baseTypeHashValue, size, typeOffset, adsDataType.ToDataType(),
32 | flags.ToDataTypeFlags(), name, type, comment, arrayRanges, subItems);
33 | }
34 |
35 | private static int ReadArrayRanges(ReadOnlySpan buffer, ushort dimensions, out AdsArrayRange[] ranges)
36 | {
37 | ranges = new AdsArrayRange[dimensions];
38 | var offset = 0;
39 | for (var i = 0; i < dimensions; i++)
40 | {
41 | offset += WireFormatting.ReadUInt32(buffer.Slice(offset), out var start);
42 | offset += WireFormatting.ReadUInt32(buffer.Slice(offset), out var length);
43 |
44 | ranges[i] = new AdsArrayRange(start, length);
45 | }
46 |
47 | return offset;
48 | }
49 |
50 | private static int ReadSubItems(ReadOnlySpan buffer, ushort count, out AdsDataTypeDto[] subItems)
51 | {
52 | subItems = new AdsDataTypeDto[count];
53 | var offset = 0;
54 | for (var i = 0; i < count; i++)
55 | {
56 | WireFormatting.ReadInt32(buffer, out var len);
57 | subItems[i] = ParseDataType(buffer.Slice(4, len - 4));
58 |
59 | offset += len;
60 | buffer = buffer.Slice(len);
61 | }
62 |
63 | return offset;
64 | }
65 | }
66 |
67 | /*
68 |
69 | //Subitems data
70 | dataType.subItems = []
71 | for (let i = 0; i < dataType.subItemCount; i++) {
72 | //Get subitem length
73 | let len = data.readUInt32LE(pos)
74 | pos += 4
75 |
76 | //Parse the subitem recursively
77 | dataType.subItems.push(await _parseDataType.call(this, data.slice(pos, pos + len)))
78 |
79 | pos += (len - 4)
80 | }
81 |
82 | //If flags contain TypeGuid
83 | if (dataType.flagsStr.includes('TypeGuid')) {
84 | dataType.typeGuid = data.slice(pos, pos + 16).toString('hex')
85 | pos += 16
86 | }
87 |
88 | //If flags contain CopyMask
89 | if (dataType.flagsStr.includes('CopyMask')) {
90 | //Let's skip this for now
91 | pos += dataType.size
92 | }
93 |
94 | dataType.rpcMethods = []
95 |
96 | //If flags contain MethodInfos (TwinCAT.Ads.dll: AdsMethodEntry)
97 | if (dataType.flagsStr.includes('MethodInfos')) {
98 | dataType.methodCount = data.readUInt16LE(pos)
99 | pos += 2
100 |
101 | //RPC methods
102 | for (let i = 0; i < dataType.methodCount; i++) {
103 | const method = {}
104 |
105 | //Get method length
106 | let len = data.readUInt32LE(pos)
107 | pos += 4
108 |
109 | //4..7 Version
110 | method.version = data.readUInt32LE(pos)
111 | pos += 4
112 |
113 | //8..11 Virtual table index
114 | method.vTableIndex = data.readUInt32LE(pos)
115 | pos += 4
116 |
117 | //12..15 Return size
118 | method.returnSize = data.readUInt32LE(pos)
119 | pos += 4
120 |
121 | //16..19 Return align size
122 | method.returnAlignSize = data.readUInt32LE(pos)
123 | pos += 4
124 |
125 | //20..23 Reserved
126 | method.reserved = data.readUInt32LE(pos)
127 | pos += 4
128 |
129 | //24..27 Return type GUID
130 | method.returnTypeGuid = data.slice(pos, pos + 16).toString('hex')
131 | pos += 16
132 |
133 | //28..31 Return data type
134 | method.retunAdsDataType = data.readUInt32LE(pos)
135 | method.retunAdsDataTypeStr = ADS.ADS_DATA_TYPES.toString(method.retunAdsDataType)
136 | pos += 4
137 |
138 | //27..30 Flags (AdsDataTypeFlags)
139 | method.flags = data.readUInt16LE(pos)
140 | method.flagsStr = ADS.ADS_DATA_TYPE_FLAGS.toStringArray(method.flags)
141 | pos += 4
142 |
143 | //31..32 Name length
144 | method.nameLength = data.readUInt16LE(pos)
145 | pos += 2
146 |
147 | //33..34 Return type length
148 | method.returnTypeLength = data.readUInt16LE(pos)
149 | pos += 2
150 |
151 | //35..36 Comment length
152 | method.commentLength = data.readUInt16LE(pos)
153 | pos += 2
154 |
155 | //37..38 Parameter count
156 | method.parameterCount = data.readUInt16LE(pos)
157 | pos += 2
158 |
159 | //39.... Name
160 | method.name = _trimPlcString(iconv.decode(data.slice(pos, pos + method.nameLength + 1), 'cp1252'))
161 | pos += method.nameLength + 1
162 |
163 | //...... Return type
164 | method.returnType = _trimPlcString(iconv.decode(data.slice(pos, pos + method.returnTypeLength + 1), 'cp1252'))
165 | pos += method.returnTypeLength + 1
166 |
167 | //...... Comment
168 | method.comment = _trimPlcString(iconv.decode(data.slice(pos, pos + method.commentLength + 1), 'cp1252'))
169 | pos += method.commentLength + 1
170 |
171 | method.parameters = []
172 |
173 | for (let p = 0; p < method.parameterCount; p++) {
174 | const param = {}
175 |
176 | let paramStartPos = pos
177 |
178 | //Get parameter length
179 | let paramLen = data.readUInt32LE(pos)
180 | pos += 4
181 |
182 | //4..7 Size
183 | param.size = data.readUInt32LE(pos)
184 | pos += 4
185 |
186 | //8..11 Align size
187 | param.alignSize = data.readUInt32LE(pos)
188 | pos += 4
189 |
190 | //12..15 Data type
191 | param.adsDataType = data.readUInt32LE(pos)
192 | param.adsDataTypeStr = ADS.ADS_DATA_TYPES.toString(param.adsDataType)
193 | pos += 4
194 |
195 | //16..19 Flags (RCP_METHOD_PARAM_FLAGS)
196 | param.flags = data.readUInt16LE(pos)
197 | param.flagsStr = ADS.RCP_METHOD_PARAM_FLAGS.toStringArray(param.flags)
198 | pos += 4
199 |
200 | //20..23 Reserved
201 | param.reserved = data.readUInt32LE(pos)
202 | pos += 4
203 |
204 | //24..27 Type GUID
205 | param.typeGuid = data.slice(pos, pos + 16).toString('hex')
206 | pos += 16
207 |
208 | //28..31 LengthIsPara
209 | param.lengthIsPara = data.readUInt16LE(pos)
210 | pos += 2
211 |
212 | //32..33 Name length
213 | param.nameLength = data.readUInt16LE(pos)
214 | pos += 2
215 |
216 | //34..35 Type length
217 | param.typeLength = data.readUInt16LE(pos)
218 | pos += 2
219 |
220 | //36..37 Comment length
221 | param.commentLength = data.readUInt16LE(pos)
222 | pos += 2
223 |
224 | //38.... Name
225 | param.name = _trimPlcString(iconv.decode(data.slice(pos, pos + param.nameLength + 1), 'cp1252'))
226 | pos += param.nameLength + 1
227 |
228 | //...... Type
229 | param.type = _trimPlcString(iconv.decode(data.slice(pos, pos + param.typeLength + 1), 'cp1252'))
230 | pos += param.typeLength + 1
231 |
232 | //...... Comment
233 | param.comment = _trimPlcString(iconv.decode(data.slice(pos, pos + param.commentLength + 1), 'cp1252'))
234 | pos += param.commentLength + 1
235 |
236 | if (pos - paramStartPos > paramLen) {
237 | //There is some additional data
238 | param.reserved2 = data.slice(pos)
239 | }
240 | method.parameters.push(param)
241 | }
242 |
243 | dataType.rpcMethods.push(method)
244 | }
245 | }
246 |
247 | //If flags contain Attributes (TwinCAT.Ads.dll: AdsAttributeEntry)
248 | //Attribute is for example, a pack-mode attribute above struct
249 | dataType.attributes = []
250 | if (dataType.flagsStr.includes('Attributes')) {
251 | dataType.attributeCount = data.readUInt16LE(pos)
252 | pos += 2
253 |
254 | //Attributes
255 | for (let i = 0; i < dataType.attributeCount; i++) {
256 | let attr = {}
257 |
258 | //Name length
259 | let nameLen = data.readUInt8(pos)
260 | pos += 1
261 |
262 | //Value length
263 | let valueLen = data.readUInt8(pos)
264 | pos += 1
265 |
266 | //Name
267 | attr.name = _trimPlcString(iconv.decode(data.slice(pos, pos + nameLen + 1), 'cp1252'))
268 | pos += (nameLen + 1)
269 |
270 | //Value
271 | attr.value = _trimPlcString(iconv.decode(data.slice(pos, pos + valueLen + 1), 'cp1252'))
272 | pos += (valueLen + 1)
273 |
274 | dataType.attributes.push(attr)
275 | }
276 | }
277 |
278 |
279 | //If flags contain EnumInfos (TwinCAT.Ads.dll: AdsEnumInfoEntry)
280 | //EnumInfo contains the enumeration values as string
281 | if (dataType.flagsStr.includes('EnumInfos')) {
282 | dataType.enumInfoCount = data.readUInt16LE(pos)
283 | pos += 2
284 | //EnumInfos
285 | dataType.enumInfo = []
286 | for (let i = 0; i < dataType.enumInfoCount; i++) {
287 | let enumInfo = {}
288 |
289 | //Name length
290 | let nameLen = data.readUInt8(pos)
291 | pos += 1
292 |
293 | //Name
294 | enumInfo.name = _trimPlcString(iconv.decode(data.slice(pos, pos + nameLen + 1), 'cp1252'))
295 | pos += (nameLen + 1)
296 |
297 | //Value
298 | enumInfo.value = data.slice(pos, pos + dataType.size)
299 | pos += dataType.size
300 |
301 | dataType.enumInfo.push(enumInfo)
302 | }
303 | }
304 |
305 | //Reserved, if any
306 | dataType.reserved = data.slice(pos)
307 |
308 | return dataType
309 | }
310 | */
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadDataTypes/AdsReadDataTypesConversation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Viscon.Communication.Ads.Common;
4 | using Viscon.Communication.Ads.Conversation.ReadUploadInfo;
5 | using Viscon.Communication.Ads.Internal;
6 |
7 | namespace Viscon.Communication.Ads.Conversation.ReadDataTypes;
8 |
9 | internal class AdsReadDataTypesConversation : IAdsConversation>
10 | {
11 | public uint DataTypeLength { get; }
12 |
13 | public AdsReadDataTypesConversation(uint dataTypeLength) => DataTypeLength = dataTypeLength;
14 |
15 | public AdsReadDataTypesConversation(AdsUploadInfoDto uploadInfo) : this(uploadInfo.DataTypeLength) { }
16 |
17 | public AdsReadRequest BuildRequest() => new(AdsReservedIndexGroup.SymbolDataTypeUpload.ToUInt32(), 0, DataTypeLength);
18 |
19 | public List ParseResponse(ReadOnlySpan buffer)
20 | {
21 | var length = LittleEndianDeserializer.ReadInt32(buffer);
22 | if (length != buffer.Length - 4)
23 | {
24 | throw new Exception(
25 | $"Received {buffer.Length} bytes of data while length indicates length should be {length} bytes.");
26 | }
27 |
28 | buffer = buffer.Slice(sizeof(uint));
29 |
30 | var results = new List();
31 | while (buffer.Length > 0)
32 | {
33 | var len = LittleEndianDeserializer.ReadInt32(buffer);
34 | results.Add(AdsDataTypeParser.ParseDataType(buffer.Slice(4, len - 4)));
35 |
36 | buffer = buffer.Slice(len);
37 | }
38 |
39 | return results;
40 | }
41 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadMultiple/AdsReadMultipleConversation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.InteropServices;
4 | using Viscon.Communication.Ads.Internal;
5 | using Viscon.Communication.Ads.Variables;
6 |
7 | namespace Viscon.Communication.Ads.Conversation.ReadMultiple
8 | {
9 | internal class AdsReadMultipleConversation : IAdsConversation
10 | {
11 | private readonly IVariableAddressAndSize[] variables;
12 | private readonly IReadResultFactory resultFactory;
13 |
14 | public AdsReadMultipleConversation(IVariableAddressAndSize[] variables, IReadResultFactory resultFactory)
15 | {
16 | this.variables = variables;
17 | this.resultFactory = resultFactory;
18 | }
19 |
20 | public AdsReadMultipleRequest BuildRequest()
21 | {
22 | return new AdsReadMultipleRequest(variables);
23 | }
24 |
25 | public TResult ParseResponse(ReadOnlySpan buffer)
26 | {
27 | var offset = WireFormatting.ReadInt32(buffer, out var length);
28 | Assertions.AssertDataLength(buffer, length, offset);
29 |
30 | var resultsLength = variables.Length * sizeof(uint);
31 | var results = MemoryMarshal.Cast(buffer.Slice(offset, resultsLength));
32 | List exceptions = null;
33 | for (var i = 0; i < variables.Length; i++)
34 | {
35 | if (results[i] == 0) continue;
36 |
37 | exceptions ??= new List();
38 | exceptions.Add(new AdsReadVariableException(variables[i], results[i]));
39 | }
40 |
41 | if (exceptions is not null) ReadMultipleException.Throw(exceptions);
42 |
43 | offset += resultsLength;
44 |
45 | return resultFactory.CreateResult(variables, buffer.Slice(offset));
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadMultiple/AdsReadMultipleRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using Viscon.Communication.Ads.Common;
4 | using Viscon.Communication.Ads.Internal;
5 | using Viscon.Communication.Ads.Variables;
6 |
7 | namespace Viscon.Communication.Ads.Conversation.ReadMultiple;
8 |
9 | public readonly struct AdsReadMultipleRequest : IAdsRequest
10 | {
11 | private readonly IVariableAddressAndSize[] variables;
12 |
13 | public AdsReadMultipleRequest(IVariableAddressAndSize[] variables)
14 | {
15 | this.variables = variables;
16 | }
17 |
18 | public AdsCommandEnum Command => AdsCommandEnum.ReadWrite;
19 |
20 | public int GetRequestLength()
21 | {
22 | return sizeof(uint) * 4 + variables.Length * sizeof(uint) * 3;
23 | }
24 |
25 | public int BuildRequest(Span span)
26 | {
27 | var offset =
28 | WireFormatting.WriteUInt32(ref span.GetStart(), AdsReservedIndexGroup.SumCommandRead.ToUInt32());
29 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), variables.Length);
30 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), (uint) variables.Sum(v => v.Size + sizeof(uint)));
31 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), (uint) GetDataLength());
32 |
33 | foreach (var variable in variables)
34 | {
35 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), variable.IndexGroup);
36 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), variable.IndexOffset);
37 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), variable.Size);
38 | }
39 |
40 | return offset;
41 | }
42 |
43 | private int GetDataLength()
44 | {
45 | return variables.Length * sizeof(uint) * 3;
46 | }
47 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadMultiple/AdsReadVariableException.cs:
--------------------------------------------------------------------------------
1 | using Viscon.Communication.Ads.Common;
2 | using Viscon.Communication.Ads.Variables;
3 |
4 | namespace Viscon.Communication.Ads.Conversation.ReadMultiple;
5 |
6 | public class AdsReadVariableException : AdsException
7 | {
8 | public IVariableAddressAndSize Variable { get; }
9 |
10 | public AdsReadVariableException(IVariableAddressAndSize variable, uint errorCode) : base(errorCode)
11 | {
12 | Variable = variable;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadMultiple/ArrayMultiReadResult.cs:
--------------------------------------------------------------------------------
1 | using Viscon.Communication.Ads.Variables;
2 |
3 | namespace Viscon.Communication.Ads.Conversation.ReadMultiple;
4 |
5 | public record ArrayMultiReadResult(IVariableData[] Results) : IMultiReadResult;
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadMultiple/ArrayMultiReadResultFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Viscon.Communication.Ads.Variables;
3 |
4 | namespace Viscon.Communication.Ads.Conversation.ReadMultiple;
5 |
6 | public class ArrayMultiReadResultFactory : IReadResultFactory
7 | {
8 | public ArrayMultiReadResult CreateResult(IVariableAddressAndSize[] variables, ReadOnlySpan span)
9 | {
10 | var results = new IVariableData[variables.Length];
11 |
12 | var offset = 0;
13 | for (var i = 0; i < variables.Length; i++)
14 | {
15 | var variable = variables[i];
16 |
17 | results[i] = new ArrayVariableData(variable, span.Slice(offset, (int)variable.Size).ToArray());
18 |
19 | offset += (int)variable.Size;
20 | }
21 |
22 | return new ArrayMultiReadResult(results);
23 | }
24 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadMultiple/IMultiReadResult.cs:
--------------------------------------------------------------------------------
1 | using Viscon.Communication.Ads.Variables;
2 |
3 | namespace Viscon.Communication.Ads.Conversation.ReadMultiple;
4 |
5 | public interface IMultiReadResult
6 | {
7 | IVariableData[] Results { get; }
8 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadMultiple/IReadResultFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Viscon.Communication.Ads.Variables;
3 |
4 | namespace Viscon.Communication.Ads.Conversation.ReadMultiple;
5 |
6 | public interface IReadResultFactory
7 | {
8 | TResult CreateResult(IVariableAddressAndSize[] variables, ReadOnlySpan span);
9 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadMultiple/PooledArrayMultiReadResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 | using Viscon.Communication.Ads.Variables;
4 |
5 | namespace Viscon.Communication.Ads.Conversation.ReadMultiple;
6 |
7 | public class PooledArrayMultiReadResult : IMultiReadResult, IDisposable
8 | {
9 | private readonly byte[] data;
10 |
11 | public PooledArrayMultiReadResult(IVariableData[] results, byte[] data)
12 | {
13 | Results = results;
14 |
15 | this.data = data;
16 | }
17 |
18 | public IVariableData[] Results { get; }
19 |
20 | public void Dispose()
21 | {
22 | ArrayPool.Shared.Return(data);
23 | }
24 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadMultiple/PooledArrayMultiReadResultFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 | using System.Linq;
4 | using Viscon.Communication.Ads.Variables;
5 |
6 | namespace Viscon.Communication.Ads.Conversation.ReadMultiple;
7 |
8 | public class PooledArrayMultiReadResultFactory : IReadResultFactory
9 | {
10 | public PooledArrayMultiReadResult CreateResult(IVariableAddressAndSize[] variables, ReadOnlySpan span)
11 | {
12 | var data = ArrayPool.Shared.Rent((int)variables.Sum(v => v.Size));
13 | try
14 | {
15 | var results = new IVariableData[variables.Length];
16 | span.CopyTo(data);
17 |
18 | var offset = 0;
19 | for (var i = 0; i < variables.Length;i++)
20 | {
21 | var variable = variables[i];
22 |
23 | results[i] = new ArraySegmentVariableData(variable,
24 | new ArraySegment(data, offset, (int)variable.Size));
25 |
26 | offset += (int) variable.Size;
27 | }
28 |
29 | return new PooledArrayMultiReadResult(results, data);
30 | }
31 | catch
32 | {
33 | ArrayPool.Shared.Return(data);
34 | throw;
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadMultiple/ReadMultipleException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Viscon.Communication.Ads.Conversation.ReadMultiple;
5 |
6 | internal class ReadMultipleException
7 | {
8 | public static void Throw(IEnumerable innerExceptions)
9 | {
10 | throw new AggregateException(
11 | "One or multiple variables could not be read, see InnerExceptions for details.", innerExceptions);
12 | }
13 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadSymbols/AdsReadSymbolsConversation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Viscon.Communication.Ads.Common;
4 | using Viscon.Communication.Ads.Conversation.ReadUploadInfo;
5 | using Viscon.Communication.Ads.Internal;
6 |
7 | namespace Viscon.Communication.Ads.Conversation.ReadSymbols
8 | {
9 | internal class AdsReadSymbolsConversation : IAdsConversation>
10 | {
11 | public uint SymbolLength { get; }
12 |
13 | public AdsReadSymbolsConversation(uint symbolLength)
14 | {
15 | SymbolLength = symbolLength;
16 | }
17 |
18 | public AdsReadSymbolsConversation(AdsUploadInfoDto uploadInfo) : this(uploadInfo.SymbolLength)
19 | {
20 | }
21 |
22 | public AdsReadRequest BuildRequest()
23 | {
24 | return new AdsReadRequest(AdsReservedIndexGroup.SymbolUpload.ToUInt32(), 0, SymbolLength);
25 | }
26 |
27 | public List ParseResponse(ReadOnlySpan buffer)
28 | {
29 | var length = LittleEndianDeserializer.ReadInt32(buffer);
30 | if (length != buffer.Length - 4)
31 | {
32 | throw new Exception(
33 | $"Received {buffer.Length} bytes of data while length indicates length should be {length} bytes.");
34 | }
35 |
36 | buffer = buffer.Slice(sizeof(uint));
37 |
38 | var results = new List();
39 | while (buffer.Length > 0)
40 | {
41 | var offset = WireFormatting.ReadInt32(buffer, out var len);
42 | results.Add(AdsSymbolParser.ParseSymbol(buffer.Slice(offset, len - offset)));
43 |
44 | buffer = buffer.Slice(len);
45 | }
46 |
47 | return results;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadSymbols/AdsSymbolParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Viscon.Communication.Ads.Common;
3 | using Viscon.Communication.Ads.Internal;
4 |
5 | namespace Viscon.Communication.Ads.Conversation.ReadSymbols;
6 |
7 | internal class AdsSymbolParser
8 | {
9 | public static AdsSymbol ParseSymbol(ReadOnlySpan buffer)
10 | {
11 | var offset = WireFormatting.ReadUInt32(buffer, out var indexGroup);
12 | offset += WireFormatting.ReadUInt32(buffer.Slice(offset), out var indexOffset);
13 | offset += WireFormatting.ReadUInt32(buffer.Slice(offset), out var size);
14 | offset += WireFormatting.ReadUInt32(buffer.Slice(offset), out var adsDataType);
15 | offset += WireFormatting.ReadUInt16(buffer.Slice(offset), out var adsSymbolFlags);
16 | offset += WireFormatting.ReadUInt16(buffer.Slice(offset), out var arrayDimension);
17 | offset += WireFormatting.ReadUInt16(buffer.Slice(offset), out var nameLength);
18 | offset += WireFormatting.ReadUInt16(buffer.Slice(offset), out var typeLength);
19 | offset += WireFormatting.ReadUInt16(buffer.Slice(offset), out var commentLength);
20 | offset += WireFormatting.ReadString(buffer.Slice(offset), nameLength, out var name);
21 | offset += WireFormatting.ReadString(buffer.Slice(offset), typeLength, out var type);
22 | offset += WireFormatting.ReadString(buffer.Slice(offset), commentLength, out var comment);
23 |
24 | return new AdsSymbol(indexGroup, indexOffset, size, name, type, comment);
25 | }
26 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadUploadInfo/AdsReadUploadInfoConversation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Viscon.Communication.Ads.Common;
3 | using Viscon.Communication.Ads.Internal;
4 |
5 | namespace Viscon.Communication.Ads.Conversation.ReadUploadInfo;
6 |
7 | internal class AdsReadUploadInfoConversation : IAdsConversation
8 | {
9 | public AdsReadRequest BuildRequest() => new(AdsReservedIndexGroup.SymbolUploadInfo2.ToUInt32(), 0, 24);
10 |
11 | public AdsUploadInfoDto ParseResponse(ReadOnlySpan buffer)
12 | {
13 | var length = LittleEndianDeserializer.ReadUInt32(buffer);
14 | if (length != 24)
15 | {
16 | throw new Exception(
17 | $"Received {buffer.Length} bytes of data while length indicates length should be {length} bytes.");
18 | }
19 |
20 | return new AdsUploadInfoDto(
21 | LittleEndianDeserializer.ReadUInt32(buffer.Slice(4)),
22 | LittleEndianDeserializer.ReadUInt32(buffer.Slice(8)),
23 | LittleEndianDeserializer.ReadUInt32(buffer.Slice(12)),
24 | LittleEndianDeserializer.ReadUInt32(buffer.Slice(16)),
25 | LittleEndianDeserializer.ReadUInt32(buffer.Slice(20)),
26 | LittleEndianDeserializer.ReadUInt32(buffer.Slice(24)));
27 | }
28 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/ReadUploadInfo/AdsUploadInfoDto.cs:
--------------------------------------------------------------------------------
1 | namespace Viscon.Communication.Ads.Conversation.ReadUploadInfo;
2 |
3 | public record AdsUploadInfoDto(uint SymbolCount, uint SymbolLength, uint DataTypeCount, uint DataTypeLength,
4 | uint ExtraCount, uint ExtraLength);
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/WriteMultiple/AdsWriteMultipleConversation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.InteropServices;
4 | using Viscon.Communication.Ads.Internal;
5 | using Viscon.Communication.Ads.Variables;
6 |
7 | namespace Viscon.Communication.Ads.Conversation.WriteMultiple
8 | {
9 | internal class AdsWriteMultipleConversation : IAdsConversation
10 | {
11 | public IVariableData[] Variables { get; }
12 |
13 | public AdsWriteMultipleConversation(IVariableData[] variables)
14 | {
15 | Variables = variables;
16 | }
17 |
18 | public AdsWriteMultipleRequest BuildRequest()
19 | {
20 | return new AdsWriteMultipleRequest(Variables);
21 | }
22 |
23 | public object ParseResponse(ReadOnlySpan buffer)
24 | {
25 | var offset = WireFormatting.ReadInt32(buffer, out var dataLength);
26 | Assertions.AssertDataLength(buffer, dataLength, offset);
27 |
28 | var results = MemoryMarshal.Cast(buffer.Slice(offset));
29 | List exceptions = null;
30 | for (var i = 0; i < Variables.Length; i++)
31 | {
32 | if (results[i] <= 0) continue;
33 |
34 | exceptions ??= new List();
35 | exceptions.Add(new AdsWriteVariableException(Variables[i], results[i]));
36 | }
37 |
38 | if (exceptions is not null) WriteMultipleException.Throw(exceptions);
39 |
40 | return null;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/WriteMultiple/AdsWriteMultipleRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using Viscon.Communication.Ads.Common;
4 | using Viscon.Communication.Ads.Internal;
5 | using Viscon.Communication.Ads.Variables;
6 |
7 | namespace Viscon.Communication.Ads.Conversation.WriteMultiple;
8 |
9 | public readonly struct AdsWriteMultipleRequest : IAdsRequest
10 | {
11 | private readonly IVariableData[] variables;
12 |
13 | public AdsWriteMultipleRequest(IVariableData[] variables)
14 | {
15 | this.variables = variables;
16 | }
17 |
18 | public AdsCommandEnum Command => AdsCommandEnum.ReadWrite;
19 |
20 | public int GetRequestLength()
21 | {
22 | return sizeof(uint) * 4 + GetDataLength();
23 | }
24 |
25 | public int BuildRequest(Span span)
26 | {
27 | var offset = WireFormatting.WriteUInt32(ref span.GetStart(), AdsReservedIndexGroup.SumCommandWrite);
28 | // Number of variables
29 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), (uint) variables.Length);
30 | // Expected response size (error code per item)
31 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), (uint) (sizeof(uint) * variables.Length));
32 | // Data length (address + length of value + value per item)
33 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), (uint) GetDataLength());
34 |
35 | foreach (var variable in variables)
36 | {
37 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), variable.Address.IndexGroup);
38 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), variable.Address.IndexOffset);
39 | offset += WireFormatting.WriteUInt32(ref span.GetOffset(offset), variable.Data.Length);
40 | }
41 |
42 | foreach (var variable in variables)
43 | {
44 | var variableData = variable.Data;
45 | variableData.CopyTo(span.Slice(offset));
46 | offset += variableData.Length;
47 | }
48 |
49 | return offset;
50 | }
51 |
52 | private int GetDataLength() => variables.Sum(v => v.Data.Length + sizeof(uint) * 3);
53 | }
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/WriteMultiple/AdsWriteVariableException.cs:
--------------------------------------------------------------------------------
1 | using Viscon.Communication.Ads.Common;
2 | using Viscon.Communication.Ads.Variables;
3 |
4 | namespace Viscon.Communication.Ads.Conversation.WriteMultiple
5 | {
6 | public class AdsWriteVariableException : AdsException
7 | {
8 | public IVariableData Variable { get; }
9 |
10 | public AdsWriteVariableException(IVariableData variable, uint errorCode) : base(errorCode)
11 | {
12 | Variable = variable;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/AdsClient/Conversation/WriteMultiple/WriteMultipleException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Viscon.Communication.Ads.Conversation.WriteMultiple;
5 |
6 | internal static class WriteMultipleException
7 | {
8 | public static void Throw(IEnumerable itemExceptions)
9 | {
10 | throw new AggregateException("One or multiple items could not be written, see InnerExceptions for details.",
11 | itemExceptions);
12 | }
13 | }
--------------------------------------------------------------------------------
/src/AdsClient/Framework/EncodingExtensions.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable once CheckNamespace
2 | namespace System.Text;
3 |
4 | #if !NETSTANDARD2_1_OR_GREATER
5 |
6 | internal static class EncodingExtensions
7 | {
8 | public static int GetBytes(this Encoding encoding, ReadOnlySpan chars, Span buffer)
9 | {
10 | var bufferArr = new byte[buffer.Length];
11 |
12 | var len = encoding.GetBytes(chars.ToArray(), 0, chars.Length, bufferArr, 0);
13 |
14 | bufferArr.AsSpan().CopyTo(buffer);
15 |
16 | return len;
17 | }
18 |
19 | public static string GetString(this Encoding encoding, ReadOnlySpan buffer)
20 | {
21 | return encoding.GetString(buffer.ToArray());
22 | }
23 | }
24 |
25 | #endif
26 |
--------------------------------------------------------------------------------
/src/AdsClient/Framework/IsExternalInit.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | // ReSharper disable once CheckNamespace
4 | namespace System.Runtime.CompilerServices;
5 |
6 | [EditorBrowsable(EditorBrowsableState.Never)]
7 | internal record IsExternalInit;
--------------------------------------------------------------------------------
/src/AdsClient/Helpers/AdsAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 |
4 | namespace Viscon.Communication.Ads.Helpers
5 | {
6 | ///
7 | /// An attribute class for members of serializable classes.
8 | ///
9 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
10 | public class AdsAttribute : Attribute
11 | {
12 | ///
13 | /// The byte size.
14 | ///
15 | public uint ByteSize;
16 |
17 | ///
18 | /// The order.
19 | ///
20 | public uint Order;
21 |
22 | ///
23 | /// The array size.
24 | ///
25 | public uint ArraySize;
26 |
27 | ///
28 | /// Creates a new instance.
29 | ///
30 | public AdsAttribute()
31 | {
32 | ByteSize = 0;
33 | Order = 99;
34 | ArraySize = 1;
35 | }
36 |
37 | private MemberInfo member;
38 | ///
39 | /// Gets or sets the member.
40 | ///
41 | internal MemberInfo Member
42 | {
43 | get { return member; }
44 | set { member = value; }
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/src/AdsClient/Helpers/AdsSerializableAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Helpers
4 | {
5 | [System.AttributeUsage(System.AttributeTargets.Class)]
6 | public class AdsSerializableAttribute : Attribute
7 | {
8 | public AdsSerializableAttribute()
9 | {
10 |
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/AdsClient/Helpers/AmsHeaderHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Helpers
4 | {
5 | internal class AmsHeaderHelper
6 | {
7 | public const int AmsTcpHeaderSize = 6;
8 | public const int AmsHeaderSize = 32;
9 | public const int AmsDataLengthOffset = 20;
10 |
11 | public static uint GetResponseLength(byte[] tcpHeader)
12 | {
13 | return BitConverter.ToUInt32(tcpHeader, 2);
14 | }
15 |
16 | public static byte[] GetAmsNetIdTarget(byte[] amsHeader)
17 | {
18 | byte[] id = new byte[6];
19 | Array.Copy(amsHeader, 0, id, 0, 6);
20 | return id;
21 | }
22 |
23 | public static ushort GetAmsPortTarget(byte[] amsHeader)
24 | {
25 | return BitConverter.ToUInt16(amsHeader, 14);
26 | }
27 |
28 | public static byte[] GetAmsNetIdSource(byte[] amsHeader)
29 | {
30 | byte[] id = new byte[6];
31 | Array.Copy(amsHeader, 8, id, 0, 6);
32 | return id;
33 | }
34 |
35 | public static ushort GetAmsPortSource(byte[] amsHeader)
36 | {
37 | return BitConverter.ToUInt16(amsHeader, 14);
38 | }
39 |
40 | public static ushort GetCommandId(byte[] amsHeader)
41 | {
42 | return BitConverter.ToUInt16(amsHeader, 16);
43 | }
44 |
45 | public static uint GetErrorCode(byte[] amsHeader)
46 | {
47 | return BitConverter.ToUInt32(amsHeader, 24);
48 | }
49 |
50 | public static uint GetInvokeId(byte[] amsHeader)
51 | {
52 | return BitConverter.ToUInt32(amsHeader, 28);
53 | }
54 |
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/AdsClient/Helpers/AmsMessageBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Viscon.Communication.Ads.CommandResponse;
5 | using Viscon.Communication.Ads.Commands;
6 | using Viscon.Communication.Ads.Common;
7 | using Viscon.Communication.Ads.Internal;
8 |
9 | namespace Viscon.Communication.Ads.Helpers
10 | {
11 | internal static class AmsMessageBuilder
12 | {
13 | public static byte[] BuildAmsMessage(Ams ams, AdsCommand adsCommand, uint invokeId) where TResponse : AdsCommandResponse, new()
14 | {
15 |
16 | IEnumerable data = adsCommand.GetBytes();
17 | IEnumerable message = ams.AmsNetIdTarget.Bytes; //AmsNetId Target
18 | message = message.Concat(
19 | BitConverter.GetBytes(adsCommand.AmsPortTargetOverride ?? ams.AmsPortTarget)); //AMSPort Target
20 | message = message.Concat(ams.AmsNetIdSource.Bytes); //AmsNetId Source
21 | message = message.Concat(BitConverter.GetBytes(ams.AmsPortSource)); //AMSPort Source
22 | message = message.Concat(BitConverter.GetBytes(adsCommand.CommandId)); //Command Id
23 | message = message.Concat(BitConverter.GetBytes((ushort)0x0004)); //State Flags
24 | message = message.Concat(BitConverter.GetBytes((uint)data.Count())); //Length
25 | message = message.Concat(BitConverter.GetBytes((uint)0)); //Error Code
26 | message = message.Concat(BitConverter.GetBytes(invokeId)); //Invoke Id
27 | message = message.Concat(data); //Data
28 |
29 | //2 bytes reserved 0 + 4 bytes for length + the rest
30 | message = BitConverter.GetBytes((ushort)0).Concat(BitConverter.GetBytes((uint)message.Count()))
31 | .Concat(message);
32 |
33 | return message.ToArray();
34 | }
35 |
36 | public static void WriteHeader(Span buffer, Ams ams, AdsCommandEnum command, ushort amsPortTarget, uint invokeId, int messageLength)
37 | {
38 | var offset = WireFormatting.WriteUInt16(ref buffer.GetStart(), default);
39 | offset += WireFormatting.WriteUInt32(ref buffer.GetOffset(offset),
40 | AmsHeaderHelper.AmsHeaderSize + messageLength);
41 |
42 | ams.AmsNetIdTarget.Bytes.ToArray().CopyTo(buffer.Slice(offset));
43 | offset += 6;
44 |
45 | offset += WireFormatting.WriteUInt16(ref buffer.GetOffset(offset), amsPortTarget);
46 |
47 | ams.AmsNetIdSource.Bytes.ToArray().CopyTo(buffer.Slice(offset));
48 | offset += 6;
49 |
50 | offset += WireFormatting.WriteUInt16(ref buffer.GetOffset(offset), ams.AmsPortSource);
51 | offset += WireFormatting.WriteUInt16(ref buffer.GetOffset(offset), command);
52 | offset += WireFormatting.WriteUInt16(ref buffer.GetOffset(offset), (ushort) 0x0004);
53 | offset += WireFormatting.WriteUInt32(ref buffer.GetOffset(offset), (uint) messageLength);
54 | offset += WireFormatting.WriteUInt32(ref buffer.GetOffset(offset), (uint) 0);
55 | offset += WireFormatting.WriteUInt32(ref buffer.GetOffset(offset), invokeId);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/AdsClient/Helpers/ByteArrayHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Text;
4 | using Viscon.Communication.Ads.Common;
5 |
6 | namespace Viscon.Communication.Ads.Helpers
7 | {
8 | public class ByteArrayHelper
9 | {
10 | ///
11 | /// Convert byte array to value defined by TypeOfValue
12 | ///
13 | /// The byte array that needs conversion
14 | /// The type of the result
15 | ///
16 | public static object ByteArrayToTypeValue(byte[] value, Type typeOfValue)
17 | {
18 | //if (Type.Equals(TypeOfValue, null)) return null;
19 | //if (Type.Equals(TypeOfValue, typeof(byte[]))) return value;
20 | //var method = typeof(GenericHelper).GetMethod("GetResultFromBytes");
21 | //var generic = method.MakeGenericMethod(TypeOfValue);
22 | //return generic.Invoke(null, new object[] { value });
23 | return GenericHelper.GetResultFromBytes(typeOfValue, value, 0);
24 | }
25 |
26 | public static string ByteArrayToString(byte[] value)
27 | {
28 | if (value == null)
29 | return "";
30 | else
31 | return string.Concat(value.Select(b => b <= 0x7f ? (char)b : '?').TakeWhile(b => b > 0)) ?? "";
32 | }
33 |
34 | public static string ByteArrayToTestString(byte[] value)
35 | {
36 | StringBuilder sb = new StringBuilder();
37 | foreach (byte val in value)
38 | {
39 | if (sb.Length > 0) sb.Append(',');
40 | sb.Append(val.ToString());
41 | }
42 | return sb.ToString();
43 | }
44 |
45 | public static DateTime ByteArrayToDateTime(byte[] value)
46 | {
47 | var seconds = BitConverter.ToUInt32(value, 0);
48 | var val = new Date(seconds);
49 | return val.ToDateTime(null);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/AdsClient/Helpers/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Viscon.Communication.Ads.Common;
3 |
4 | namespace Viscon.Communication.Ads.Helpers
5 | {
6 | public static class Extensions
7 | {
8 | public static byte[] ToAdsBytes(this String value)
9 | {
10 | byte[] result = new byte[value.Length + 1];
11 |
12 | for (int index = 0; index < value.Length; ++index)
13 | {
14 | char ch = value[index];
15 | if (ch <= 0x7f)
16 | result[index] = (byte)ch;
17 | else
18 | result[index] = (byte)'?';
19 | }
20 | result[result.Length-1] = 0;
21 |
22 | return result;
23 | }
24 |
25 | public static Time ToTime(this TimeSpan value)
26 | {
27 | return new Time(value);
28 | }
29 |
30 | public static Date ToDate(this DateTime value)
31 | {
32 | return new Date(value);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/AdsClient/Helpers/GenericHelper.cs:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2011 Inando
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a
4 | copy of this software and associated documentation files (the "Software"),
5 | to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 | and/or sell copies of the Software, and to permit persons to whom the
8 | Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included
11 | in all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
18 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
19 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | */
21 |
22 | using System;
23 | using System.Collections.Generic;
24 | using System.Linq;
25 | using Viscon.Communication.Ads.Common;
26 |
27 | namespace Viscon.Communication.Ads.Helpers
28 | {
29 | ///
30 | /// An enumeration of types known to ADS.
31 | ///
32 | internal enum AdsTypeEnum { Unknown, Bool, Byte, Char, Int16, Int32, Int64, UInt16, UInt32, UInt64, Single, Double, String, DateTime, Date, Time };
33 |
34 | ///
35 | /// A helper class for various conversions.
36 | ///
37 | internal static class GenericHelper
38 | {
39 | const string TypeNotImplementedError = "Type {0} must be implemented or has the AdsSerializable attribute!";
40 |
41 | #region Type byte length
42 | ///
43 | /// Get length in bytes from a valuetype or AdsSerializable
44 | ///
45 | /// The default string length.
46 | /// The array length.
47 | /// The length.
48 | public static uint GetByteLengthFromType(uint defaultStringLength, uint arrayLength = 1)
49 | {
50 | return GetByteLengthFromType(typeof(T), defaultStringLength, arrayLength);
51 | }
52 |
53 | ///
54 | /// Get length in bytes from a valuetype or AdsSerializable
55 | ///
56 | /// The type.
57 | /// The default string length.
58 | /// The array length.
59 | /// The length
60 | public static uint GetByteLengthFromType(Type type, uint defaultStringLength, uint arrayLength = 1)
61 | {
62 | uint factor = 1;
63 |
64 | if (type.IsArray)
65 | {
66 | factor = arrayLength;
67 | type = type.GetElementType();
68 | }
69 |
70 | var adsType = GetEnumFromType(type);
71 |
72 | if (adsType != AdsTypeEnum.Unknown)
73 | {
74 | var length = GetByteLengthFromConvertible(adsType, defaultStringLength);
75 | if (length == 0)
76 | throw new AdsException(String.Format("Function GetByteLengthFromType doesn't support this type ({0}) yet!", type.FullName));
77 | return length * factor;
78 | }
79 | else
80 | {
81 | if (type.IsAdsSerializable())
82 | {
83 | uint length = 0;
84 | var attributes = GetAdsAttributes(type, defaultStringLength);
85 | foreach (var a in attributes)
86 | {
87 | length += a.ByteSize;
88 | }
89 | return length * factor;
90 | }
91 | else
92 | throw new AdsException(String.Format(TypeNotImplementedError, type.FullName));
93 | }
94 | }
95 | #endregion
96 |
97 | #region Byte array -> Object
98 | ///
99 | /// Convert byte array to valuetype or AdsSerializable
100 | ///
101 | ///
102 | ///
103 | ///
104 | ///
105 | ///
106 | /// The resulting object.
107 | public static object GetResultFromBytes(Type type, byte[] value, uint defaultStringLength, uint arrayLength = 1, object adsObj = null)
108 | {
109 | if (value == null)
110 | throw new ArgumentNullException("value", "GetResultFromBytes");
111 |
112 | if (type.IsArray)
113 | {
114 | var elementType = type.GetElementType();
115 | var elementSize = (int)(value.Length / arrayLength);
116 | var array = (adsObj as Array) ?? Array.CreateInstance(elementType, new int[] { (int)arrayLength });
117 |
118 | for (var i = 0; i < arrayLength; i++)
119 | {
120 | // Prepare the new value array.
121 | var valarray = new byte[elementSize];
122 | Array.Copy(value, i * elementSize, valarray, 0, elementSize);
123 |
124 | // Get the previous value (if available) and store the results.
125 | var val = array.GetValue(i);
126 | val = GetResultFromBytes(elementType, valarray, defaultStringLength, 1, val);
127 | array.SetValue(val, i);
128 | }
129 |
130 | return array;
131 | }
132 | else
133 | return GetResultFromBytesInternal(type, value, defaultStringLength, adsObj);
134 | }
135 |
136 | private static object GetResultFromBytesInternal(Type type, byte[] value, uint defaultStringLength, object adsObj)
137 | {
138 | var adsType = GetEnumFromType(type);
139 | if (adsType != AdsTypeEnum.Unknown)
140 | {
141 | return ConvertBytesToType(adsType, value);
142 | }
143 | else
144 | {
145 | if (type.IsAdsSerializable())
146 | {
147 | if (adsObj == null)
148 | adsObj = Activator.CreateInstance(type);
149 |
150 | var attributes = GetAdsAttributes(type, defaultStringLength);
151 |
152 | uint pos = 0;
153 |
154 | foreach (var attr in attributes)
155 | {
156 | try
157 | {
158 | // Prepare the new value array.
159 | var valarray = new byte[attr.ByteSize];
160 | Array.Copy(value, (int)pos, valarray, 0, (int)attr.ByteSize);
161 |
162 | // Get the previous data (if available) and store the results.
163 | var proptype = attr.Member.GetMemberType();
164 | var val = attr.Member.GetValue(adsObj);
165 | val = GetResultFromBytes(proptype, valarray, defaultStringLength, attr.ArraySize, val);
166 | attr.Member.SetValue(adsObj, val);
167 |
168 | // Increment byte pointer.
169 | pos += attr.ByteSize;
170 | }
171 | catch
172 | { break; }
173 | }
174 |
175 | return adsObj;
176 | }
177 | else
178 | throw new AdsException(String.Format(TypeNotImplementedError, type.FullName));
179 | }
180 | }
181 |
182 |
183 | ///
184 | /// Convert byte array to generic valuetype or AdsSerializable
185 | ///
186 | /// ValueType or AdsSerializable
187 | ///
188 | ///
189 | public static T GetResultFromBytes(byte[] value, uint defaultStringLength, uint arrayLength = 1, object adsObj = null)
190 | {
191 | Type type = typeof(T);
192 | var o = GetResultFromBytes(type, value, defaultStringLength, arrayLength, adsObj);
193 | if (o == null)
194 | return default(T);
195 | else
196 | return (T)Convert.ChangeType(o, type);
197 | }
198 | #endregion
199 |
200 | #region Object -> Byte array
201 | ///
202 | /// Convert ValueType or AdsSerializable to byte array
203 | ///
204 | /// ValueType or AdsSerializable
205 | /// The value that needs conversion
206 | ///
207 | public static IEnumerable GetBytesFromType(T varValue, uint defaultStringLength)
208 | {
209 | List varValueBytes = null;
210 | var type = typeof(T);
211 |
212 | var adsType = GetEnumFromType(type);
213 | if (adsType != AdsTypeEnum.Unknown)
214 | {
215 | varValueBytes = GetBytesFromConvertible(adsType, varValue, defaultStringLength).ToList();
216 | }
217 | else
218 | {
219 | if (type.IsAdsSerializable())
220 | {
221 | var totallength = GetByteLengthFromType(type, defaultStringLength);
222 | varValueBytes = new List((int)totallength);
223 | var attributes = GetAdsAttributes(type, defaultStringLength);
224 |
225 | foreach (var attr in attributes)
226 | {
227 | var memberType = attr.Member.GetMemberType();
228 | adsType = GetEnumFromType(memberType);
229 | var val = attr.Member.GetValue(varValue);
230 | var bytes = GetBytesFromConvertible(adsType, val, defaultStringLength);
231 | if (bytes.Count() != attr.ByteSize)
232 | {
233 | if (bytes.Count() > attr.ByteSize)
234 | bytes = bytes.Take((int)attr.ByteSize).ToList();
235 | }
236 | varValueBytes.AddRange(bytes);
237 | }
238 | }
239 | }
240 |
241 | if (varValueBytes == null)
242 | throw new AdsException("Function GetBytesFromType doesn't support this type yet!");
243 |
244 | return varValueBytes;
245 | }
246 | #endregion
247 |
248 | #region AdsAttribute enumeration
249 | ///
250 | /// Gets all members marked with an .
251 | ///
252 | /// The type.
253 | /// The default string length.
254 | /// An enumeration of attributes.
255 | public static IEnumerable GetAdsAttributes(Type type, uint defaultStringLength)
256 | {
257 | var attributes = new List();
258 | var members = type.GetPublicFieldsAndProperties();
259 |
260 | foreach (var member in members)
261 | {
262 | var attr = member.GetAdsAttribute();
263 |
264 | if (attr != null)
265 | {
266 | attr.Member = member;
267 | if (attr.ByteSize == 0)
268 | {
269 | var memberType = member.GetMemberType();
270 | attr.ByteSize = GetByteLengthFromType(memberType, defaultStringLength, attr.ArraySize);
271 | }
272 | attributes.Add(attr);
273 | }
274 | }
275 |
276 | return attributes.OrderBy(a => a.Order);
277 | }
278 | #endregion
279 |
280 | #region Known type conversions
281 | private static IEnumerable GetBytesFromConvertible(AdsTypeEnum adsType, object value, uint defaultStringLength)
282 | {
283 | IEnumerable varValueBytes = null;
284 |
285 | if (value == null) return null;
286 |
287 | switch (adsType)
288 | {
289 | case AdsTypeEnum.Bool: varValueBytes = BitConverter.GetBytes((bool)value); break;
290 | case AdsTypeEnum.Byte: varValueBytes = new byte[] { (byte)value }; break;
291 | case AdsTypeEnum.Char: varValueBytes = BitConverter.GetBytes((char)value); break;
292 | case AdsTypeEnum.Int16: varValueBytes = BitConverter.GetBytes((Int16)value); break;
293 | case AdsTypeEnum.Int32: varValueBytes = BitConverter.GetBytes((Int32)value); break;
294 | case AdsTypeEnum.Int64: varValueBytes = BitConverter.GetBytes((Int64)value); break;
295 | case AdsTypeEnum.UInt16: varValueBytes = BitConverter.GetBytes((UInt16)value); break;
296 | case AdsTypeEnum.UInt32: varValueBytes = BitConverter.GetBytes((UInt32)value); break;
297 | case AdsTypeEnum.UInt64: varValueBytes = BitConverter.GetBytes((UInt64)value); break;
298 | case AdsTypeEnum.Single: varValueBytes = BitConverter.GetBytes((Single)value); break;
299 | case AdsTypeEnum.Double: varValueBytes = BitConverter.GetBytes((Double)value); break;
300 | case AdsTypeEnum.DateTime: varValueBytes = BitConverter.GetBytes((Int32)value); break;
301 | case AdsTypeEnum.String: varValueBytes = value.ToString().ToAdsBytes(); break;
302 | case AdsTypeEnum.Date: varValueBytes = BitConverter.GetBytes((Int32)value); break;
303 | case AdsTypeEnum.Time: varValueBytes = BitConverter.GetBytes((Int32)value); break;
304 | }
305 |
306 | return varValueBytes;
307 | }
308 |
309 | private static uint GetByteLengthFromConvertible(AdsTypeEnum adsType, uint defaultStringLength)
310 | {
311 | uint length = 0;
312 |
313 | switch (adsType)
314 | {
315 | case AdsTypeEnum.Bool: length = 1; break;
316 | case AdsTypeEnum.Byte: length = 1; break;
317 | case AdsTypeEnum.Int16: length = 2; break;
318 | case AdsTypeEnum.Int32: length = 4; break;
319 | case AdsTypeEnum.Int64: length = 8; break;
320 | case AdsTypeEnum.UInt16: length = 2; break;
321 | case AdsTypeEnum.UInt32: length = 4; break;
322 | case AdsTypeEnum.UInt64: length = 8; break;
323 | case AdsTypeEnum.Single: length = 4; break;
324 | case AdsTypeEnum.Double: length = 8; break;
325 | case AdsTypeEnum.String: length = defaultStringLength; break;
326 | case AdsTypeEnum.DateTime: length = 4; break;
327 | case AdsTypeEnum.Date: length = 4; break;
328 | case AdsTypeEnum.Time: length = 4; break;
329 | }
330 |
331 | return length;
332 | }
333 |
334 | private static object ConvertBytesToType(AdsTypeEnum adsType, byte[] value)
335 | {
336 | try
337 | {
338 | switch (adsType)
339 | {
340 | case AdsTypeEnum.Bool: return BitConverter.ToBoolean(value, 0);
341 | case AdsTypeEnum.Byte: return value[0];
342 | case AdsTypeEnum.Int16: return BitConverter.ToInt16(value, 0);
343 | case AdsTypeEnum.Int32: return BitConverter.ToInt32(value, 0);
344 | case AdsTypeEnum.Int64: return BitConverter.ToInt64(value, 0);
345 | case AdsTypeEnum.UInt16: return BitConverter.ToUInt16(value, 0);
346 | case AdsTypeEnum.UInt32: return BitConverter.ToUInt32(value, 0);
347 | case AdsTypeEnum.UInt64: return BitConverter.ToUInt64(value, 0);
348 | case AdsTypeEnum.Single: return BitConverter.ToSingle(value, 0);
349 | case AdsTypeEnum.Double: return BitConverter.ToDouble(value, 0);
350 | case AdsTypeEnum.String: return ByteArrayHelper.ByteArrayToString(value);
351 | case AdsTypeEnum.DateTime: return ByteArrayHelper.ByteArrayToDateTime(value);
352 | case AdsTypeEnum.Date: return new Date(BitConverter.ToUInt32(value, 0));
353 | case AdsTypeEnum.Time: return new Time(BitConverter.ToUInt32(value, 0));
354 | default:
355 | return null;
356 | }
357 | }
358 | catch
359 | {
360 | return null;
361 | }
362 | }
363 |
364 | public static AdsTypeEnum GetEnumFromType(Type type)
365 | {
366 | if (Type.Equals(type, typeof(bool))) return AdsTypeEnum.Bool;
367 | if (Type.Equals(type, typeof(byte))) return AdsTypeEnum.Byte;
368 | if (Type.Equals(type, typeof(Int16))) return AdsTypeEnum.Int16;
369 | if (Type.Equals(type, typeof(Int32))) return AdsTypeEnum.Int32;
370 | if (Type.Equals(type, typeof(Int64))) return AdsTypeEnum.Int64;
371 | if (Type.Equals(type, typeof(UInt16))) return AdsTypeEnum.UInt16;
372 | if (Type.Equals(type, typeof(UInt32))) return AdsTypeEnum.UInt32;
373 | if (Type.Equals(type, typeof(UInt64))) return AdsTypeEnum.UInt64;
374 | if (Type.Equals(type, typeof(Single))) return AdsTypeEnum.Single;
375 | if (Type.Equals(type, typeof(Double))) return AdsTypeEnum.Double;
376 | if (Type.Equals(type, typeof(String))) return AdsTypeEnum.String;
377 | if (Type.Equals(type, typeof(DateTime))) return AdsTypeEnum.DateTime;
378 | if (Type.Equals(type, typeof(Date))) return AdsTypeEnum.Date;
379 | if (Type.Equals(type, typeof(Time))) return AdsTypeEnum.Time;
380 |
381 | return AdsTypeEnum.Unknown;
382 | }
383 | #endregion
384 | }
385 | }
386 |
--------------------------------------------------------------------------------
/src/AdsClient/Helpers/IdGenerator.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 |
3 | namespace Viscon.Communication.Ads.Helpers
4 | {
5 | internal class IdGenerator
6 | {
7 | private int id;
8 |
9 | public uint Next()
10 | {
11 | return (uint)Interlocked.Increment(ref id);
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/AdsClient/Helpers/MemberInfoHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 |
6 | namespace Viscon.Communication.Ads.Helpers
7 | {
8 | ///
9 | /// Extension classes for MemberInfo
10 | ///
11 | public static class MemberInfoHelper
12 | {
13 | private static object memberDictLock = new object();
14 | private static Dictionary> memberDict = new Dictionary>();
15 |
16 | private static object adsAttributeDictLock = new object();
17 | private static Dictionary adsAttributeDict = new Dictionary();
18 |
19 | private static object adsSerializableDictLock = new object();
20 | private static Dictionary adsSerializableDict = new Dictionary();
21 |
22 | ///
23 | /// Gets a flag, whether the type is ADS serializable.
24 | ///
25 | /// The type.
26 | /// True, if the type is serializable.
27 | public static bool IsAdsSerializable(this Type type)
28 | {
29 | lock (adsSerializableDictLock)
30 | {
31 | if (!adsSerializableDict.ContainsKey(type))
32 | adsSerializableDict.Add(type, type.GetTypeInfo().GetCustomAttributes().OfType().Count() > 0);
33 | return adsSerializableDict[type];
34 | }
35 | }
36 |
37 | ///
38 | /// Gets the that is associated to this member.
39 | ///
40 | /// The or .
41 | /// The attached , if available. Otherwise, null.
42 | public static AdsAttribute GetAdsAttribute(this MemberInfo info)
43 | {
44 | lock (adsAttributeDictLock)
45 | {
46 | if (!adsAttributeDict.ContainsKey(info))
47 | adsAttributeDict.Add(info, info.GetAdsAttributeInternal());
48 | return adsAttributeDict[info];
49 | }
50 | }
51 |
52 | ///
53 | /// Gets the that is associated to this member.
54 | ///
55 | /// The or .
56 | /// The attached , if available. Otherwise, null.
57 | private static AdsAttribute GetAdsAttributeInternal(this MemberInfo info)
58 | {
59 | var attributes = info.GetCustomAttributes(typeof(AdsAttribute), false);
60 |
61 | if ((attributes != null) && (attributes.Count() > 0))
62 | return (AdsAttribute)attributes.FirstOrDefault();
63 | else
64 | return null;
65 | }
66 |
67 | ///
68 | /// Get all public fields and properties.
69 | ///
70 | /// The type.
71 | /// All public fields and properties.
72 | public static IEnumerable GetPublicFieldsAndProperties(this Type type)
73 | {
74 | lock (memberDictLock)
75 | {
76 | if (!memberDict.ContainsKey(type))
77 | memberDict.Add(type, type.GetPublicFieldsAndPropertiesInternal().ToList());
78 | return memberDict[type];
79 | }
80 | }
81 |
82 | ///
83 | /// Get all public fields and properties.
84 | ///
85 | /// The type.
86 | /// All public fields and properties.
87 | private static IEnumerable GetPublicFieldsAndPropertiesInternal(this Type type)
88 | {
89 | // First, return all fields.
90 | foreach (var f in type.GetRuntimeFields())
91 | if (f.IsPublic)
92 | yield return f;
93 |
94 | // Then, return all properties.
95 | foreach (var p in type.GetRuntimeProperties())
96 | yield return p;
97 | }
98 |
99 | ///
100 | /// Gets the member type.
101 | ///
102 | /// The or .
103 | /// The underlying member type.
104 | public static Type GetMemberType(this MemberInfo info)
105 | {
106 | if (info is FieldInfo)
107 | return ((FieldInfo)info).FieldType;
108 | else
109 | return ((PropertyInfo)info).PropertyType;
110 | }
111 |
112 | ///
113 | /// Sets the value of a member.
114 | ///
115 | /// The or .
116 | /// The object.
117 | /// The value.
118 | public static void SetValue(this MemberInfo info, object obj, object value)
119 | {
120 | if (info is FieldInfo)
121 | ((FieldInfo)info).SetValue(obj, value);
122 | else
123 | {
124 | var pi = (PropertyInfo)info;
125 | if (pi.CanWrite)
126 | pi.SetValue(obj, value);
127 | }
128 | }
129 |
130 | ///
131 | /// Gets the value of a member.
132 | ///
133 | /// The or .
134 | /// The object.
135 | /// The value.
136 | public static object GetValue(this MemberInfo info, object obj)
137 | {
138 | if (info is FieldInfo)
139 | return ((FieldInfo)info).GetValue(obj);
140 | else
141 | return ((PropertyInfo)info).GetValue(obj);
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/AdsClient/Helpers/Signal.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Threading;
4 | using System.Threading.Channels;
5 | using System.Threading.Tasks;
6 |
7 | namespace Viscon.Communication.Ads.Helpers
8 | {
9 | [DebuggerNonUserCode]
10 | [DebuggerDisplay(nameof(NeedToWait) + ": {" + nameof(NeedToWait) + ",nq}")]
11 | internal class Signal : IDisposable
12 | {
13 | public Signal()
14 | {
15 | if (!channel.Writer.TryWrite(0))
16 | {
17 | throw new Exception("Failed to initialize the send signal.");
18 | }
19 | }
20 |
21 | private readonly Channel channel = Channel.CreateBounded(1);
22 |
23 | public void Dispose() => channel.Writer.Complete();
24 |
25 | public ValueTask WaitAsync(CancellationToken cancellationToken) =>
26 | channel.Reader.ReadAsync(cancellationToken);
27 |
28 | public bool TryRelease() => channel.Writer.TryWrite(0);
29 |
30 | private bool NeedToWait => channel.Reader.Count == 0;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/AdsClient/Helpers/StringHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 | using System.Text;
4 |
5 | namespace Viscon.Communication.Ads.Helpers
6 | {
7 | public static class StringHelper
8 | {
9 | public static Encoding CP1252 = CodePagesEncodingProvider.Instance.GetEncoding(1252);
10 |
11 | public static int Encode(ReadOnlySpan chars, Span buffer)
12 | {
13 | var len = CP1252.GetBytes(chars, buffer);
14 | buffer[len] = 0;
15 |
16 | return len + 1;
17 | }
18 |
19 | public static string Decode(ReadOnlySpan buffer)
20 | {
21 | var end = buffer.IndexOf((byte) 0);
22 | if (end > -1) buffer = buffer.Slice(0, end);
23 |
24 | return CP1252.GetString(buffer);
25 | }
26 |
27 | public static string Decode(ReadOnlySequence buffer)
28 | {
29 | return Decode(buffer.First.Span.Length < buffer.Length ? buffer.ToArray() : buffer.First.Span);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/AdsClient/IAmsSocket.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace Viscon.Communication.Ads
6 | {
7 | public interface IAmsSocket
8 | {
9 | bool Connected { get;}
10 |
11 | void Close();
12 |
13 | Task ConnectAsync(IIncomingMessageHandler messageHandler, CancellationToken cancellationToken = default);
14 |
15 | Task SendAsync(byte[] message);
16 |
17 | Task SendAsync(ArraySegment buffer);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/AdsClient/IIncomingMessageHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads;
4 |
5 | public interface IIncomingMessageHandler
6 | {
7 | void HandleException(Exception exception);
8 | void HandleMessage(byte[] message);
9 | }
--------------------------------------------------------------------------------
/src/AdsClient/Internal/Assertions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Internal;
4 |
5 | internal class Assertions
6 | {
7 | public static void AssertDataLength(ReadOnlySpan buffer, int length, int offset)
8 | {
9 | if (length != buffer.Length - offset)
10 | {
11 | throw new Exception(
12 | $"Received {buffer.Length} bytes of data, but length indicates {length} bytes remaining at offset {offset}, resulting in a expected total of {length + offset} bytes.");
13 | }
14 | }
15 |
16 | public static void AssertTimeoutIsValid(TimeSpan value)
17 | {
18 | var totalMilliseconds = (long)value.TotalMilliseconds;
19 | if (totalMilliseconds is < -1 or > int.MaxValue)
20 | {
21 | ThrowTimeoutIsInvalid(value);
22 | }
23 | }
24 |
25 | private static void ThrowTimeoutIsInvalid(TimeSpan value) =>
26 | throw new ArgumentOutOfRangeException(nameof(value),
27 | $"The timeout {value.TotalMilliseconds}ms is less than -1 or greater than Int32.MaxValue.");
28 | }
--------------------------------------------------------------------------------
/src/AdsClient/Internal/EnumExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using Viscon.Communication.Ads.Common;
3 |
4 | namespace Viscon.Communication.Ads.Internal;
5 |
6 | internal static class EnumExtensions
7 | {
8 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
9 | public static uint ToUInt32(this AdsReservedIndexGroup indexGroup) =>
10 | Unsafe.As(ref indexGroup);
11 |
12 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
13 | public static AdsDataType ToDataType(this uint value) => Unsafe.As(ref value);
14 |
15 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
16 | public static AdsDataTypeFlags ToDataTypeFlags(this uint value) => Unsafe.As(ref value);
17 | }
--------------------------------------------------------------------------------
/src/AdsClient/Internal/LittleEndianDeserializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 | using System.Buffers.Binary;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace Viscon.Communication.Ads.Internal;
7 |
8 | ///
9 | /// Serializer to read little endian values from the input stream.
10 | ///
11 | ///
12 | /// Heavily inspired by the NetworkOrderDeserializer from RabbitMQ.Client
13 | /// (https://github.com/rabbitmq/rabbitmq-dotnet-client).
14 | ///
15 | internal static class LittleEndianDeserializer
16 | {
17 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
18 | public static ushort ReadUInt16(ReadOnlySpan span)
19 | {
20 | return BinaryPrimitives.ReadUInt16LittleEndian(span);
21 | }
22 |
23 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
24 | public static ushort ReadUInt16(ReadOnlySequence buffer)
25 | {
26 | if (!BinaryPrimitives.TryReadUInt16LittleEndian(buffer.First.Span, out var value))
27 | {
28 | Span bytes = stackalloc byte[4];
29 | buffer.Slice(0, 4).CopyTo(bytes);
30 | value = BinaryPrimitives.ReadUInt16LittleEndian(bytes);
31 | }
32 |
33 | return value;
34 | }
35 |
36 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
37 | public static int ReadInt32(ReadOnlySpan span)
38 | {
39 | return BinaryPrimitives.ReadInt32LittleEndian(span);
40 | }
41 |
42 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
43 | public static uint ReadUInt32(ReadOnlySpan span)
44 | {
45 | return BinaryPrimitives.ReadUInt32LittleEndian(span);
46 | }
47 |
48 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
49 | public static uint ReadUInt32(ReadOnlySequence buffer)
50 | {
51 | if (!BinaryPrimitives.TryReadUInt32LittleEndian(buffer.First.Span, out var value))
52 | {
53 | Span bytes = stackalloc byte[4];
54 | buffer.Slice(0, 4).CopyTo(bytes);
55 | value = BinaryPrimitives.ReadUInt32LittleEndian(bytes);
56 | }
57 |
58 | return value;
59 | }
60 | }
--------------------------------------------------------------------------------
/src/AdsClient/Internal/LittleEndianSerializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers.Binary;
3 | using System.Runtime.CompilerServices;
4 |
5 | namespace Viscon.Communication.Ads.Internal;
6 |
7 | ///
8 | /// Serializer to write little endian values to the output stream.
9 | ///
10 | ///
11 | /// Heavily inspired by the NetworkOrderSerializer from RabbitMQ.Client
12 | /// (https://github.com/rabbitmq/rabbitmq-dotnet-client).
13 | ///
14 | internal static class LittleEndianSerializer
15 | {
16 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
17 | public static void WriteUInt16(ref byte destination, ushort val)
18 | {
19 | Unsafe.WriteUnaligned(ref destination,
20 | BitConverter.IsLittleEndian ? val : BinaryPrimitives.ReverseEndianness(val));
21 | }
22 |
23 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
24 | public static void WriteUInt32(ref byte destination, uint val)
25 | {
26 | Unsafe.WriteUnaligned(ref destination,
27 | BitConverter.IsLittleEndian ? val : BinaryPrimitives.ReverseEndianness(val));
28 | }
29 | }
--------------------------------------------------------------------------------
/src/AdsClient/Internal/SocketAwaitable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Sockets;
3 | using System.Runtime.CompilerServices;
4 | using System.Threading;
5 |
6 | namespace Viscon.Communication.Ads.Internal;
7 |
8 | ///
9 | /// Reusable awaitable for socket operations.
10 | ///
11 | ///
12 | /// Based on https://devblogs.microsoft.com/pfxteam/awaiting-socket-operations/.
13 | ///
14 | internal sealed class SocketAwaitable : SocketAsyncEventArgs, INotifyCompletion
15 | {
16 | private static readonly Action Sentinel = () => { };
17 |
18 | public volatile bool WasCompleted;
19 | private Action? continuation;
20 |
21 | protected override void OnCompleted(SocketAsyncEventArgs _)
22 | {
23 | var prev = continuation ?? Interlocked.CompareExchange(ref continuation, Sentinel, null);
24 | prev?.Invoke();
25 | }
26 |
27 | internal void Reset()
28 | {
29 | WasCompleted = false;
30 | continuation = null;
31 | }
32 |
33 | public SocketAwaitable GetAwaiter()
34 | {
35 | return this;
36 | }
37 |
38 | public bool IsCompleted => WasCompleted;
39 |
40 | public void OnCompleted(Action continuation)
41 | {
42 | if (this.continuation == Sentinel ||
43 | Interlocked.CompareExchange(ref this.continuation, continuation, null) == Sentinel)
44 | {
45 | continuation.Invoke();
46 | }
47 | }
48 |
49 | public void GetResult()
50 | {
51 | if (SocketError != SocketError.Success)
52 | {
53 | throw new SocketException((int)SocketError);
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/src/AdsClient/Internal/SocketExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Sockets;
2 |
3 | namespace Viscon.Communication.Ads.Internal;
4 |
5 | ///
6 | /// Socket extensions to send and receive using .
7 | ///
8 | ///
9 | /// Based on https://devblogs.microsoft.com/pfxteam/awaiting-socket-operations/.
10 | ///
11 | internal static class SocketExtensions
12 | {
13 | public static SocketAwaitable ReceiveAwaitable(this Socket socket, SocketAwaitable awaitable)
14 | {
15 | awaitable.Reset();
16 | if (!socket.ReceiveAsync(awaitable))
17 | awaitable.WasCompleted = true;
18 | return awaitable;
19 | }
20 |
21 | public static SocketAwaitable SendAwaitable(this Socket socket, SocketAwaitable awaitable)
22 | {
23 | awaitable.Reset();
24 | if (!socket.SendAsync(awaitable))
25 | awaitable.WasCompleted = true;
26 | return awaitable;
27 | }
28 | }
--------------------------------------------------------------------------------
/src/AdsClient/Internal/TypeExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace Viscon.Communication.Ads.Internal;
6 |
7 | ///
8 | /// Serializer to write little endian values to the output stream.
9 | ///
10 | ///
11 | /// Heavily inspired by the TypeExtensions from RabbitMQ.Client
12 | /// (https://github.com/rabbitmq/rabbitmq-dotnet-client).
13 | ///
14 | internal static class TypeExtensions
15 | {
16 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
17 | public static ref byte GetStart(this Span span)
18 | {
19 | return ref MemoryMarshal.GetReference(span);
20 | }
21 |
22 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
23 | public static ref byte GetOffset(this Span span, int offset)
24 | {
25 | return ref span.GetStart().GetOffset(offset);
26 | }
27 |
28 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
29 | public static ref byte GetOffset(this ref byte source, int offset)
30 | {
31 | // The cast to uint is in order to avoid a sign-extending move
32 | // in the machine code.
33 | return ref Unsafe.Add(ref source, (uint)offset);
34 | }
35 | }
--------------------------------------------------------------------------------
/src/AdsClient/Internal/WireFormatting.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Runtime.CompilerServices;
4 | using Viscon.Communication.Ads.Helpers;
5 |
6 | namespace Viscon.Communication.Ads.Internal;
7 |
8 | ///
9 | /// Helpers for reading and writing data in the expect wire format for the ADS protocol.
10 | ///
11 | public static class WireFormatting
12 | {
13 | public static int ReadInt32(ReadOnlySpan span, out int value)
14 | {
15 | value = LittleEndianDeserializer.ReadInt32(span);
16 | return sizeof(int);
17 | }
18 |
19 | public static int ReadString(ReadOnlySpan span, ushort length, out string value)
20 | {
21 | value = StringHelper.Decode(span.Slice(0, length));
22 | return length + 1;
23 | }
24 |
25 | public static int ReadUInt16(ReadOnlySpan span, out ushort value)
26 | {
27 | value = LittleEndianDeserializer.ReadUInt16(span);
28 | return sizeof(ushort);
29 | }
30 |
31 | public static int ReadUInt32(ReadOnlySpan span, out uint value)
32 | {
33 | value = LittleEndianDeserializer.ReadUInt32(span);
34 | return sizeof(uint);
35 | }
36 |
37 | public static int WriteString(Span destination, string value)
38 | {
39 | return StringHelper.Encode(value.AsSpan(), destination);
40 | }
41 |
42 | public static int WriteUInt16(ref byte destination, ushort value)
43 | {
44 | LittleEndianSerializer.WriteUInt16(ref destination, value);
45 | return sizeof(ushort);
46 | }
47 |
48 | public static int WriteUInt16(ref byte destination, T value)
49 | {
50 | Debug.Assert(Unsafe.SizeOf() == sizeof(ushort),
51 | $"Passed type {typeof(T)} of size {Unsafe.SizeOf()} to method {nameof(WriteUInt32)}, expected size of {sizeof(ushort)}");
52 |
53 | return WriteUInt16(ref destination, Unsafe.As(ref value));
54 | }
55 |
56 | public static int WriteUInt32(ref byte destination, uint value)
57 | {
58 | LittleEndianSerializer.WriteUInt32(ref destination, value);
59 | return sizeof(uint);
60 | }
61 |
62 | public static int WriteUInt32(ref byte destination, T value) where T : struct
63 | {
64 | Debug.Assert(Unsafe.SizeOf() == sizeof(uint), $"Passed type {typeof(T)} of size {Unsafe.SizeOf()} to method {nameof(WriteUInt32)}, expected size of {sizeof(uint)}");
65 |
66 | return WriteUInt32(ref destination, Unsafe.As(ref value));
67 | }
68 | }
--------------------------------------------------------------------------------
/src/AdsClient/Special/AdsSpecial.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Viscon.Communication.Ads.Commands;
6 | using Viscon.Communication.Ads.Common;
7 | using Viscon.Communication.Ads.Helpers;
8 |
9 | namespace Viscon.Communication.Ads.Special
10 | {
11 | ///
12 | /// Special functions. (functionality not documented by Beckhoff)
13 | ///
14 | public class AdsSpecial
15 | {
16 | private Ams ams;
17 |
18 | internal AdsSpecial(Ams ams)
19 | {
20 | this.ams = ams;
21 | }
22 |
23 | ///
24 | /// Get an xml description of the plc
25 | /// You can use XDocument.Parse(xml).ToString() to make the xml more readable
26 | ///
27 | /// The token to monitor for cancellation requests.
28 | ///
29 | public async Task GetTargetDescAsync(CancellationToken cancellationToken = default)
30 | {
31 | AdsReadCommand adsCommand = new AdsReadCommand(0x000002bc, 0x00000001, 4) { AmsPortTargetOverride = 10000 };
32 | var result = await adsCommand.RunAsync(ams, cancellationToken).ConfigureAwait(false);
33 | uint length = BitConverter.ToUInt32(result.Data, 0);
34 | adsCommand = new AdsReadCommand(0x000002bc, 0x00000001, length) { AmsPortTargetOverride = 10000 };
35 | result = await adsCommand.RunAsync(ams, cancellationToken).ConfigureAwait(false);
36 | string xml = ByteArrayHelper.ByteArrayToString(result.Data);
37 | return xml;
38 | }
39 |
40 | ///
41 | /// Get the current routes
42 | ///
43 | /// The token to monitor for cancellation requests.
44 | ///
45 | public async Task> GetCurrentRoutesAsync(CancellationToken cancellationToken = default)
46 | {
47 | bool ok = true;
48 | uint index = 0;
49 | var routes = new List();
50 |
51 | while (ok)
52 | {
53 | try
54 | {
55 | AdsReadCommand adsCommand =
56 | new AdsReadCommand(0x00000323, index++, 0x0800) { AmsPortTargetOverride = 10000 };
57 | var result = await adsCommand.RunAsync(ams, cancellationToken).ConfigureAwait(false);
58 | int length = result.Data.Length - 44;
59 | byte[] routeBytes = new byte[length];
60 | Array.Copy(result.Data, 44, routeBytes, 0, length);
61 | string routeString = ByteArrayHelper.ByteArrayToString(routeBytes);
62 | int stringLlength = routeString.Length + 1;
63 | Array.Copy(routeBytes, stringLlength, routeBytes, 0, length - stringLlength);
64 | routeString += " " + ByteArrayHelper.ByteArrayToString(routeBytes);
65 | routes.Add(routeString);
66 | }
67 | catch (AdsException ex)
68 | {
69 | if ((uint) ex.ErrorCode == 1814) ok = false;
70 | else throw;
71 | }
72 | }
73 | return routes;
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/AdsClient/Variables/ArraySegmentVariableData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Variables;
4 |
5 | public record ArraySegmentVariableData(IVariableAddress Address, ArraySegment Data) : IVariableData
6 | {
7 | ReadOnlySpan IVariableData.Data => Data;
8 | }
--------------------------------------------------------------------------------
/src/AdsClient/Variables/ArrayVariableData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Variables;
4 |
5 | public record ArrayVariableData(IVariableAddress Address, byte[] Data) : IVariableData
6 | {
7 | ReadOnlySpan IVariableData.Data => Data;
8 | }
--------------------------------------------------------------------------------
/src/AdsClient/Variables/IVariableAddress.cs:
--------------------------------------------------------------------------------
1 | namespace Viscon.Communication.Ads.Variables;
2 |
3 | public interface IVariableAddress
4 | {
5 | uint IndexGroup { get; }
6 | uint IndexOffset { get; }
7 | }
--------------------------------------------------------------------------------
/src/AdsClient/Variables/IVariableAddressAndSize.cs:
--------------------------------------------------------------------------------
1 | namespace Viscon.Communication.Ads.Variables;
2 |
3 | public interface IVariableAddressAndSize : IVariableAddress
4 | {
5 | uint Size { get; }
6 | }
--------------------------------------------------------------------------------
/src/AdsClient/Variables/IVariableData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Variables;
4 |
5 | public interface IVariableData
6 | {
7 | IVariableAddress Address { get; }
8 | ReadOnlySpan Data { get; }
9 | }
--------------------------------------------------------------------------------
/src/AdsClient/Variables/MemoryVariableData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Viscon.Communication.Ads.Variables;
4 |
5 | public record MemoryVariableData(IVariableAddress Address, ReadOnlyMemory Data) : IVariableData
6 | {
7 | ReadOnlySpan IVariableData.Data => Data.Span;
8 | }
--------------------------------------------------------------------------------
/src/AdsClient/Variables/VariableAddress.cs:
--------------------------------------------------------------------------------
1 | namespace Viscon.Communication.Ads.Variables;
2 |
3 | public record VariableAddress(uint IndexGroup, uint IndexOffset) : IVariableAddress;
--------------------------------------------------------------------------------
/src/AdsClient/Variables/VariableAddressAndSize.cs:
--------------------------------------------------------------------------------
1 | namespace Viscon.Communication.Ads.Variables;
2 |
3 | public record VariableAddressAndSize(uint IndexGroup, uint IndexOffset, uint Size) : VariableAddress(IndexGroup, IndexOffset), IVariableAddressAndSize;
--------------------------------------------------------------------------------
/src/AdsClient/Viscon.Communication.Ads.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.1;netstandard2.0;net462
4 | latest
5 | portable
6 |
7 |
8 | Copyright (c) 2011 Inando
9 | Copyright (c) 2023 Viscon Factory Intelligence B.V.
10 |
11 | AdsClient PCL library
12 |
13 | A client implementation of the Beckhoff Twincat ADS protocol, targeting
14 | NetStandard2.0 compatible platforms.
15 |
16 | inando;mycroes
17 | README.md
18 | icon.png
19 | MIT
20 | Ads;AdsClient;Twincat;Beckhoff
21 | https://github.com/VisconFactoryIntelligence/AdsClient
22 | https://github.com/VisconFactoryIntelligence/AdsClient.git
23 |
24 | true
25 | true
26 | snupkg
27 | true
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | runtime; build; native; contentfiles; analyzers; buildtransitive
37 | all
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/AdsDumper/AdsDumper.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net7.0
6 | enable
7 | enable
8 | false
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/AdsDumper/Program.cs:
--------------------------------------------------------------------------------
1 | using Viscon.Communication.Ads;
2 |
3 | using var client = new AdsClient("172.31.20.201.1.1", "172.31.20.20", "172.31.20.20.1.1");
4 | try
5 | {
6 | await client.Ams.ConnectAsync();
7 | Console.WriteLine("Connected");
8 |
9 | Console.WriteLine();
10 | Console.WriteLine("Reading device info...");
11 | var deviceInfo = await client.ReadDeviceInfoAsync();
12 | Console.WriteLine(deviceInfo);
13 |
14 | Console.WriteLine();
15 | Console.WriteLine("Reading device state...");
16 | var deviceState = await client.ReadStateAsync();
17 | Console.WriteLine(deviceState);
18 |
19 | Console.WriteLine();
20 | Console.WriteLine("Reading symbols...");
21 | var symbols = await client.GetSymbolsAsync();
22 |
23 | Console.WriteLine($"Name\tTypeName (size)\tComment\tIndexGroup\tIndexOffset");
24 | foreach (var symbol in symbols)
25 | {
26 | Console.WriteLine(
27 | $"{symbol.Name}\t{symbol.TypeName} ({symbol.Size})\t{symbol.Comment}\t{symbol.IndexGroup}\t{symbol.IndexOffset}");
28 | }
29 |
30 | Console.WriteLine();
31 | Console.WriteLine("Reading types...");
32 | var types = await client.GetDataTypesAsync();
33 |
34 | Console.WriteLine("Name\tType (size)\tComment\tDataType\tVersion");
35 | foreach (var type in types)
36 | {
37 | Console.WriteLine($"{type.Name}\t{type.Type} ({type.Size})\t{type.Comment}\t{type.DataType}\t{type.Version}");
38 | foreach (var subItem in type.SubItems)
39 | {
40 | Console.WriteLine(
41 | $"- {subItem.Name}\t{subItem.Type} ({subItem.Size})\t{subItem.Comment}\t{subItem.DataType}\t{subItem.Version}");
42 | }
43 | }
44 | }
45 | catch (Exception e)
46 | {
47 | Console.WriteLine($"Unhandled exception occurred: {e}");
48 | Environment.Exit(1);
49 | }
50 |
--------------------------------------------------------------------------------
/test/AdsClient/AdsCommandsAsyncTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using FakeItEasy;
6 | using Shouldly;
7 | using Viscon.Communication.Ads.Common;
8 | using Viscon.Communication.Ads.Helpers;
9 | using Xunit;
10 |
11 | namespace Viscon.Communication.Ads.Test
12 | {
13 | public class AdsCommandsAsyncTest : IDisposable
14 | {
15 | private readonly IAmsSocket amsSocket = A.Fake();
16 | private readonly AdsClient client;
17 | private IIncomingMessageHandler messageHandler;
18 | private bool connected;
19 |
20 | public AdsCommandsAsyncTest()
21 | {
22 | A.CallTo(() => amsSocket.ConnectAsync(A.Ignored, A.Ignored))
23 | .ReturnsLazily(call =>
24 | {
25 | messageHandler = call.GetArgument(0);
26 | connected = true;
27 | return Task.CompletedTask;
28 | });
29 | A.CallTo(() => amsSocket.Connected).ReturnsLazily(() => connected);
30 |
31 | client = new AdsClient(amsNetIdSource: "10.0.0.120.1.1", amsSocket: amsSocket,
32 | amsNetIdTarget: "5.1.204.123.1.1");
33 |
34 | if (!client.Ams.ConnectAsync().IsCompletedSuccessfully)
35 | {
36 | throw new Exception("Connect call should have completed synchronously.");
37 | }
38 | }
39 |
40 | public void Dispose()
41 | {
42 | client.Dispose();
43 | }
44 |
45 | [Fact]
46 | public void ReadDeviceInfoAsync()
47 | {
48 | // Arrange
49 | var msgSend = new byte[]
50 | {
51 | 0, 0, 32, 0, 0, 0, 5, 1, 204, 123, 1, 1, 33, 3, 10, 0, 0, 120, 1, 1, 137, 128, 1, 0, 4, 0, 0, 0, 0,
52 | 0, 0, 0, 0, 0, 1, 0, 0, 0
53 | };
54 |
55 | var msgReceive = new byte[]
56 | {
57 | 0, 0, 56, 0, 0, 0, 10, 0, 0, 120, 1, 1, 137, 128, 5, 1, 204, 123, 1, 1, 33, 3, 1, 0, 5, 0, 24, 0, 0,
58 | 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 11, 234, 5, 84, 67, 97, 116, 80, 108, 99, 67, 116, 114,
59 | 108, 0, 0, 0, 0, 0
60 | };
61 |
62 | SetupRequestResponse(msgSend, msgReceive);
63 |
64 | // Act
65 | var task = client.ReadDeviceInfoAsync();
66 |
67 | // Assert
68 | task.IsCompleted.ShouldBeTrue("Call should have completed synchronously.");
69 | AdsDeviceInfo deviceInfo = task.Result;
70 | deviceInfo.ToString().ShouldBe("Version: 2.11.1514 Devicename: TCatPlcCtrl");
71 | }
72 |
73 | [Fact]
74 | public void ReadStateAsync()
75 | {
76 | // Arrange
77 | var msgSend = new byte[]
78 | {
79 | 0, 0, 32, 0, 0, 0, 5, 1, 204, 123, 1, 1, 33, 3, 10, 0, 0, 120, 1, 1, 137, 128, 4, 0, 4, 0, 0, 0, 0,
80 | 0, 0, 0, 0, 0, 1, 0, 0, 0
81 | };
82 |
83 | var msgReceive = new byte[]
84 | {
85 | 0, 0, 40, 0, 0, 0, 10, 0, 0, 120, 1, 1, 137, 128, 5, 1, 204, 123, 1, 1, 33, 3, 4, 0, 5, 0, 8, 0, 0,
86 | 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0
87 | };
88 |
89 | SetupRequestResponse(msgSend, msgReceive);
90 |
91 | // Act
92 | var task = client.ReadStateAsync();
93 |
94 | // Assert
95 | task.IsCompleted.ShouldBeTrue("Call should have completed synchronously.");
96 | var state = task.Result;
97 | state.ToString().ShouldBe("Ads state: 5 (Run) Device state: 0");
98 | }
99 |
100 | [Fact]
101 | public void GetSymhandleByNameAsync()
102 | {
103 | // Arrange
104 | var msgSend = new byte[]
105 | {
106 | 0, 0, 58, 0, 0, 0, 5, 1, 204, 123, 1, 1, 33, 3, 10, 0, 0, 120, 1, 1, 137, 128, 9, 0, 4, 0, 26, 0, 0,
107 | 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 240, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 10, 0, 0, 0, 46, 84, 69, 83, 84,
108 | 84, 73, 77, 69, 0
109 | };
110 |
111 | var msgReceive = new byte[]
112 | {
113 | 0, 0, 44, 0, 0, 0, 10, 0, 0, 120, 1, 1, 137, 128, 5, 1, 204, 123, 1, 1, 33, 3, 9, 0, 5, 0, 12, 0, 0,
114 | 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 141, 2, 0, 164
115 | };
116 |
117 | SetupRequestResponse(msgSend, msgReceive);
118 |
119 | // Act
120 | var task = client.GetSymhandleByNameAsync(".TESTTIME");
121 |
122 | // Assert
123 | task.IsCompleted.ShouldBeTrue("Call should have completed synchronously.");
124 | var handle = task.Result;
125 | handle.ShouldBe(2751464077);
126 | }
127 |
128 | private void SetupRequestResponse(byte[] tx, byte[] rx)
129 | {
130 | const int invocationIdOffset = 34;
131 |
132 | var isMatch = (byte[] sendData) =>
133 | {
134 | var beforeInvocationId = new Range(Index.Start, invocationIdOffset);
135 | var afterInvocationId = new Range(invocationIdOffset + sizeof(uint), Index.End);
136 |
137 | return tx[beforeInvocationId].SequenceEqual(sendData[beforeInvocationId]) &&
138 | tx[afterInvocationId].SequenceEqual(sendData[afterInvocationId]);
139 | };
140 |
141 | A.CallTo(() => amsSocket.SendAsync(A.That.Matches(buffer => isMatch(buffer))))
142 | .ReturnsLazily(call =>
143 | {
144 | // The receive header is consumed by the AmsSocket, so strip it from the data passed to Ams
145 | var res = rx[AmsHeaderHelper.AmsTcpHeaderSize..];
146 |
147 | // Copy the invocation ID from request to response
148 | var buffer = call.GetArgument(0);
149 | buffer.AsSpan().Slice(invocationIdOffset, sizeof(uint))
150 | .CopyTo(res.AsSpan()[(invocationIdOffset - AmsHeaderHelper.AmsTcpHeaderSize)..]);
151 |
152 | messageHandler.HandleMessage(res);
153 |
154 | return Task.CompletedTask;
155 | });
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/test/AdsClient/Viscon.Communication.Ads.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6
4 | webding
5 | Inando
6 | latest
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------