├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── TwinCAT.JsonExtension.Tests ├── CalculatorTestData.cs ├── DebugAttribute.cs ├── DebugPlcTypeBufferContainer.cs ├── DebugSubItem.cs ├── DebugSymbol.cs ├── DebugType.cs ├── ReadJsonTests.cs ├── TestReadWriteOperations.cs ├── TwinCAT.JsonExtension.Tests.csproj ├── TypeConversion.cs ├── WriteArrayTest.cs └── WriteJsonTests.cs ├── TwinCAT.JsonExtension.sln ├── TwinCAT.JsonExtension ├── AdsClientExtensions.cs └── TwinCAT.JsonExtension.csproj └── doc └── images └── logo.jpg /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: fbarresi 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | 332 | run_tests_and_coverage.bat 333 | coverage/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Federico Barresi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # TwinCAT.JsonExtension 4 | TwinCAT variables to and from json 5 | 6 | [![Build status](https://ci.appveyor.com/api/projects/status/4ggo35buwmno05u2/branch/master?svg=true)](https://ci.appveyor.com/project/fbarresi/twincat-jsonextension/branch/master) 7 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/6286aa6bb6f2402fa4f7553d749a5a8a)](https://www.codacy.com/manual/fbarresi/TwinCAT.JsonExtension?utm_source=github.com&utm_medium=referral&utm_content=fbarresi/TwinCAT.JsonExtension&utm_campaign=Badge_Grade) 8 | [![codecov](https://codecov.io/gh/fbarresi/TwinCAT.JsonExtension/branch/master/graph/badge.svg)](https://codecov.io/gh/fbarresi/TwinCAT.JsonExtension) 9 | ![Licence](https://img.shields.io/github/license/fbarresi/twincat.jsonextension.svg) 10 | [![Nuget Version](https://img.shields.io/nuget/v/TwinCAT.JsonExtension.svg)](https://www.nuget.org/packages/TwinCAT.JsonExtension/) 11 | 12 | Bring the power of Json.Net to TwinCAT 13 | 14 | Tranform DUTs decorated with the _custom_ **Json-Attribute** like this: 15 | 16 | ```reStructuredText 17 | TYPE JsonDUT : 18 | STRUCT 19 | {attribute 'json' := 'message'} 20 | sMessage : STRING := 'test'; 21 | iResponse : INT; 22 | {attribute 'json' := 'status'} 23 | sStatus : STRING := 'success'; 24 | {attribute 'json' := 'numbers'} 25 | daNumbers : ARRAY[1..3] OF DINT := [1,2,3]; 26 | END_STRUCT 27 | END_TYPE 28 | ``` 29 | 30 | into this (and back) **recursively** and absolutely **type-independent**: 31 | 32 | ```javascript 33 | { 34 | "message": "test", 35 | "status" : "success", 36 | "numbers" : [1,2,3] 37 | } 38 | ``` 39 | 40 | only calling this two extension methods on your connected `AdsClient`: 41 | ```csharp 42 | var json = await client.ReadJson("GVL.JsonDutVariable") 43 | ``` 44 | 45 | ```csharp 46 | await client.WriteJson("GVL.JsonDutVariable", json); 47 | ``` 48 | 49 | ### Options 50 | #### Progress indication 51 | For lengthy operations, a progress indiciator can be used to give some feedback about the current progress. By passing a `Progress` object as parameter to `ReadJson` or `WriteJson` it is possible to count the total number of primitive types (INT, DINT, REAL, ...) that were read or written to the PLC, respectively. 52 | 53 | ```csharp 54 | int objects = 0; 55 | var progress = new Progress(); 56 | progress.ProgressChanged += (sender, args) => { objects++; Console.CursorLeft = 0; Console.Write(objects); }; 57 | 58 | Console.WriteLine("Primitives read from PLC"); 59 | await client.ReadJson("GVL.JsonDutVariable", progress: progress); 60 | 61 | Console.WriteLine("\nPrimitives written to PLC"); 62 | await client.WriteJson("GVL.JsonDutVariable", json, progress: progress); 63 | ``` 64 | 65 | #### Enumeration stringify 66 | Values of enumerations are by default started as integer values. However, sometimes it is beneficial to store said values as strings. This can be achieved by the `stringify` parameter. 67 | 68 | ```csharp 69 | await client.ReadJson("GVL.JsonDutVariable", force: true, stringifyEnums: true); 70 | ``` 71 | 72 | 73 | #### Read/Write without json attribute 74 | The attributes mentioned above are optional when using this library. The following example achieves a similar result. The only difference 75 | is the instance names in the generated json file. 76 | 77 | ```reStructuredText 78 | TYPE JsonDUT : 79 | STRUCT 80 | sMessage : STRING := 'test'; 81 | iResponse : INT; 82 | sStatus : STRING := 'success'; 83 | daNumbers : ARRAY[1..3] OF DINT := [1,2,3]; 84 | END_STRUCT 85 | END_TYPE 86 | ``` 87 | yields 88 | 89 | ```javascript 90 | { 91 | "sMessage": "test", 92 | "iResponse" : 0, 93 | "sStatus": "success", 94 | "daNumbers" : [1,2,3] 95 | } 96 | ``` 97 | 98 | by calling the ReadJsonAsync method on your connected `AdsClient` 99 | ```csharp 100 | var json = await client.ReadJson("GVL.JsonDutVariable", force: true); 101 | ``` 102 | 103 | Have fun using this simple package and don't forget to **star this project**! 104 | 105 | ## Referenced projects 106 | 107 | Would you like to see the power of **TwinCAT.JsonExtension** in action? 108 | 109 | Then checkout [BeckhoffHttpClient](https://github.com/fbarresi/BeckhoffHttpClient), an _unofficial_ TwinCAT function for HTTP requests 110 | 111 | or 112 | 113 | [TwincatAdsTool](https://github.com/fbarresi/TwincatAdsTool) your swiss knife for twincat development. 114 | 115 | ## Credits 116 | 117 | Special thanks to [JetBrains](https://www.jetbrains.com/?from=TwinCAT.JsonExtension) for supporting this open source project. 118 | 119 | 120 | -------------------------------------------------------------------------------- /TwinCAT.JsonExtension.Tests/CalculatorTestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace TwinCAT.JsonExtension.Tests 7 | { 8 | public class CalculatorTestData : IEnumerable 9 | { 10 | public IEnumerator GetEnumerator() 11 | { 12 | yield return new object[] { 10 }; 13 | yield return new object[] { null }; 14 | yield return new object[] { new object() }; 15 | yield return new object[] { new DateTime(2019, 11, 26, 14, 26, 18) }; 16 | yield return new object[] { new DateTime(2019, 10, 31, 22, 10, 12) }; 17 | yield return new object[] { DateTime.Now.Date }; 18 | yield return new object[] { DateTime.Now.Date - TimeSpan.FromDays(10) }; 19 | yield return new object[] { TimeSpan.FromMilliseconds(638787) }; 20 | yield return new object[] { TimeSpan.FromMilliseconds(538787) }; 21 | yield return new object[] { TimeSpan.FromMilliseconds(832092) }; 22 | yield return new object[] { TimeSpan.FromMilliseconds(732092) }; 23 | } 24 | 25 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 26 | } 27 | } -------------------------------------------------------------------------------- /TwinCAT.JsonExtension.Tests/DebugAttribute.cs: -------------------------------------------------------------------------------- 1 | using TwinCAT.TypeSystem; 2 | 3 | namespace TwinCAT.JsonExtension.Tests 4 | { 5 | public class DebugAttribute: ITypeAttribute 6 | { 7 | public DebugAttribute(string name, string value) 8 | { 9 | Name = name; 10 | Value = value; 11 | } 12 | 13 | public string Name { get; } 14 | public string Value { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /TwinCAT.JsonExtension.Tests/DebugPlcTypeBufferContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using TwinCAT.Ads; 3 | using TwinCAT.Ads.Internal; 4 | 5 | namespace TwinCAT.JsonExtension.Tests 6 | { 7 | public class DebugPlcTypeBufferContainer 8 | { 9 | public uint EntryLength { get; set; } 10 | public uint Version { get; set; } 11 | public uint HashValue { get; set; } 12 | public uint TypeHashValue { get; set; } 13 | public uint Size { get; set; } 14 | public uint Offset { get; set; } 15 | public AdsDataTypeId BaseTypeId { get; set; } 16 | public AdsDataTypeFlags Flags { get; set; } 17 | public ushort NameLen { get; set; } 18 | public ushort FieldTypeNameLen { get; set; } 19 | public ushort CommentLen { get; set; } 20 | public ushort ArrayDim { get; set; } 21 | public ushort SubItems { get; set; } 22 | public string Name { get; set; } 23 | public string FieldTypeName { get; set; } 24 | public string Comment { get; set; } 25 | public int LowerBound { get; set; } 26 | public int Elements { get; set; } 27 | } 28 | } -------------------------------------------------------------------------------- /TwinCAT.JsonExtension.Tests/DebugSubItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using TwinCAT.Ads; 4 | using TwinCAT.Ads.Internal; 5 | using TwinCAT.TypeSystem; 6 | 7 | namespace TwinCAT.JsonExtension.Tests 8 | { 9 | public class DebugSubItem : IAttributedInstance, IMember 10 | { 11 | public int Size { get; } 12 | public bool IsBitType { get; } 13 | public int BitSize { get; } 14 | public int ByteSize { get; } 15 | public bool IsByteAligned { get; } 16 | public IDataType? DataType { get; set; } 17 | public string TypeName { get; } 18 | public string InstanceName { get; set; } 19 | public string InstancePath { get; } 20 | public bool IsStatic { get; } 21 | public bool IsReference { get; } 22 | public bool IsPointer { get; } 23 | public string Comment { get; } 24 | public ITypeAttributeCollection Attributes { get; set; } 25 | public Encoding ValueEncoding { get; } 26 | public string Name { get; set; } 27 | public string SubItemName { get; set; } 28 | public IDataType ParentType { get; } 29 | public int Offset { get; } 30 | public int ByteOffset { get; } 31 | public int BitOffset { get; } 32 | } 33 | } -------------------------------------------------------------------------------- /TwinCAT.JsonExtension.Tests/DebugSymbol.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using TwinCAT.Ads; 5 | using TwinCAT.Ads.TypeSystem; 6 | using TwinCAT.TypeSystem; 7 | 8 | namespace TwinCAT.JsonExtension.Tests 9 | { 10 | internal class DebugSymbol : IAdsSymbol 11 | { 12 | public int Size { get; } 13 | public bool IsBitType { get; } 14 | public int BitSize { get; } 15 | public int ByteSize { get; } 16 | public bool IsByteAligned { get; } 17 | public IDataType? DataType { get; set; } 18 | public string TypeName { get; } 19 | public string InstanceName { get; } 20 | public string InstancePath => Name; 21 | public bool IsStatic { get; } 22 | public bool IsReference { get; } 23 | public bool IsPointer { get; } 24 | public string Comment { get; } 25 | public ITypeAttributeCollection Attributes { get; } 26 | public Encoding ValueEncoding { get; } 27 | public DataTypeCategory Category { get; } 28 | public ISymbol? Parent { get; } 29 | public ISymbolCollection SubSymbols { get; } 30 | public bool IsContainerType { get; } 31 | public bool IsPrimitiveType { get; } 32 | public bool IsPersistent { get; } 33 | public bool IsReadOnly { get; } 34 | public bool IsRecursive { get; } 35 | public uint IndexGroup { get; } 36 | public uint IndexOffset { get; } 37 | public bool IsVirtual { get; } 38 | public byte ContextMask { get; } 39 | public AmsAddress? ImageBaseAddress { get; } 40 | public AdsDataTypeId DataTypeId { get; } 41 | public string Name { get; set; } 42 | } 43 | } -------------------------------------------------------------------------------- /TwinCAT.JsonExtension.Tests/DebugType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using TwinCAT.Ads; 6 | using TwinCAT.Ads.Internal; 7 | using TwinCAT.Ads.TypeSystem; 8 | using TwinCAT.TypeSystem; 9 | 10 | namespace TwinCAT.JsonExtension.Tests 11 | { 12 | internal class DebugType : IDataType, IStructType, IBitSize, IManagedMappableType, IResolvableType, IBindable2, IBindable, IArrayType 13 | { 14 | public int Size { get; } 15 | public bool IsBitType { get; } 16 | public int BitSize { get; } 17 | public int ByteSize { get; } 18 | public bool IsByteAligned { get; } 19 | public int Id { get; } 20 | public DataTypeCategory Category { get; set; } 21 | public string Name { get; set; } 22 | public string Namespace { get; } 23 | public string FullName { get; } 24 | public bool IsPrimitive { get; } 25 | public bool IsContainer { get; } 26 | public bool IsPointer { get; } 27 | public bool IsReference { get; } 28 | public ITypeAttributeCollection Attributes { get; } 29 | public string Comment { get; } 30 | public Type ManagedType { get; set; } 31 | public IInterfaceType[] InterfaceImplementations { get; } 32 | public IMemberCollection Members { get; set; } 33 | public string BaseTypeName { get; } 34 | IDataType IInterfaceType.BaseType { get; } 35 | public IMemberCollection AllMembers { get; } 36 | public bool HasRpcMethods { get; } 37 | public string[] InterfaceImplementationNames { get; } 38 | public DebugType BaseType { get; set; } 39 | public IDataType ResolveType(DataTypeResolveStrategy type) 40 | { 41 | throw new NotImplementedException(); 42 | } 43 | 44 | public void Bind(IBinder binder) 45 | { 46 | throw new NotImplementedException(); 47 | } 48 | 49 | public bool IsBound { get; } 50 | public bool IsBindingResolved(bool recurse) 51 | { 52 | throw new NotImplementedException(); 53 | } 54 | 55 | public Task ResolveWithBinderAsync(bool recurse, IBinder binder, CancellationToken cancel) 56 | { 57 | throw new NotImplementedException(); 58 | } 59 | 60 | public bool ResolveWithBinder(bool recurse, IBinder binder) 61 | { 62 | throw new NotImplementedException(); 63 | } 64 | 65 | public IDimensionCollection Dimensions { get; set; } 66 | public IDataType? ElementType { get; set; } 67 | public string ElementTypeName { get; } 68 | public bool IsJagged { get; } 69 | public int JaggedLevel { get; } 70 | 71 | public static Type t = typeof(bool); 72 | 73 | public IRpcMethodCollection RpcMethods { get; } 74 | } 75 | } -------------------------------------------------------------------------------- /TwinCAT.JsonExtension.Tests/ReadJsonTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Moq; 6 | using Newtonsoft.Json.Linq; 7 | using Shouldly; 8 | using TwinCAT.Ads; 9 | using TwinCAT.Ads.Internal; 10 | using TwinCAT.Ads.TypeSystem; 11 | using TwinCAT.TypeSystem; 12 | using Xunit; 13 | 14 | namespace TwinCAT.JsonExtension.Tests 15 | { 16 | public class ReadJsonTests 17 | { 18 | [Fact] 19 | public async Task TestReadSimpleJson() 20 | { 21 | var tcAdsSymbol = new DebugSymbol(); 22 | tcAdsSymbol.DataType = new PrimitiveType("test", typeof(int)); 23 | var value = 1; 24 | var clientMock = TestReadWriteOperations.GetClientMock(tcAdsSymbol, value); 25 | var variableName = "test"; 26 | var expected = new JObject(); 27 | expected.Add(variableName, value); 28 | var json = await clientMock.Object.ReadJson(variableName); 29 | JToken.DeepEquals(json, expected).ShouldBeTrue(); 30 | } 31 | 32 | [Fact] 33 | public async Task TestReadArray() 34 | { 35 | var tcAdsSymbol = new DebugSymbol(); 36 | tcAdsSymbol.DataType = new ArrayType(new PrimitiveType("INT", typeof(int)), 37 | new DimensionCollection(3)); 38 | var value = new int[]{1,2,3}; 39 | var clientMock = TestReadWriteOperations.GetClientMock(tcAdsSymbol, value); 40 | var variableName = "test"; 41 | var expected = new JObject(); 42 | expected.Add(variableName, new JArray(value)); 43 | var json = await clientMock.Object.ReadJson(variableName); 44 | JToken.DeepEquals(json, expected).ShouldBeTrue(); 45 | } 46 | 47 | [Fact] 48 | public async Task TestReadMultidimensionalArray() 49 | { 50 | var tcAdsSymbol = new DebugSymbol(); 51 | var arrayType = new ArrayType(new PrimitiveType("INT", typeof(int)), 52 | new DimensionCollection(3)); 53 | var multipleArrayType = 54 | new ArrayType(arrayType, new DimensionCollection(3)); 55 | tcAdsSymbol.DataType = multipleArrayType; 56 | var value = new int[][]{new int[] {1,2,3}, new int[] {4,5,6}}; 57 | var clientMock = TestReadWriteOperations.GetClientMock(tcAdsSymbol, value); 58 | var variableName = "test"; 59 | var expected = new JObject(); 60 | expected.Add(variableName, new JArray(new JArray(value[0]), new JArray(value[1]))); 61 | var json = await clientMock.Object.ReadJson(variableName); 62 | JToken.DeepEquals(json, expected).ShouldBeTrue(); 63 | } 64 | 65 | [Fact] 66 | public async Task TestReadComplexType() 67 | { 68 | var innerVariableName = "c"; 69 | var variableName = "test"; 70 | var value = 1; 71 | 72 | //var structure = new StructType(new AdsDataTypeEntry(true, StringMarshaler.Default, new ReadOnlySpan(new byte[1]))); 73 | 74 | var complexSymbol = new DebugSymbol(); 75 | var members = new List(){new DebugSubItem() 76 | { 77 | InstanceName = innerVariableName, 78 | SubItemName = innerVariableName, 79 | Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List(){new DebugAttribute("json", innerVariableName)})) 80 | }}; 81 | complexSymbol.DataType = new DebugType(){ManagedType = null, Members = new MemberCollection(members), Category = DataTypeCategory.Struct}; 82 | 83 | var childSymbol = new DebugSymbol(); 84 | childSymbol.DataType = new DebugType() { ManagedType = typeof(int), Category = DataTypeCategory.Primitive}; 85 | 86 | var clientMock = new Mock(); 87 | 88 | clientMock.Setup(client => client.ReadValue(It.Is(s => s == childSymbol))) 89 | .Returns(1); 90 | 91 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.Equals(variableName)))) 92 | .Returns(complexSymbol); 93 | 94 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(variableName+"."+ innerVariableName)))) 95 | .Returns(childSymbol); 96 | 97 | var expected = new JObject(); 98 | expected.Add(innerVariableName, value); 99 | 100 | var json = await clientMock.Object.ReadJson(variableName); 101 | JToken.DeepEquals(json, expected).ShouldBeTrue(); 102 | } 103 | 104 | [Fact] 105 | public async Task TestReadVeryComplexType() 106 | { 107 | var secondInnerVariableName = "a"; 108 | var innerVariableName = "c"; 109 | var variableName = "test"; 110 | var value = 1; 111 | 112 | var complexSymbol = new DebugSymbol(); 113 | complexSymbol.DataType = new DebugType() 114 | { 115 | ManagedType = null, 116 | Members = new MemberCollection(new List(){new DebugSubItem() 117 | { 118 | InstanceName = innerVariableName, 119 | SubItemName = innerVariableName, 120 | Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List(){new DebugAttribute("json", innerVariableName)})) 121 | }}) 122 | }; 123 | 124 | var innerComplexSymbol = new DebugSymbol(); 125 | innerComplexSymbol.DataType = new DebugType() 126 | { 127 | ManagedType = null, 128 | Members = new MemberCollection(new List(){new DebugSubItem() 129 | { 130 | InstanceName = secondInnerVariableName, 131 | SubItemName = secondInnerVariableName, 132 | Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List(){new DebugAttribute("json", secondInnerVariableName) })) 133 | }}) 134 | }; 135 | 136 | var childSymbol = new DebugSymbol(); 137 | childSymbol.DataType = new DebugType() { ManagedType = typeof(int) }; 138 | 139 | var clientMock = new Mock(); 140 | 141 | clientMock.Setup(client => client.ReadValue(It.Is(s => s == childSymbol))) 142 | .Returns(1); 143 | 144 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.Equals(variableName)))) 145 | .Returns(complexSymbol); 146 | 147 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(variableName + "." + innerVariableName)))) 148 | .Returns(innerComplexSymbol); 149 | 150 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(variableName + "."+ innerVariableName+"."+secondInnerVariableName)))) 151 | .Returns(childSymbol); 152 | 153 | var expected = new JObject(); 154 | var child = new JObject(); 155 | child.Add(secondInnerVariableName, value); 156 | expected.Add(innerVariableName, child); 157 | 158 | var json = await clientMock.Object.ReadJson(variableName); 159 | JToken.DeepEquals(json, expected).ShouldBeTrue(); 160 | } 161 | 162 | [Fact] 163 | public async Task TestReadVeryComplexArray() 164 | { 165 | var arraySymbol = new DebugSymbol(); 166 | var elementCount = 3; 167 | arraySymbol.DataType = new DebugType() { Category = DataTypeCategory.Array, BaseType = new DebugType() { ManagedType = null }, Dimensions = new ReadOnlyDimensionCollection(new DimensionCollection(new List() { new Dimension(0, elementCount) })) }; 168 | 169 | var secondInnerVariableName = "a"; 170 | var innerVariableName = "c"; 171 | var variableName = "test"; 172 | var value = 1; 173 | 174 | var complexSymbol = new DebugSymbol(); 175 | complexSymbol.DataType = new DebugType() 176 | { 177 | ManagedType = null, 178 | Members = new MemberCollection(new List(){new DebugSubItem() 179 | { 180 | InstanceName = innerVariableName, 181 | SubItemName = innerVariableName, 182 | Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List(){new DebugAttribute("json", innerVariableName)})) 183 | }}) 184 | }; 185 | 186 | var innerComplexSymbol = new DebugSymbol(); 187 | innerComplexSymbol.DataType = new DebugType() 188 | { 189 | ManagedType = null, 190 | Members = new MemberCollection(new List(){new DebugSubItem() 191 | { 192 | InstanceName = secondInnerVariableName, 193 | SubItemName = secondInnerVariableName, 194 | Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List(){new DebugAttribute("json", secondInnerVariableName) })) 195 | }}) 196 | }; 197 | 198 | var childSymbol = new DebugSymbol(); 199 | childSymbol.DataType = new DebugType() { ManagedType = typeof(int) }; 200 | 201 | var clientMock = new Mock(); 202 | 203 | clientMock.Setup(client => client.ReadValue(It.Is(s => s == childSymbol))) 204 | .Returns(1); 205 | 206 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.Equals(variableName)))) 207 | .Returns(arraySymbol); 208 | 209 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(variableName+"[") && s.EndsWith("]")))) 210 | .Returns(complexSymbol); 211 | 212 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.EndsWith("]." + innerVariableName)))) 213 | .Returns(innerComplexSymbol); 214 | 215 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.EndsWith("]." + innerVariableName + "." + secondInnerVariableName)))) 216 | .Returns(childSymbol); 217 | 218 | var expected = new JObject(); 219 | var complex = new JObject(); 220 | var child = new JObject(); 221 | child.Add(secondInnerVariableName, value); 222 | complex.Add(innerVariableName, child); 223 | expected.Add(variableName, new JArray(Enumerable.Repeat(complex, elementCount))); 224 | 225 | var json = await clientMock.Object.ReadJson(variableName); 226 | JToken.DeepEquals(json, expected).ShouldBeTrue(); 227 | } 228 | 229 | [Fact] 230 | public async Task TestReadEnumStringified() 231 | { 232 | var tcAdsSymbol = new DebugSymbol(); 233 | var values = new EnumValueCollection(); 234 | values.AddValue("Apple", 4); 235 | values.AddValue("Banana", 10); 236 | tcAdsSymbol.DataType = new EnumType("TestEnum", new PrimitiveType("test", typeof(int)), values); 237 | var value = 10; 238 | var clientMock = TestReadWriteOperations.GetClientMock(tcAdsSymbol, value); 239 | var variableName = "test"; 240 | var expected = new JObject(); 241 | expected.Add(variableName, "Banana"); 242 | var json = await clientMock.Object.ReadJson(variableName, force: true, stringifyEnums: true); 243 | JToken.DeepEquals(json, expected).ShouldBeTrue(); 244 | } 245 | } 246 | } -------------------------------------------------------------------------------- /TwinCAT.JsonExtension.Tests/TestReadWriteOperations.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Moq; 4 | using Shouldly; 5 | using TwinCAT.Ads; 6 | using TwinCAT.Ads.TypeSystem; 7 | using TwinCAT.TypeSystem; 8 | using Xunit; 9 | 10 | namespace TwinCAT.JsonExtension.Tests 11 | { 12 | public class TestReadWriteOperations 13 | { 14 | public static Mock GetClientMock(IAdsSymbol symbol, T value) 15 | { 16 | var clientMock = new Mock(); 17 | 18 | clientMock.Setup(client => client.ReadValue(It.IsAny())) 19 | .Returns(value); 20 | 21 | clientMock.Setup(client => client.ReadSymbol(It.IsAny())) 22 | .Returns(symbol); 23 | 24 | return clientMock; 25 | } 26 | 27 | 28 | [Fact] 29 | public async Task HasJsonNameForce() 30 | { 31 | var item = new DebugSubItem(); 32 | var jsonName = item.HasJsonName(true); 33 | jsonName.ShouldBe(true); 34 | } 35 | 36 | [Fact] 37 | public async Task HasEmptyJsonName() 38 | { 39 | var item = new DebugSubItem(){Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List()))}; 40 | var jsonName = item.HasJsonName(false); 41 | jsonName.ShouldBe(false); 42 | } 43 | 44 | [Fact] 45 | public async Task HasJsonName() 46 | { 47 | var item = new DebugSubItem() { Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List(){new DebugAttribute("JsOn", "json_name")})) }; 48 | var jsonName = item.HasJsonName(false); 49 | jsonName.ShouldBe(true); 50 | } 51 | 52 | [Fact] 53 | public async Task ReadJsonName() 54 | { 55 | var item = new DebugSubItem() { Name = "test", Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List() { new DebugAttribute("JsOn", "json_name") })) }; 56 | var jsonName = item.GetJsonName(); 57 | jsonName.ShouldBe("json_name"); 58 | } 59 | 60 | [Fact] 61 | public async Task ReadEmptyJsonName() 62 | { 63 | var item = new DebugSubItem() { InstanceName = "test", Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List())) }; 64 | var jsonName = item.GetJsonName(); 65 | jsonName.ShouldBe("test"); 66 | } 67 | 68 | [Fact] 69 | public async Task ReadSymbolWithDifferentType() 70 | { 71 | int value = 10; 72 | var symbol = new DebugSymbol(); 73 | var variableName = "test.object"; 74 | 75 | var clientMock = GetClientMock(symbol, value); 76 | 77 | var readVariable = await clientMock.Object.ReadAsync(variableName); 78 | 79 | clientMock.Verify(client => client.ReadSymbol(variableName), Times.Once); 80 | clientMock.Verify(client => client.ReadValue(symbol), Times.Once); 81 | readVariable.ShouldBe(value.ToString()); 82 | } 83 | 84 | [Fact] 85 | public async Task ReadSymbol() 86 | { 87 | int value = 11; 88 | var symbol = new DebugSymbol(); 89 | var variableName = "test.object2"; 90 | 91 | var clientMock = GetClientMock(symbol, value); 92 | 93 | var readVariable = await clientMock.Object.ReadAsync(variableName); 94 | 95 | clientMock.Verify(client => client.ReadSymbol(variableName), Times.Once); 96 | clientMock.Verify(client => client.ReadValue(symbol), Times.Once); 97 | readVariable.ShouldBe(value); 98 | } 99 | 100 | [Fact] 101 | public async Task WriteSymbol() 102 | { 103 | var symbol = new DebugSymbol(); 104 | var targetType = typeof(int); 105 | symbol.DataType = new DebugType { ManagedType = targetType }; 106 | int value = 12; 107 | var variableName = "test.object3"; 108 | 109 | var clientMock = GetClientMock(symbol, value); 110 | 111 | await clientMock.Object.WriteAsync(variableName, value); 112 | 113 | clientMock.Verify(client => client.ReadSymbol(variableName), Times.Once); 114 | clientMock.Verify(client => client.WriteValue(symbol, value as object), Times.Once); 115 | } 116 | 117 | [Fact] 118 | public async Task WriteSymbolWithDifferentType() 119 | { 120 | var symbol = new DebugSymbol(); 121 | var targetType = typeof(string); 122 | symbol.DataType = new DebugType { ManagedType = targetType }; 123 | var variableName = "test.object4"; 124 | int value = 14; 125 | 126 | var clientMock = GetClientMock(symbol, value); 127 | 128 | await clientMock.Object.WriteAsync(variableName, value); 129 | 130 | clientMock.Verify(client => client.ReadSymbol(variableName), Times.Once); 131 | clientMock.Verify(client => client.WriteValue(symbol, 132 | It.Is(o => o.ToString() == value.ToString())), Times.Once); 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /TwinCAT.JsonExtension.Tests/TwinCAT.JsonExtension.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net462 5 | 6 | false 7 | 8 | latest 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | all 25 | 26 | 27 | all 28 | runtime; build; native; contentfiles; analyzers; buildtransitive 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /TwinCAT.JsonExtension.Tests/TypeConversion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Shouldly; 3 | using TwinCAT.PlcOpen; 4 | using Xunit; 5 | 6 | namespace TwinCAT.JsonExtension.Tests 7 | { 8 | public class TypeConversion 9 | { 10 | [Theory] 11 | [ClassData(typeof(CalculatorTestData))] 12 | public void TestConvertToDotNetManagedType(object obj) 13 | { 14 | obj.TryConvertToDotNetManagedType().ShouldBe(obj); 15 | } 16 | 17 | [Fact] 18 | public void ConvertDT() 19 | { 20 | var expected = new DateTime(2019,11,26,14,26,18); 21 | var obj = PlcOpenDTConverter.Create(expected); 22 | obj.TryConvertToDotNetManagedType().ShouldBe(expected); 23 | } 24 | 25 | [Fact] 26 | public void ConvertDate() 27 | { 28 | var expected = DateTime.Now.Date; 29 | var obj = PlcOpenDateConverter.Create(expected); 30 | (obj.TryConvertToDotNetManagedType() is DateTime ? (DateTime) obj.TryConvertToDotNetManagedType() : default).Date.ShouldBe(expected); 31 | } 32 | 33 | [Fact] 34 | public void ConvertTime() 35 | { 36 | var expected = TimeSpan.FromMilliseconds(638787); 37 | var obj = PlcOpenTimeConverter.CreateTime(expected); 38 | obj.TryConvertToDotNetManagedType().ShouldBe(expected); 39 | } 40 | 41 | [Fact] 42 | public void ConvertLTime() 43 | { 44 | var expected = TimeSpan.FromMilliseconds(832092); 45 | var obj = PlcOpenTimeConverter.CreateLTime(expected); 46 | obj.TryConvertToDotNetManagedType().ShouldBe(expected); 47 | } 48 | 49 | [Fact] 50 | public void ConvertDT2() 51 | { 52 | var expected = new DateTime(2019, 11, 27, 14, 26, 18); 53 | var obj = PlcOpenDTConverter.Create(expected); 54 | obj.TryConvertToDotNetManagedType().ShouldBe(expected); 55 | } 56 | 57 | [Fact] 58 | public void ConvertDate2() 59 | { 60 | var expected = DateTime.Now.Date-TimeSpan.FromDays(1); 61 | var obj = PlcOpenDateConverter.Create(expected); 62 | (obj.TryConvertToDotNetManagedType() is DateTime ? (DateTime) obj.TryConvertToDotNetManagedType() : default).Date.ShouldBe(expected); 63 | } 64 | 65 | [Fact] 66 | public void ConvertTime2() 67 | { 68 | var expected = TimeSpan.FromMilliseconds(738787); 69 | var obj = PlcOpenTimeConverter.CreateTime(expected); 70 | obj.TryConvertToDotNetManagedType().ShouldBe(expected); 71 | } 72 | 73 | [Fact] 74 | public void ConvertLTime2() 75 | { 76 | var expected = TimeSpan.FromMilliseconds(932092); 77 | var obj = PlcOpenTimeConverter.CreateLTime(expected); 78 | obj.TryConvertToDotNetManagedType().ShouldBe(expected); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /TwinCAT.JsonExtension.Tests/WriteArrayTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Moq; 6 | using Newtonsoft.Json.Linq; 7 | using Shouldly; 8 | using TwinCAT.Ads; 9 | using TwinCAT.TypeSystem; 10 | using Xunit; 11 | 12 | namespace TwinCAT.JsonExtension.Tests 13 | { 14 | public class WriteArrayTest 15 | { 16 | [Fact] 17 | public async Task WriteJsonArray() 18 | { 19 | var originName = "o"; 20 | var value = 1; 21 | 22 | var arraySymbol = new DebugSymbol(); 23 | var elementCount = 3; 24 | arraySymbol.DataType = new DebugType() 25 | { 26 | Category = DataTypeCategory.Array, 27 | BaseType = new DebugType() {ManagedType = typeof(int)}, Dimensions = new ReadOnlyDimensionCollection(new DimensionCollection(new List() {new Dimension(0, elementCount)})), 28 | ElementType = new DebugType() {ManagedType = typeof(int)} 29 | }; 30 | 31 | 32 | var childSymbol = new DebugSymbol(); 33 | childSymbol.DataType = new DebugType() 34 | { 35 | ManagedType = typeof(int), 36 | Category = DataTypeCategory.Primitive 37 | }; 38 | 39 | var clientMock = new Mock(); 40 | 41 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(originName)))) 42 | .Returns(arraySymbol); 43 | 44 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(originName + "[") && s.EndsWith("]")))) 45 | .Returns(childSymbol); 46 | 47 | 48 | var array = new JArray(Enumerable.Repeat(value, elementCount)); 49 | 50 | await clientMock.Object.WriteJson(originName, array); 51 | 52 | clientMock.Verify(client => client.WriteValue(childSymbol, It.Is(v => int.Parse(v.ToString()) == value)), Times.Exactly(elementCount)); 53 | } 54 | 55 | [Fact] 56 | public async Task WriteWrongVariableForJsonArray() 57 | { 58 | var originName = "o"; 59 | var value = 1; 60 | 61 | var arraySymbol = new DebugSymbol(); 62 | var elementCount = 3; 63 | arraySymbol.DataType = new DebugType() {Category = DataTypeCategory.Array, BaseType = new DebugType() {ManagedType = typeof(int)}, Dimensions = new ReadOnlyDimensionCollection(new DimensionCollection(new List() {new Dimension(0, elementCount)}))}; 64 | 65 | 66 | var childSymbol = new DebugSymbol(); 67 | childSymbol.DataType = new DebugType() {ManagedType = typeof(int)}; 68 | 69 | var clientMock = new Mock(); 70 | 71 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(originName)))) 72 | .Returns(arraySymbol); 73 | 74 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(originName + "[") && s.EndsWith("]")))) 75 | .Returns(childSymbol); 76 | 77 | 78 | var array = new JArray(Enumerable.Repeat(value, elementCount)); 79 | 80 | await clientMock.Object.WriteJson(originName+"[0]", array).ShouldThrowAsync(typeof(InvalidOperationException)); 81 | 82 | clientMock.Verify(client => client.WriteValue(childSymbol, value), Times.Never); 83 | } 84 | 85 | [Fact] 86 | public async Task WriteComplexJsonArray() 87 | { 88 | var secondInnerVariableName = "a"; 89 | var innerVariableName = "c"; 90 | var variableName = "test"; 91 | var value = 1; 92 | 93 | 94 | var arraySymbol = new DebugSymbol(); 95 | var elementCount = 3; 96 | arraySymbol.DataType = new DebugType() 97 | { 98 | Category = DataTypeCategory.Array, 99 | BaseType = new DebugType() {ManagedType = null}, 100 | Dimensions = new ReadOnlyDimensionCollection(new DimensionCollection(new List() {new Dimension(0, elementCount)})), 101 | ElementType = new DebugType(){ Category = DataTypeCategory.Struct} 102 | }; 103 | 104 | 105 | var complexSymbol = new DebugSymbol() {Name = variableName}; 106 | complexSymbol.DataType = new DebugType() 107 | { 108 | ManagedType = null, 109 | Members = new MemberCollection(new List() 110 | { 111 | new DebugSubItem() 112 | { 113 | InstanceName = innerVariableName, 114 | SubItemName = innerVariableName, 115 | Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List() {new DebugAttribute("json", innerVariableName)})) 116 | } 117 | }) 118 | }; 119 | 120 | var innerComplexSymbol = new DebugSymbol() {Name = variableName + "." + innerVariableName}; 121 | innerComplexSymbol.DataType = new DebugType() 122 | { 123 | ManagedType = null, 124 | Members = new MemberCollection(new List() 125 | { 126 | new DebugSubItem() 127 | { 128 | InstanceName = secondInnerVariableName, 129 | SubItemName = secondInnerVariableName, 130 | Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List() {new DebugAttribute("json", secondInnerVariableName)})) 131 | } 132 | }) 133 | }; 134 | 135 | var childSymbol = new DebugSymbol() {Name = variableName + "." + innerVariableName + "." + secondInnerVariableName}; 136 | childSymbol.DataType = new DebugType() {ManagedType = typeof(int)}; 137 | 138 | var clientMock = new Mock(); 139 | 140 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(variableName)))) 141 | .Returns(arraySymbol); 142 | 143 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(variableName + "[") && s.EndsWith("]")))) 144 | .Returns(complexSymbol); 145 | 146 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.EndsWith("]." + innerVariableName)))) 147 | .Returns(innerComplexSymbol); 148 | 149 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.EndsWith("]." + innerVariableName + "." + secondInnerVariableName)))) 150 | .Callback((string s) => { childSymbol.Name = s; }) 151 | .Returns(childSymbol); 152 | 153 | 154 | var complex = new JObject(); 155 | var child = new JObject(); 156 | child.Add(secondInnerVariableName, value); 157 | complex.Add(innerVariableName, child); 158 | var array = new JArray(Enumerable.Repeat(complex, elementCount)); 159 | 160 | await clientMock.Object.WriteJson(variableName, array); 161 | 162 | clientMock.Verify(client => client.WriteValue(childSymbol, It.Is(v => int.Parse(v.ToString()) == value)), Times.Exactly(elementCount)); 163 | 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /TwinCAT.JsonExtension.Tests/WriteJsonTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Moq; 5 | using Newtonsoft.Json.Linq; 6 | using Shouldly; 7 | using TwinCAT.Ads; 8 | using TwinCAT.TypeSystem; 9 | using Xunit; 10 | 11 | namespace TwinCAT.JsonExtension.Tests 12 | { 13 | public class WriteJsonTests 14 | { 15 | [Fact] 16 | public async Task WriteJson() 17 | { 18 | var innerVariableName = "c"; 19 | var variableName = "test"; 20 | var value = 1; 21 | 22 | var complexSymbol = new DebugSymbol() {Name = variableName}; 23 | complexSymbol.DataType = new DebugType() 24 | { 25 | Name = variableName, 26 | ManagedType = null, 27 | Members = new MemberCollection(new List() 28 | { 29 | new DebugSubItem() 30 | { 31 | InstanceName = innerVariableName, 32 | SubItemName = innerVariableName, 33 | Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List() {new DebugAttribute("json", innerVariableName)})) 34 | } 35 | }) 36 | }; 37 | 38 | var childSymbol = new DebugSymbol() {Name = variableName + "." + innerVariableName}; 39 | childSymbol.DataType = new DebugType() {ManagedType = typeof(int), Name = innerVariableName}; 40 | 41 | var clientMock = new Mock(); 42 | 43 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.Equals(variableName)))) 44 | .Returns(complexSymbol); 45 | 46 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(variableName + "." + innerVariableName)))) 47 | .Returns(childSymbol); 48 | 49 | var json = new JObject(); 50 | json.Add(innerVariableName, value); 51 | await clientMock.Object.WriteJson(variableName, json); 52 | clientMock.Verify(client => client.WriteValue(childSymbol, It.Is(v => v.ToString() == value.ToString())), Times.Once); 53 | } 54 | 55 | [Fact] 56 | public async Task WriteComplexJsonArray() 57 | { 58 | var secondInnerVariableName = "a"; 59 | var innerVariableName = "c"; 60 | var variableName = "test"; 61 | var originName = "o"; 62 | var value = 1; 63 | 64 | var origin = new DebugSymbol(); 65 | origin.DataType = new DebugType() 66 | { 67 | ManagedType = null, 68 | Members = new MemberCollection(new List() 69 | { 70 | new DebugSubItem() 71 | { 72 | InstanceName = variableName, 73 | SubItemName = variableName, 74 | Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List() {new DebugAttribute("json", variableName)})) 75 | } 76 | }) 77 | }; 78 | var arraySymbol = new DebugSymbol(); 79 | var elementCount = 3; 80 | arraySymbol.DataType = new DebugType() 81 | { 82 | Category = DataTypeCategory.Array, 83 | BaseType = new DebugType() {ManagedType = null}, 84 | Dimensions = new ReadOnlyDimensionCollection(new DimensionCollection(new List() {new Dimension(0, elementCount)})), 85 | ElementType = new DebugType(){Category = DataTypeCategory.Struct} 86 | }; 87 | 88 | 89 | var complexSymbol = new DebugSymbol() {Name = variableName}; 90 | complexSymbol.DataType = new DebugType() 91 | { 92 | ManagedType = null, 93 | Members = new MemberCollection(new List() 94 | { 95 | new DebugSubItem() 96 | { 97 | InstanceName = innerVariableName, 98 | SubItemName = innerVariableName, 99 | Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List() {new DebugAttribute("json", innerVariableName)})) 100 | } 101 | }) 102 | }; 103 | 104 | var innerComplexSymbol = new DebugSymbol() {Name = variableName + "." + innerVariableName}; 105 | innerComplexSymbol.DataType = new DebugType() 106 | { 107 | ManagedType = null, 108 | Members = new MemberCollection(new List() 109 | { 110 | new DebugSubItem() 111 | { 112 | InstanceName = secondInnerVariableName, 113 | SubItemName = secondInnerVariableName, 114 | Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List() {new DebugAttribute("json", secondInnerVariableName)})) 115 | } 116 | }) 117 | }; 118 | 119 | var childSymbol = new DebugSymbol() {Name = variableName + "." + innerVariableName + "." + secondInnerVariableName}; 120 | childSymbol.DataType = new DebugType() {ManagedType = typeof(int)}; 121 | 122 | var clientMock = new Mock(); 123 | 124 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.Equals(originName)))) 125 | .Returns(origin); 126 | 127 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(originName + "." + variableName)))) 128 | .Returns(arraySymbol); 129 | 130 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(originName + "." + variableName + "[") && s.EndsWith("]")))) 131 | .Returns(complexSymbol); 132 | 133 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.EndsWith("]." + innerVariableName)))) 134 | .Returns(innerComplexSymbol); 135 | 136 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.EndsWith("]." + innerVariableName + "." + secondInnerVariableName)))) 137 | .Callback((string s) => { childSymbol.Name = s; }) 138 | .Returns(childSymbol); 139 | 140 | var json = new JObject(); 141 | var complex = new JObject(); 142 | var child = new JObject(); 143 | child.Add(secondInnerVariableName, value); 144 | complex.Add(innerVariableName, child); 145 | json.Add(variableName, new JArray(Enumerable.Repeat(complex, elementCount))); 146 | 147 | await clientMock.Object.WriteJson(originName, json); 148 | 149 | clientMock.Verify(client => client.WriteValue(childSymbol, It.Is(v => v.ToString() == value.ToString())), Times.Exactly(elementCount)); 150 | } 151 | 152 | [Fact] 153 | public async Task WriteJsonArray() 154 | { 155 | var variableName = "test"; 156 | var originName = "o"; 157 | var value = 1; 158 | 159 | var origin = new DebugSymbol(); 160 | origin.DataType = new DebugType() 161 | { 162 | ManagedType = null, 163 | Members = new MemberCollection(new List() 164 | { 165 | new DebugSubItem() 166 | { 167 | InstanceName = variableName, 168 | SubItemName = variableName, 169 | Attributes = new ReadOnlyTypeAttributeCollection(new TypeAttributeCollection(new List() {new DebugAttribute("json", variableName)})) 170 | } 171 | }) 172 | }; 173 | var arraySymbol = new DebugSymbol(); 174 | var elementCount = 3; 175 | arraySymbol.DataType = new DebugType() 176 | { 177 | Category = DataTypeCategory.Array, 178 | BaseType = new DebugType() {ManagedType = typeof(int)}, Dimensions = new ReadOnlyDimensionCollection(new DimensionCollection(new List() {new Dimension(0, elementCount)})), 179 | ElementType = new DebugType() {ManagedType = typeof(int)} 180 | }; 181 | 182 | 183 | var childSymbol = new DebugSymbol(); 184 | childSymbol.DataType = new DebugType() {ManagedType = typeof(int)}; 185 | 186 | var clientMock = new Mock(); 187 | 188 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.Equals(originName)))) 189 | .Returns(origin); 190 | 191 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(originName + "." + variableName)))) 192 | .Returns(arraySymbol); 193 | 194 | clientMock.Setup(client => client.ReadSymbol(It.Is(s => s.StartsWith(originName + "." + variableName + "[") && s.EndsWith("]")))) 195 | .Returns(childSymbol); 196 | 197 | 198 | var json = new JObject(); 199 | json.Add(variableName, new JArray(Enumerable.Repeat(value, elementCount))); 200 | 201 | await clientMock.Object.WriteJson(originName, json); 202 | 203 | clientMock.Verify(client => client.WriteValue(childSymbol, It.Is(v => v.ToString() == value.ToString())), Times.Exactly(elementCount)); 204 | } 205 | 206 | 207 | } 208 | 209 | 210 | } -------------------------------------------------------------------------------- /TwinCAT.JsonExtension.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.352 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TwinCAT.JsonExtension", "TwinCAT.JsonExtension\TwinCAT.JsonExtension.csproj", "{781232EA-EB55-49AE-BFA2-1926F590873D}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwinCAT.JsonExtension.Tests", "TwinCAT.JsonExtension.Tests\TwinCAT.JsonExtension.Tests.csproj", "{20ADDB82-8C7B-446C-B177-A05999290DF7}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {781232EA-EB55-49AE-BFA2-1926F590873D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {781232EA-EB55-49AE-BFA2-1926F590873D}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {781232EA-EB55-49AE-BFA2-1926F590873D}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {781232EA-EB55-49AE-BFA2-1926F590873D}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {20ADDB82-8C7B-446C-B177-A05999290DF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {20ADDB82-8C7B-446C-B177-A05999290DF7}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {20ADDB82-8C7B-446C-B177-A05999290DF7}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {20ADDB82-8C7B-446C-B177-A05999290DF7}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {D7CF3BB3-3B60-4C8D-B0F9-655F7A5346C2} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /TwinCAT.JsonExtension/AdsClientExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Newtonsoft.Json.Linq; 6 | using TwinCAT.Ads; 7 | using TwinCAT.Ads.TypeSystem; 8 | using TwinCAT.PlcOpen; 9 | using TwinCAT.TypeSystem; 10 | 11 | namespace TwinCAT.JsonExtension 12 | { 13 | public static class AdsClientExtensions 14 | { 15 | public static Task WriteAsync(this IAdsSymbolicAccess client, string variablePath, T value) 16 | { 17 | return WriteAsync(client, variablePath, value, CancellationToken.None, null); 18 | } 19 | public static Task WriteAsync(this IAdsSymbolicAccess client, string variablePath, T value, IProgress progress) 20 | { 21 | return WriteAsync(client, variablePath, value, CancellationToken.None, progress); 22 | } 23 | public static Task WriteAsync(this IAdsSymbolicAccess client, string variablePath, T value, CancellationToken token) 24 | { 25 | return WriteAsync(client, variablePath, value, token, null); 26 | } 27 | public static Task WriteAsync(this IAdsSymbolicAccess client, string variablePath, T value, CancellationToken token, IProgress progress) 28 | { 29 | return Task.Run(() => 30 | { 31 | var symbolInfo = client.ReadSymbol(variablePath); 32 | var targetType = (symbolInfo.DataType as IManagedMappableType)?.ManagedType; 33 | object targetValue; 34 | 35 | if (symbolInfo.DataType.Category == DataTypeCategory.Enum && value is JToken j && j.Type == JTokenType.String) 36 | { 37 | var enumType = symbolInfo.DataType as IEnumType; 38 | targetValue = Convert.ChangeType(enumType.Parse(value.ToString()), targetType); 39 | } 40 | else if (targetType.Namespace == "TwinCAT.PlcOpen") 41 | { 42 | targetValue = value.TryConvertToPlcOpenType(targetType); 43 | } 44 | else 45 | { 46 | targetValue = targetType != null 47 | ? (value is JToken jToken) ? jToken.ToObject(targetType) : Convert.ChangeType(value, targetType) 48 | : value; 49 | } 50 | client.WriteValue(symbolInfo, targetValue); 51 | 52 | progress?.Report(1); 53 | }, token); 54 | } 55 | public static Task ReadAsync(this IAdsSymbolicAccess client, string variablePath) 56 | { 57 | return ReadAsync(client, variablePath, CancellationToken.None); 58 | } 59 | public static Task ReadAsync(this IAdsSymbolicAccess client, string variablePath, CancellationToken token) 60 | { 61 | return Task.Run(() => 62 | { 63 | var symbolInfo = client.ReadSymbol(variablePath); 64 | var obj = client.ReadValue(symbolInfo); 65 | return (T) Convert.ChangeType(obj, typeof(T)); 66 | }, token); 67 | } 68 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JArray array) 69 | { 70 | return WriteJson(client, variablePath, array, false, CancellationToken.None, null); 71 | } 72 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JArray array, CancellationToken token) 73 | { 74 | return WriteJson(client, variablePath, array, false, token, null); 75 | } 76 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JArray array, CancellationToken token, IProgress progress) 77 | { 78 | return WriteJson(client, variablePath, array, false, token, progress); 79 | } 80 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JArray array, IProgress progress) 81 | { 82 | return WriteJson(client, variablePath, array, false, CancellationToken.None, progress); 83 | } 84 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JArray array, bool force) 85 | { 86 | return WriteArray(client, variablePath, array, force, CancellationToken.None, null); 87 | } 88 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JArray array, bool force, CancellationToken token) 89 | { 90 | return WriteArray(client, variablePath, array, force, token, null); 91 | } 92 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JArray array, bool force, IProgress progress) 93 | { 94 | return WriteArray(client, variablePath, array, force, CancellationToken.None, progress); 95 | } 96 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JArray array, bool force, CancellationToken token, IProgress progress) 97 | { 98 | return WriteArray(client, variablePath, array, force, token, progress); 99 | } 100 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JObject obj) 101 | { 102 | return WriteJson(client, variablePath, obj, false, CancellationToken.None, null); 103 | } 104 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JObject obj, IProgress progress) 105 | { 106 | return WriteJson(client, variablePath, obj, false, CancellationToken.None, progress); 107 | } 108 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JObject obj, CancellationToken token) 109 | { 110 | return WriteJson(client, variablePath, obj, false, token, null); 111 | } 112 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JObject obj, CancellationToken token, IProgress progress) 113 | { 114 | return WriteJson(client, variablePath, obj, false, token, progress); 115 | } 116 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JObject obj, bool force) 117 | { 118 | return WriteRecursive(client, variablePath, obj, string.Empty, force, CancellationToken.None, null); 119 | } 120 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JObject obj, bool force, IProgress progress) 121 | { 122 | return WriteRecursive(client, variablePath, obj, string.Empty, force, CancellationToken.None, progress); 123 | } 124 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JObject obj, bool force, CancellationToken token) 125 | { 126 | return WriteRecursive(client, variablePath, obj, string.Empty, force, token, null); 127 | } 128 | public static Task WriteJson(this IAdsSymbolicAccess client, string variablePath, JObject obj, bool force, CancellationToken token, IProgress progress) 129 | { 130 | return WriteRecursive(client, variablePath, obj, string.Empty, force, token, progress); 131 | } 132 | 133 | private static async Task WriteArray(this IAdsSymbolicAccess client, string variablePath, JArray array, bool force, CancellationToken token, IProgress progress) 134 | { 135 | var symbolInfo = client.ReadSymbol(variablePath); 136 | var dataType = symbolInfo.DataType as IArrayType; 137 | 138 | if (dataType.Category != DataTypeCategory.Array) 139 | { 140 | throw new InvalidOperationException($"Type of plc variable {variablePath} must be an array"); 141 | } 142 | 143 | var elementCount = array.Count < dataType.Dimensions.ElementCount ? array.Count : dataType.Dimensions.ElementCount; 144 | for (int i = 0; i < elementCount; i++) 145 | { 146 | if ((dataType.ElementType as IManagedMappableType)?.ManagedType != null) 147 | { 148 | await WriteAsync(client, variablePath + $"[{i + dataType.Dimensions.LowerBounds.First()}]", array[i], token, progress).ConfigureAwait(false); 149 | } 150 | else if (dataType.ElementType?.Category == DataTypeCategory.Array) 151 | { 152 | await WriteArray(client, variablePath + $"[{i + dataType.Dimensions.LowerBounds.First()}]", array[i] as JArray, force, token, progress).ConfigureAwait(false); 153 | } 154 | else 155 | { 156 | await WriteRecursive(client, variablePath + $"[{i + dataType.Dimensions.LowerBounds.First()}]", array[i] as JObject, string.Empty, force, token, progress).ConfigureAwait(false); 157 | } 158 | } 159 | } 160 | 161 | private static async Task WriteRecursive(this IAdsSymbolicAccess client, string variablePath, JObject parent, string jsonName, bool force, CancellationToken token, IProgress progress) 162 | { 163 | var symbolInfo = client.ReadSymbol(variablePath); 164 | var dataType = symbolInfo.DataType; 165 | { 166 | if (dataType.Category == DataTypeCategory.Array) 167 | { 168 | var arrayType = symbolInfo.DataType as IArrayType; 169 | var array = parent.SelectToken(jsonName) as JArray; 170 | var elementCount = array.Count < arrayType.Dimensions.ElementCount ? array.Count : arrayType.Dimensions.ElementCount; 171 | for (int i = 0; i < elementCount; i++) 172 | { 173 | if ((arrayType.ElementType as IManagedMappableType)?.ManagedType != null) 174 | { 175 | await WriteAsync(client, variablePath + $"[{i + arrayType.Dimensions.LowerBounds.First()}]", array[i], token, progress).ConfigureAwait(false); 176 | } 177 | else 178 | { 179 | await WriteRecursive(client, variablePath + $"[{i + arrayType.Dimensions.LowerBounds.First()}]", parent, jsonName + $"[{i}]", force, token, progress).ConfigureAwait(false); 180 | } 181 | } 182 | } 183 | else if ((dataType as IManagedMappableType)?.ManagedType == null) 184 | { 185 | var structType = symbolInfo.DataType as IStructType; 186 | if ((bool) structType?.Members.Any()) 187 | { 188 | foreach (var subItem in structType.Members) 189 | { 190 | if (HasJsonName(subItem, force)) 191 | { 192 | await WriteRecursive(client, variablePath + "." + subItem.InstanceName, parent, string.IsNullOrEmpty(jsonName) ? GetJsonName(subItem) : jsonName + "." + GetJsonName(subItem), force, token, progress).ConfigureAwait(false); ; 193 | } 194 | } 195 | } 196 | } 197 | else 198 | { 199 | var value = parent.SelectToken(jsonName); 200 | if (value != null) 201 | { 202 | await WriteAsync(client, symbolInfo.InstancePath, value, token, progress).ConfigureAwait(false); 203 | } 204 | } 205 | } 206 | } 207 | 208 | public static Task ReadJson(this IAdsSymbolicAccess client, string variablePath) 209 | { 210 | return ReadJson(client, variablePath, false, false, CancellationToken.None, null); 211 | } 212 | public static Task ReadJson(this IAdsSymbolicAccess client, string variablePath, IProgress progress) 213 | { 214 | return ReadJson(client, variablePath, false, false, CancellationToken.None, progress); 215 | } 216 | public static Task ReadJson(this IAdsSymbolicAccess client, string variablePath, CancellationToken token) 217 | { 218 | return ReadJson(client, variablePath, false, false, token, null); 219 | } 220 | public static Task ReadJson(this IAdsSymbolicAccess client, string variablePath, CancellationToken token, IProgress progress) 221 | { 222 | return ReadJson(client, variablePath, false, false, token, progress); 223 | } 224 | public static Task ReadJson(this IAdsSymbolicAccess client, string variablePath, bool force) 225 | { 226 | return ReadJson(client, variablePath, force, false, CancellationToken.None, null); 227 | } 228 | public static Task ReadJson(this IAdsSymbolicAccess client, string variablePath, bool force, CancellationToken token) 229 | { 230 | return ReadJson(client, variablePath, force, false, token, null); 231 | } 232 | public static Task ReadJson(this IAdsSymbolicAccess client, string variablePath, bool force, CancellationToken token, IProgress progress) 233 | { 234 | return ReadJson(client, variablePath, force, false, token, progress); 235 | } 236 | public static Task ReadJson(this IAdsSymbolicAccess client, string variablePath, bool force, IProgress progress) 237 | { 238 | return ReadJson(client, variablePath, force, false, CancellationToken.None, progress); 239 | } 240 | public static Task ReadJson(this IAdsSymbolicAccess client, string variablePath, bool force, bool stringifyEnums) 241 | { 242 | return ReadJson(client, variablePath, force, stringifyEnums, CancellationToken.None, null); 243 | } 244 | public static Task ReadJson(this IAdsSymbolicAccess client, string variablePath, bool force, bool stringifyEnums, IProgress progress) 245 | { 246 | return ReadJson(client, variablePath, force, stringifyEnums, CancellationToken.None, progress); 247 | } 248 | public static Task ReadJson(this IAdsSymbolicAccess client, string variablePath, bool force, bool stringifyEnums, CancellationToken token) 249 | { 250 | return ReadJson(client, variablePath, force, stringifyEnums, token, null); 251 | } 252 | public static Task ReadJson(this IAdsSymbolicAccess client, string variablePath, bool force, bool stringifyEnums, CancellationToken token, IProgress progress) 253 | { 254 | return Task.Run(() => ReadRecursive(client, variablePath, new JObject(), GetVariableNameFromFullPath(variablePath), force:force, stringifyEnums:stringifyEnums, progress:progress), token); 255 | } 256 | 257 | private static JObject ReadRecursive(IAdsSymbolicAccess client, string variablePath, JObject parent, string jsonName, bool isChild = false, bool force = false, bool stringifyEnums = false, IProgress progress = null) 258 | { 259 | var symbolInfo = client.ReadSymbol(variablePath); 260 | var dataType = symbolInfo.DataType; 261 | { 262 | if (dataType.Category == DataTypeCategory.Array) 263 | { 264 | var arrayType = symbolInfo.DataType as IArrayType; 265 | if ((arrayType.ElementType as IManagedMappableType)?.ManagedType != null) 266 | { 267 | var obj = client.ReadValue(symbolInfo); 268 | parent.Add(jsonName, JArray.FromObject(obj)); 269 | } 270 | else 271 | { 272 | var array = new JArray(); 273 | for (int i = arrayType.Dimensions.LowerBounds.First(); i <= arrayType.Dimensions.UpperBounds.First(); i++) 274 | { 275 | var child = new JObject(); 276 | ReadRecursive(client, variablePath + $"[{i}]", child, jsonName, isChild:false, force:force, stringifyEnums:stringifyEnums, progress); 277 | if (child[jsonName] != null && dataType.Category == DataTypeCategory.Array) 278 | { 279 | array.Add(child[jsonName]); 280 | } 281 | else 282 | { 283 | array.Add(child); 284 | } 285 | } 286 | parent.Add(jsonName, array); 287 | } 288 | } 289 | else if ((dataType as IManagedMappableType)?.ManagedType == null) 290 | { 291 | var structType = symbolInfo.DataType as IStructType; 292 | if ((bool) structType?.Members.Any()) 293 | { 294 | var child = new JObject(); 295 | foreach (var subItem in structType?.Members) 296 | { 297 | if (HasJsonName(subItem, force)) 298 | { 299 | ReadRecursive(client, variablePath + "." + subItem.InstanceName, isChild ? child : parent, GetJsonName(subItem), isChild:true, force:force, stringifyEnums:stringifyEnums, progress); 300 | } 301 | } 302 | if (isChild) 303 | { 304 | parent.Add(jsonName, child); 305 | } 306 | } 307 | } 308 | else if(dataType.Category == DataTypeCategory.Enum) 309 | { 310 | var obj = client.ReadValue(symbolInfo); 311 | if (stringifyEnums) 312 | { 313 | var enumType = symbolInfo.DataType as IEnumType; 314 | parent.Add(jsonName, new JValue(enumType.ToString((IConvertible)obj))); 315 | } 316 | else 317 | { 318 | parent.Add(jsonName, new JValue(obj.TryConvertToDotNetManagedType())); 319 | } 320 | 321 | progress?.Report(1); 322 | } 323 | else 324 | { 325 | var obj = client.ReadValue(symbolInfo); 326 | parent.Add(jsonName, new JValue(obj.TryConvertToDotNetManagedType())); 327 | 328 | progress?.Report(1); 329 | } 330 | } 331 | 332 | return parent; 333 | } 334 | 335 | public static string GetVariableNameFromFullPath(this string variablePath) 336 | { 337 | return variablePath.Split(new[] {'.'}, StringSplitOptions.RemoveEmptyEntries).Last(); 338 | } 339 | 340 | public static string GetJsonName(this IAttributedInstance dataType) 341 | { 342 | var jsonName = dataType.Attributes.FirstOrDefault(attribute => attribute.Name.Equals("json", StringComparison.InvariantCultureIgnoreCase))?.Value; 343 | return string.IsNullOrEmpty(jsonName) ? GetVariableNameFromFullPath(dataType.InstanceName) : jsonName; 344 | } 345 | 346 | public static bool HasJsonName(this IAttributedInstance subItem, bool force) 347 | { 348 | if (force) 349 | { 350 | return true; 351 | } 352 | return subItem.Attributes.Any(attribute => attribute.Name.Equals("json", StringComparison.InvariantCultureIgnoreCase)); 353 | } 354 | 355 | /// 356 | /// try to convert TwinCAT ManagedType to .Net ManagedType (e.g. DT => DateTime, TIME => TimeSpan, etc.) 357 | /// If no conversion is possible return the unmodified object 358 | /// 359 | /// object to convert 360 | /// 361 | public static object TryConvertToDotNetManagedType(this object obj) 362 | { 363 | return obj.TryConvertDateTime().TryConvertTimeSpan(); 364 | } 365 | 366 | public static object TryConvertDateTime(this object obj) 367 | { 368 | bool conversion; 369 | object newObject; 370 | switch (obj) 371 | { 372 | case DT dt: 373 | { 374 | return dt.Value; 375 | } 376 | 377 | case DATE date: 378 | { 379 | conversion = PlcOpenDateConverter.TryConvert(date, typeof(DateTime), out newObject); 380 | if (conversion) 381 | { 382 | return newObject; 383 | } 384 | 385 | break; 386 | } 387 | } 388 | 389 | return obj; 390 | } 391 | public static object TryConvertTimeSpan(this object obj) 392 | { 393 | bool conversion; 394 | object newObject; 395 | switch (obj) 396 | { 397 | case TIME time: 398 | { 399 | conversion = PlcOpenTimeConverter.TryConvert(time, typeof(TimeSpan), out newObject); 400 | if (conversion) 401 | { 402 | return newObject; 403 | } 404 | 405 | break; 406 | } 407 | 408 | case LTIME ltime: 409 | { 410 | conversion = PlcOpenTimeConverter.TryConvert(ltime, typeof(TimeSpan), out newObject); 411 | if (conversion) 412 | { 413 | return newObject; 414 | } 415 | 416 | break; 417 | } 418 | } 419 | 420 | return obj; 421 | } 422 | 423 | public static object TryConvertToPlcOpenType(this object obj, Type targetType) 424 | { 425 | if (targetType == typeof(DT) || targetType == typeof(DATE)) 426 | { 427 | var dateTimeOffset = (DateTimeOffset) ((obj is JToken jToken) 428 | ? jToken.ToObject(typeof(DateTimeOffset)) 429 | : JToken.FromObject(obj).ToObject(typeof(DateTimeOffset))); 430 | return dateTimeOffset.TryConvertToPlcOpenType(targetType); 431 | } 432 | if (targetType == typeof(TIME) || targetType == typeof(LTIME)) 433 | { 434 | var timeSpan = (TimeSpan) ((obj is JToken jToken) 435 | ? jToken.ToObject(typeof(TimeSpan)) 436 | : JToken.FromObject(obj).ToObject(typeof(TimeSpan))); 437 | return timeSpan.TryConvertToPlcOpenType(targetType); 438 | } 439 | 440 | return obj; 441 | } 442 | 443 | public static object TryConvertToPlcOpenType(this DateTimeOffset dateTimeOffset, Type targetType) 444 | { 445 | if (targetType == typeof(DT)) 446 | { 447 | return new DT(dateTimeOffset); 448 | } 449 | 450 | if (targetType == typeof(DATE)) 451 | { 452 | return new DATE(dateTimeOffset); 453 | } 454 | 455 | return dateTimeOffset; 456 | } 457 | 458 | public static object TryConvertToPlcOpenType(this TimeSpan timeSpan, Type targetType) 459 | { 460 | if (targetType == typeof(TIME)) 461 | { 462 | return new TIME(timeSpan); 463 | } 464 | 465 | if (targetType == typeof(LTIME)) 466 | { 467 | return new LTIME(timeSpan); 468 | } 469 | 470 | return timeSpan; 471 | } 472 | } 473 | } 474 | -------------------------------------------------------------------------------- /TwinCAT.JsonExtension/TwinCAT.JsonExtension.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 6 | true 7 | snupkg 8 | Federico Barresi 9 | true 10 | https://github.com/fbarresi/TwinCAT.JsonExtension 11 | https://raw.githubusercontent.com/fbarresi/TwinCAT.JsonExtension/master/LICENSE 12 | TwinCAT TwinCAT.Ads Json Json.net Beckhoff 13 | 14 | 1.0.0 15 | https://raw.githubusercontent.com/fbarresi/TwinCAT.JsonExtension/master/doc/images/logo.jpg 16 | Convert TwinCAT variables to and from Json 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /doc/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbarresi/TwinCAT.JsonExtension/5cf097c0437749e8f7ac7863e617393ce8aa8168/doc/images/logo.jpg --------------------------------------------------------------------------------