├── .Zeugwerk └── config.json ├── .github └── workflows │ └── documentation.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── docfx.json ├── images │ └── TcLog_header.svg ├── index.md ├── metadata.json ├── toc.yml └── userguide │ ├── configuration.md │ ├── customization.md │ ├── getting_started.md │ ├── installation.md │ ├── license.md │ ├── logging.md │ ├── performance.md │ └── toc.yml ├── src ├── TcLog.sln ├── TcLogProj │ ├── TcLog.libcat.xml │ ├── TcLog │ │ ├── CONSTANTS.TcGVL │ │ ├── Logger │ │ │ ├── LogLevelToAdsLogMsgType.TcPOU │ │ │ ├── TcLog.TcPOU │ │ │ ├── TcLogCore.TcPOU │ │ │ ├── TcLogTrig.TcPOU │ │ │ ├── _DataTypes │ │ │ │ ├── Error.TcDUT │ │ │ │ ├── ErrorCodes.TcDUT │ │ │ │ ├── LogLevels.TcDUT │ │ │ │ ├── LoggingConfiguration.TcDUT │ │ │ │ └── RollingIntervals.TcDUT │ │ │ ├── _Interfaces │ │ │ │ ├── ILog.TcIO │ │ │ │ └── ILogCore.TcIO │ │ │ └── _Tests │ │ │ │ └── TestWrapper_NET.TcPOU │ │ ├── StringBuilder │ │ │ ├── StringBuilder.TcPOU │ │ │ └── _Interfaces │ │ │ │ ├── ILimitedStringBuilder.TcIO │ │ │ │ └── IStringBuilder.TcIO │ │ ├── TcLog.plcproj │ │ ├── TcLog.plcproj.bak │ │ └── Utils │ │ │ ├── AnyToString.TcPOU │ │ │ ├── DateTime.TcPOU │ │ │ ├── DeleteOldFiles.TcPOU │ │ │ ├── DynamicStringBuffer.TcPOU │ │ │ ├── GenerateTimeData.TcPOU.bak │ │ │ ├── GetFileAgeInSeconds.TcPOU │ │ │ ├── _DataTypes │ │ │ ├── DeleteFilesState.TcDUT │ │ │ └── PersistToFileState.TcDUT │ │ │ └── _Tests │ │ │ ├── ConvertAnyToString_TEST.TcPOU │ │ │ ├── DateTime_TEST.TcPOU │ │ │ └── DynamicStringBuffer_TEST.TcPOU │ ├── TcLogProj.tsproj │ └── TcLogTEST │ │ ├── PlcTask.TcTTO │ │ ├── TESTS.TcPOU │ │ └── TcLogTEST.plcproj └── TcLogTest.NET │ ├── PersistingToFileSystem.cs │ ├── PlcFixture.cs │ └── TcLogTest.NET.csproj └── tools └── release_library.ps1 /.Zeugwerk/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileversion": 1, 3 | "solution": "src/TcLog.sln", 4 | "projects": [ 5 | { 6 | "name": "TcLogProj", 7 | "plcs": [ 8 | { 9 | "version": "0.2.2", 10 | "name": "TcLog", 11 | "type": "Library", 12 | "frameworks": {}, 13 | "references": { 14 | "*": [ 15 | "Tc2_Standard=*", 16 | "Tc2_System=*", 17 | "Tc2_Utilities=*", 18 | "Tc3_DynamicMemory=*", 19 | "Tc3_Modules=*", 20 | "Base Interfaces=newest" 21 | ] 22 | }, 23 | "repositories": [], 24 | "bindings": {}, 25 | "patches": { 26 | "platform": {}, 27 | "argument": {} 28 | } 29 | } 30 | ] 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - 'docs/**' 8 | - 'src/**' 9 | pull_request: 10 | workflow_dispatch: 11 | jobs: 12 | Build: 13 | name: Documentation 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Build 17 | uses: Zeugwerk/zkdoc-action@1.0.0 18 | with: 19 | username: ${{ secrets.ACTIONS_ZGWK_USERNAME }} 20 | password: ${{ secrets.ACTIONS_ZGWK_PASSWORD }} 21 | filepath: "." 22 | doc-folder: "docs" 23 | - name: Deploy 24 | uses: peaceiris/actions-gh-pages@v3 25 | with: 26 | deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} 27 | publish_dir: archive/docs/html 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gitignore template for TwinCAT3 2 | # website: https://www.beckhoff.com/twincat3/ 3 | # 4 | # Recommended: VisualStudio.gitignore 5 | 6 | # TwinCAT files 7 | *.tpy 8 | *.tclrs 9 | *.compiled-library 10 | *.compileinfo 11 | # Don't include the tmc-file rule if either of the following is true: 12 | # 1. You've got TwinCAT C++ projects, as the information in the TMC-file is created manually for the C++ projects (in that case, only (manually) ignore the tmc-files for the PLC projects) 13 | # 2. You've created a standalone PLC-project and added events to it, as these are stored in the TMC-file. 14 | *.tmc 15 | *.tmcRefac 16 | *.library 17 | *.project.~u 18 | *.tsproj.bak 19 | *.xti.bak 20 | *.~u 21 | .DS_Store 22 | LineIDs.dbg 23 | LineIDs.dbg.bak 24 | _Boot/ 25 | _CompileInfo/ 26 | _Libraries/ 27 | _ModuleInstall/ 28 | 29 | 30 | ## Ignore Visual Studio temporary files, build results, and 31 | ## files generated by popular Visual Studio add-ons. 32 | ## 33 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 34 | 35 | # User-specific files 36 | *.rsuser 37 | *.suo 38 | *.user 39 | *.userosscache 40 | *.sln.docstates 41 | 42 | # User-specific files (MonoDevelop/Xamarin Studio) 43 | *.userprefs 44 | 45 | # Mono auto generated files 46 | mono_crash.* 47 | 48 | # Build results 49 | [Dd]ebug/ 50 | [Dd]ebugPublic/ 51 | [Rr]elease/ 52 | [Rr]eleases/ 53 | x64/ 54 | x86/ 55 | [Ww][Ii][Nn]32/ 56 | [Aa][Rr][Mm]/ 57 | [Aa][Rr][Mm]64/ 58 | bld/ 59 | [Bb]in/ 60 | [Oo]bj/ 61 | [Ll]og/ 62 | [Ll]ogs/ 63 | 64 | # Visual Studio 2015/2017 cache/options directory 65 | .vs/ 66 | # Uncomment if you have tasks that create the project's static files in wwwroot 67 | #wwwroot/ 68 | 69 | # Visual Studio 2017 auto generated files 70 | Generated\ Files/ 71 | 72 | # MSTest test Results 73 | [Tt]est[Rr]esult*/ 74 | [Bb]uild[Ll]og.* 75 | 76 | # NUnit 77 | *.VisualState.xml 78 | TestResult.xml 79 | nunit-*.xml 80 | 81 | # Build Results of an ATL Project 82 | [Dd]ebugPS/ 83 | [Rr]eleasePS/ 84 | dlldata.c 85 | 86 | # Benchmark Results 87 | BenchmarkDotNet.Artifacts/ 88 | 89 | # .NET 90 | project.lock.json 91 | project.fragment.lock.json 92 | artifacts/ 93 | 94 | # Tye 95 | .tye/ 96 | 97 | # ASP.NET Scaffolding 98 | ScaffoldingReadMe.txt 99 | 100 | # StyleCop 101 | StyleCopReport.xml 102 | 103 | # Files built by Visual Studio 104 | *_i.c 105 | *_p.c 106 | *_h.h 107 | *.ilk 108 | *.meta 109 | *.obj 110 | *.iobj 111 | *.pch 112 | *.pdb 113 | *.ipdb 114 | *.pgc 115 | *.pgd 116 | *.rsp 117 | *.sbr 118 | *.tlb 119 | *.tli 120 | *.tlh 121 | *.tmp 122 | *.tmp_proj 123 | *_wpftmp.csproj 124 | *.log 125 | *.tlog 126 | *.vspscc 127 | *.vssscc 128 | .builds 129 | *.pidb 130 | *.svclog 131 | *.scc 132 | 133 | # Chutzpah Test files 134 | _Chutzpah* 135 | 136 | # Visual C++ cache files 137 | ipch/ 138 | *.aps 139 | *.ncb 140 | *.opendb 141 | *.opensdf 142 | *.sdf 143 | *.cachefile 144 | *.VC.db 145 | *.VC.VC.opendb 146 | 147 | # Visual Studio profiler 148 | *.psess 149 | *.vsp 150 | *.vspx 151 | *.sap 152 | 153 | # Visual Studio Trace Files 154 | *.e2e 155 | 156 | # TFS 2012 Local Workspace 157 | $tf/ 158 | 159 | # Guidance Automation Toolkit 160 | *.gpState 161 | 162 | # ReSharper is a .NET coding add-in 163 | _ReSharper*/ 164 | *.[Rr]e[Ss]harper 165 | *.DotSettings.user 166 | 167 | # TeamCity is a build add-in 168 | _TeamCity* 169 | 170 | # DotCover is a Code Coverage Tool 171 | *.dotCover 172 | 173 | # AxoCover is a Code Coverage Tool 174 | .axoCover/* 175 | !.axoCover/settings.json 176 | 177 | # Coverlet is a free, cross platform Code Coverage Tool 178 | coverage*.json 179 | coverage*.xml 180 | coverage*.info 181 | 182 | # Visual Studio code coverage results 183 | *.coverage 184 | *.coveragexml 185 | 186 | # NCrunch 187 | _NCrunch_* 188 | .*crunch*.local.xml 189 | nCrunchTemp_* 190 | 191 | # MightyMoose 192 | *.mm.* 193 | AutoTest.Net/ 194 | 195 | # Web workbench (sass) 196 | .sass-cache/ 197 | 198 | # Installshield output folder 199 | [Ee]xpress/ 200 | 201 | # DocProject is a documentation generator add-in 202 | DocProject/buildhelp/ 203 | DocProject/Help/*.HxT 204 | DocProject/Help/*.HxC 205 | DocProject/Help/*.hhc 206 | DocProject/Help/*.hhk 207 | DocProject/Help/*.hhp 208 | DocProject/Help/Html2 209 | DocProject/Help/html 210 | 211 | # Click-Once directory 212 | publish/ 213 | 214 | # Publish Web Output 215 | *.[Pp]ublish.xml 216 | *.azurePubxml 217 | # Note: Comment the next line if you want to checkin your web deploy settings, 218 | # but database connection strings (with potential passwords) will be unencrypted 219 | *.pubxml 220 | *.publishproj 221 | 222 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 223 | # checkin your Azure Web App publish settings, but sensitive information contained 224 | # in these scripts will be unencrypted 225 | PublishScripts/ 226 | 227 | # NuGet Packages 228 | *.nupkg 229 | # NuGet Symbol Packages 230 | *.snupkg 231 | # The packages folder can be ignored because of Package Restore 232 | **/[Pp]ackages/* 233 | # except build/, which is used as an MSBuild target. 234 | !**/[Pp]ackages/build/ 235 | # Uncomment if necessary however generally it will be regenerated when needed 236 | #!**/[Pp]ackages/repositories.config 237 | # NuGet v3's project.json files produces more ignorable files 238 | *.nuget.props 239 | *.nuget.targets 240 | 241 | # Microsoft Azure Build Output 242 | csx/ 243 | *.build.csdef 244 | 245 | # Microsoft Azure Emulator 246 | ecf/ 247 | rcf/ 248 | 249 | # Windows Store app package directories and files 250 | AppPackages/ 251 | BundleArtifacts/ 252 | Package.StoreAssociation.xml 253 | _pkginfo.txt 254 | *.appx 255 | *.appxbundle 256 | *.appxupload 257 | 258 | # Visual Studio cache files 259 | # files ending in .cache can be ignored 260 | *.[Cc]ache 261 | # but keep track of directories ending in .cache 262 | !?*.[Cc]ache/ 263 | 264 | # Others 265 | ClientBin/ 266 | ~$* 267 | *~ 268 | *.dbmdl 269 | *.dbproj.schemaview 270 | *.jfm 271 | *.pfx 272 | *.publishsettings 273 | orleans.codegen.cs 274 | 275 | # Including strong name files can present a security risk 276 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 277 | #*.snk 278 | 279 | # Since there are multiple workflows, uncomment next line to ignore bower_components 280 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 281 | #bower_components/ 282 | 283 | # RIA/Silverlight projects 284 | Generated_Code/ 285 | 286 | # Backup & report files from converting an old project file 287 | # to a newer Visual Studio version. Backup files are not needed, 288 | # because we have git ;-) 289 | _UpgradeReport_Files/ 290 | Backup*/ 291 | UpgradeLog*.XML 292 | UpgradeLog*.htm 293 | ServiceFabricBackup/ 294 | *.rptproj.bak 295 | 296 | # SQL Server files 297 | *.mdf 298 | *.ldf 299 | *.ndf 300 | 301 | # Business Intelligence projects 302 | *.rdl.data 303 | *.bim.layout 304 | *.bim_*.settings 305 | *.rptproj.rsuser 306 | *- [Bb]ackup.rdl 307 | *- [Bb]ackup ([0-9]).rdl 308 | *- [Bb]ackup ([0-9][0-9]).rdl 309 | 310 | # Microsoft Fakes 311 | FakesAssemblies/ 312 | 313 | # GhostDoc plugin setting file 314 | *.GhostDoc.xml 315 | 316 | # Node.js Tools for Visual Studio 317 | .ntvs_analysis.dat 318 | node_modules/ 319 | 320 | # Visual Studio 6 build log 321 | *.plg 322 | 323 | # Visual Studio 6 workspace options file 324 | *.opt 325 | 326 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 327 | *.vbw 328 | 329 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 330 | *.vbp 331 | 332 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 333 | *.dsw 334 | *.dsp 335 | 336 | # Visual Studio 6 technical files 337 | *.ncb 338 | *.aps 339 | 340 | # Visual Studio LightSwitch build output 341 | **/*.HTMLClient/GeneratedArtifacts 342 | **/*.DesktopClient/GeneratedArtifacts 343 | **/*.DesktopClient/ModelManifest.xml 344 | **/*.Server/GeneratedArtifacts 345 | **/*.Server/ModelManifest.xml 346 | _Pvt_Extensions 347 | 348 | # Paket dependency manager 349 | .paket/paket.exe 350 | paket-files/ 351 | 352 | # FAKE - F# Make 353 | .fake/ 354 | 355 | # CodeRush personal settings 356 | .cr/personal 357 | 358 | # Python Tools for Visual Studio (PTVS) 359 | __pycache__/ 360 | *.pyc 361 | 362 | # Cake - Uncomment if you are using it 363 | # tools/** 364 | # !tools/packages.config 365 | 366 | # Tabs Studio 367 | *.tss 368 | 369 | # Telerik's JustMock configuration file 370 | *.jmconfig 371 | 372 | # BizTalk build output 373 | *.btp.cs 374 | *.btm.cs 375 | *.odx.cs 376 | *.xsd.cs 377 | 378 | # OpenCover UI analysis results 379 | OpenCover/ 380 | 381 | # Azure Stream Analytics local run output 382 | ASALocalRun/ 383 | 384 | # MSBuild Binary and Structured Log 385 | *.binlog 386 | 387 | # NVidia Nsight GPU debugger configuration file 388 | *.nvuser 389 | 390 | # MFractors (Xamarin productivity tool) working folder 391 | .mfractor/ 392 | 393 | # Local History for Visual Studio 394 | .localhistory/ 395 | 396 | # Visual Studio History (VSHistory) files 397 | .vshistory/ 398 | 399 | # BeatPulse healthcheck temp database 400 | healthchecksdb 401 | 402 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 403 | MigrationBackup/ 404 | 405 | # Ionide (cross platform F# VS Code tools) working folder 406 | .ionide/ 407 | 408 | # Fody - auto-generated XML schema 409 | FodyWeavers.xsd 410 | 411 | # VS Code files for those working on multiple tools 412 | .vscode/* 413 | !.vscode/settings.json 414 | !.vscode/tasks.json 415 | !.vscode/launch.json 416 | !.vscode/extensions.json 417 | *.code-workspace 418 | 419 | # Local History for Visual Studio Code 420 | .history/ 421 | 422 | # Windows Installer files from build outputs 423 | *.cab 424 | *.msi 425 | *.msix 426 | *.msm 427 | *.msp 428 | 429 | # JetBrains Rider 430 | *.sln.iml 431 | 432 | ## 433 | ## Visual studio for Mac 434 | ## 435 | 436 | 437 | # globs 438 | Makefile.in 439 | *.userprefs 440 | *.usertasks 441 | config.make 442 | config.status 443 | aclocal.m4 444 | install-sh 445 | autom4te.cache/ 446 | *.tar.gz 447 | tarballs/ 448 | test-results/ 449 | 450 | # Mac bundle stuff 451 | *.dmg 452 | *.app 453 | 454 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 455 | # General 456 | .DS_Store 457 | .AppleDouble 458 | .LSOverride 459 | 460 | # Icon must end with two \r 461 | Icon 462 | 463 | 464 | # Thumbnails 465 | ._* 466 | 467 | # Files that might appear in the root of a volume 468 | .DocumentRevisions-V100 469 | .fseventsd 470 | .Spotlight-V100 471 | .TemporaryItems 472 | .Trashes 473 | .VolumeIcon.icns 474 | .com.apple.timemachine.donotpresent 475 | 476 | # Directories potentially created on remote AFP share 477 | .AppleDB 478 | .AppleDesktop 479 | Network Trash Folder 480 | Temporary Items 481 | .apdisk 482 | 483 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 484 | # Windows thumbnail cache files 485 | Thumbs.db 486 | ehthumbs.db 487 | ehthumbs_vista.db 488 | 489 | # Dump file 490 | *.stackdump 491 | 492 | # Folder config file 493 | [Dd]esktop.ini 494 | 495 | # Recycle Bin used on file shares 496 | $RECYCLE.BIN/ 497 | 498 | # Windows Installer files 499 | *.cab 500 | *.msi 501 | *.msix 502 | *.msm 503 | *.msp 504 | 505 | # Windows shortcuts 506 | *.lnk 507 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | :blue_heart: :+1: Thanks for your time and effort in contributing to this project! :+1: :blue_heart: 2 | 3 | There are several ways to contribute to this project. You can report bugs, suggest enhancements or directly contribute code via pull requests. 4 | 5 | ### Reporting bugs 6 | 7 | * Please use the GitHub issue search feature to check if your bug is already reported. 8 | * If it is not reported yet, please provide a detailed description of the problem, including the steps to reproduce it. 9 | * If possible, please provide a minimalistic code example that reproduces the problem. 10 | * If possible, please provide screenshots that illustrate the problem. 11 | 12 | ### Suggesting enhancements 13 | 14 | * Please use the GitHub issue search feature to check if your enhancement is already suggested. 15 | * If it is not suggested yet, please provide a detailed description of your proposal, including the use case behind it. 16 | * If possible, please provide a minimalistic code example that illustrates your proposal. 17 | 18 | ### Pull requests 19 | 20 | Please make sure that your pull request 21 | * is based on the `main` branch 22 | * only contains related commits 23 | * contains a detailed description of the changes 24 | * contains tests for the changes 25 | 26 | 27 | ### Environment setup 28 | 29 | #### TwinCAT and Visual Studio 30 | The project uses TwinCAT 3.1.4024.29 and Visual Studio 2019 with .NET 7.0. 31 | 32 | Please use the recommended [Zeugwerk IDE settings.](https://doc.zeugwerk.dev/contribute/recom_xae_settings.html) 33 | 34 | #### Unit tests 35 | 36 | [TcUnit](https://tcunit.org/) is used for unit tests in plc project itself. 37 | 38 | These tests are complemented by [XUnit](https://xunit.net/) tests in the .NET project. These tests mainly cover the the file-system related actions, since these are way easier to implement in .NET than in TwinCAT. 39 | 40 | #### Code style 41 | 42 | The project uses the [TcBlack](https://github.com/Roald87/TcBlack) formatter for TwinCAT code. Please make sure to format your code accordingly before creating a pull request. 43 | 44 | #### Naming conventions 45 | 46 | This project uses [Zeugwerk naming conventions](https://doc.zeugwerk.dev/contribute/contribute_code.html#naming-conventions) for the TwinCAT code. 47 | 48 | Unfortunately, the Beckhoff PLC Static Analysis does not support checking custom naming conventions yet. Therefore, please make sure to check your code manually before creating a pull request. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 bengeisler 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 | ![](docs/images/TcLog_header.svg "TcLog_header") 2 | 3 | *Logging in TwinCAT with the on-board means is limited to the output as ADS event. The TcLog library presented here enables flexible logging to the file system.* 4 | 5 | It's usage is as simple as this: 6 | 7 | Configure the core logger in your project: 8 | 9 | ```st 10 | VAR 11 | _coreLogger : TcLogLib.TcLogCore(bufferSize := 100 * (Tc2_System.MAX_STRING_LENGTH + TcLogLib.Constants.FifoOverhead)); 12 | END_VAR 13 | 14 | _coreLogger 15 | .WriteToAds() 16 | .WriteToFile('c:\logs\', 'sensor_data.txt') 17 | .MinimumLevel(TcLogLib.LogLevels.Debug) 18 | .RunLogger(); 19 | ``` 20 | 21 | Then, maybe in a different POU, use `TcLog` to log messages: 22 | 23 | ```st 24 | VAR 25 | _logger: TcLogLib.TcLog; 26 | _myInt : INT := 10; 27 | _myVarInfo : __SYSTEM.VAR_INFO := __VARINFO(_myInt); 28 | END_VAR 29 | 30 | _logger 31 | .AppendString('Let´s log some values: ') 32 | .AppendAny(_myInt) 33 | .AppendString(' - or some symbols: ') 34 | .AppendVariable(_myVarInfo, _myInt) 35 | .Error(''); 36 | ``` 37 | 38 | This will log both messages to both the ADS output and the file system. 39 | 40 | 🚀 **Features** 🚀 41 | 42 | - Log to ADS output 43 | - Log to file system 44 | - Fluent interface for easy configuration and usage 45 | - Specification of minimum log level 46 | - Set rolling interval for log files 47 | - Delete old log files automatically 48 | - Dynamically expanding log buffer 49 | - Log messages with or without timestamp 50 | - Custom log message formatting 51 | 52 | 🧪 **Tests** 🧪 53 | 54 | The project contains both unit ([TcUnit](https://tcunit.org)) and integration tests ([xUnit](https://xunit.net)). 55 | 56 | ## Install TcLog 57 | See the [installation guide](https://bengeisler.github.io/TcLog/userguide/installation.html) for instructions on how to install TcLog. 58 | 59 | ## Getting started 60 | Get quickly up and running with TcLog: [Get Started](https://bengeisler.github.io/TcLog/userguide/getting_started.html) 61 | 62 | ## API reference 63 | Find the full API reference [here](https://bengeisler.github.io/TcLog/reference/TcLogLib/Constants.html). 64 | 65 | ## License 66 | The library is licensed under the [MIT License](LICENSE). 67 | 68 | ## Contributing 69 | Contributions are welcome. Please see the [contribution guide](CONTRIBUTING.md) for details. 70 | 71 | Create a library release by running `.\release_library.ps1 -version "major.minor.patch"` from the `tools` folder. This removes the dependency on `TcUnit` and sets the lib version. 72 | 73 | ## Further ways of logging in TwinCAT 74 | With [log4TC](https://mbc-engineering.github.io/log4TC/index.html) there is another logging option for TwinCAT. This enables structured logging, but an additional Windows service must be installed, which communicates with the PLC library. `TcLog` on the other hand comes as a pure PLC library. 75 | The code for log4TC has been published as open source on [GitHub](https://github.com/mbc-engineering/log4TC/releases). 76 | 77 | ## Disclaimer 78 | This project is not affiliated with Beckhoff Automation GmbH & Co. KG and was first published in 2021 at [my blog](https://benediktgeisler.de/en/blog/tclog/). 79 | -------------------------------------------------------------------------------- /docs/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "content": [ 4 | { 5 | "files": [ 6 | "userguide/*.md", 7 | "userguide/toc.yml", 8 | "reference/**/*.md", 9 | "reference/toc.yml", 10 | "reference/**/toc.yml", 11 | "toc.yml", 12 | "*.md" 13 | ] 14 | } 15 | ], 16 | "resource": [ 17 | { 18 | "files": ["images/**"] 19 | } 20 | ], 21 | "dest": "html", 22 | "fileMetadataFiles": [], 23 | "template": [ 24 | "statictoc", "template" 25 | ], 26 | "postProcessors": [], 27 | "markdownEngineName": "markdig", 28 | "noLangKeyword": false, 29 | "keepFileLink": false, 30 | "cleanupCacheHistory": false, 31 | "disableGitFeatures": false 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/images/TcLog_header.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | TcLog 40 | Flexible logging 41 | functionality for 42 | TwinCAT 3. 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # TcLog 2 | `TcLog` is a flexible logging framework for TwinCAT 3 that allows to log to both the ADS output and the file system. It is designed to be easy to use and to integrate into your project. It has no external dependencies and is available as library for TwinCAT 3. 3 | 4 | It features the following: 5 | - Log to ADS output 6 | - Log to file system 7 | - Fluent interface for easy configuration and usage 8 | - Specification of minimum log level 9 | - Set rolling interval for log files 10 | - Delete old log files automatically 11 | - Dynamically expanding log buffer 12 | - Log messages with or without timestamp 13 | - Custom log message formatting 14 | 15 | The library is fully unit- and integration-tested. 16 | 17 | ## Install TcLog 18 | See here how to install TcLog: 19 | 20 | 21 | ## Getting started 22 | Get quickly up and running with TcLog: 23 | 24 | 25 | ## Documentation 26 | Find the full API reference here: 27 | 28 | 29 | ## License 30 | The library is licensed under the [MIT License](../LICENSE). -------------------------------------------------------------------------------- /docs/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "_appTitle": "TcLog", 3 | "_appFooter": "(c) 2023 Ben Geisler - Documentation powered by Zeugwerk GmbH", 4 | "_disableContribution": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/toc.yml: -------------------------------------------------------------------------------- 1 | - name: User guide 2 | href: userguide/ 3 | - name: API Reference 4 | href: reference/ 5 | -------------------------------------------------------------------------------- /docs/userguide/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | `TcLogCore` is used to build the logging configuration and to run the persistence mechanism. 3 | 4 | ## Message format 5 | The message format can be adapted with several methods of `TcLogCore` that are described in the following. 6 | 7 | ### Delimiter 8 | `TcLogCore` can be configured to use an arbitrary delimiter between the components of the log entry with `.SetDelimiter('|')`. 9 | 10 | ### Including the instance path in the log message 11 | TcLog offers with `.IncludeInstancePath()` the possibility to include the location where the message was triggered into the message text: 12 | 13 | ```st 14 | _coreLogger 15 | .WriteToAds() 16 | .IncludeInstancePath() 17 | .MinimumLevel(LogLevels.Warning) 18 | .RunLogger(); 19 | 20 | _logger.Error('This is an error message.'); 21 | ``` 22 | 23 | ![Including the instance path](https://www.benediktgeisler.de/InstancePath.png "Including the instance path") 24 | 25 | ## Log to ADS output 26 | When adding the method `.WriteToAds()` to `TcLogCore`, the log messages are sent to the ADS output: 27 | 28 | ```st 29 | _coreLogger 30 | .WriteToAds() 31 | .RunLogger(); 32 | ``` 33 | 34 | ## Log to file system 35 | TcLog brings the option to store logs in the file system in the form of text files. This option can be applied to `TcLogCore` via the method `.WriteToFile(path, filename)`: 36 | 37 | ```st 38 | _coreLogger 39 | .IncludeInstancePath() 40 | .MinimumLevel(LogLevels.Warning) 41 | .WriteToFile('c:\logs\', 'test.txt') 42 | .RunLogger(); 43 | 44 | _loggerTrig 45 | .OnRisingEdge(_log) 46 | .Error('rTrig Test'); 47 | ``` 48 | 49 | ![Logging to the file system](https://www.benediktgeisler.de/LogMessageInFiileSystem.png "Logging to the file system") 50 | 51 | ### Timestamp 52 | The file name is additionally prefixed with the creation date of the log file. The format of the date can be defined arbitrarily by means of a format string. Example: 53 | 54 | *YYMMDD-hh:mm:ss:iii* 55 | 56 | > [!IMPORTANT] 57 | > Upper and lower case must be maintained, furthermore the same letters must always be placed one after the other. Blocks of identical letters are not permitted: ~~*YYMMDD-YYYY*~~ 58 | 59 | This format is passed to `TcLogCore` via the method `.TimestampFormat('YYMMDD-hh:mm:ss:iii')`. 60 | 61 | ## Minimum log level 62 | With the method `.MinimumLevel(level)` of `TcLogCore` the minimum log level can be specified. All messages with a lower log level are ignored. 63 | 64 | TcLog supports the following log levels: 65 | - `LogLevels.Debug` 66 | - `LogLevels.Information` 67 | - `LogLevels.Warning` 68 | - `LogLevels.Error` 69 | - `LogLevels.Fatal` 70 | 71 | 72 | ## Rolling interval 73 | A *rolling interval* denotes the interval until a new log file is created. TcLog offers the possibility to create a new logfile in regular intervals. This *rolling interval* is specified to `TcLogCore` via `SetRollingInterval(..)`: 74 | - `RollingIntervals.None`: Do not create a new log file. 75 | - `RollingIntervals.Hourly`: Create a new log file every hour 76 | - `RollingIntervals.Daily`: Create a new log file daily 77 | - `RollingIntervals.Monthly`: Create a new log file every month. 78 | 79 | The log file is only created when a message is triggered. 80 | 81 | ## Delete old log files 82 | To get rid of old log files, a lifespan of logs can be set with help of the method `DeleteLogsAfterDays(days)` of `TcLogCore`. Log files whose lifespan exceed the specified limit will automatically be deleted at midnight. 83 | 84 | ## Starting the logger 85 | After the configuration is complete, the logger is started with the method `RunLogger()` of `TcLogCore`. 86 | 87 | ## Using different verbosity levels 88 | Different scenarios may require different logging strategies. For example, you may want to log all messages with a log level of `Error` or higher in production, but all messages with a log level of `Debug` or higher in development. You can achieve this like this: 89 | 90 | ```st 91 | VAR 92 | _coreLogger : TcLogCore(bufferSize := 100 * (Tc2_System.MAX_STRING_LENGTH + Constants.FifoOverhead)); 93 | _logger : TcLog; 94 | _isDevelopment : BOOL := TRUE; 95 | END_VAR 96 | 97 | _coreLogger 98 | .WriteToAds() 99 | .WriteToFile('c:\logs\', 'sensor_data.txt'); 100 | 101 | IF _isDevelopment THEN 102 | _coreLogger.MinimumLevel(LogLevels.Debug); 103 | ELSE 104 | _coreLogger.MinimumLevel(LogLevels.Error); 105 | END_IF 106 | 107 | _coreLogger.RunLogger(); 108 | 109 | _logger.Debug('This is a debug message.'); 110 | _logger.Error('This is an error message.'); 111 | ``` 112 | -------------------------------------------------------------------------------- /docs/userguide/customization.md: -------------------------------------------------------------------------------- 1 | # Customization 2 | TcLog is design to be easily customizable. This page shows how to customize the logger to your needs. 3 | 4 | ## Custom logging templates 5 | If one wants to record sensor data instead of the standard logs, for example, this is possible. The easiest way to do this is to program a wrapper around `TcLog` that enforces the specific template. 6 | 7 | ### Example: Logging of sensor data 8 | 9 | Suppose we want to record sensor data in `REAL` format. The data is to be saved in a csv file that has the following format: 10 | 11 | `hh:mm:ss;device designation;value;unit` 12 | 13 | And the output should look like this: 14 | 15 | `10:33:15;+CC1-B31;35.1;°C` 16 | 17 | ### Wrapper around `TcLog` 18 | 19 | As wrapper we use an function block that encapsulates `TcLog` and enforces the data input with the help of the inputs. Furthermore it implements the interface `ILog` which establishes the link between logger and base logger. 20 | 21 | ```st 22 | FUNCTION_BLOCK UserLog IMPLEMENTS ILog 23 | VAR_INPUT 24 | Condition: BOOL; 25 | Identification: STRING; 26 | Value: REAL; 27 | Unit: STRING; 28 | END_VAR 29 | VAR 30 | _getTimeData: DateTime; 31 | _timestamp: STRING; 32 | END_VAR 33 | VAR_STAT 34 | _logger: TcLog; 35 | END_VAR 36 | 37 | _getTimeData(); 38 | _timestamp := _getTimeData.ToString('hh:mm:ss'); 39 | 40 | _logger 41 | .OnCondition(Condition) 42 | .AppendString(_timestamp) 43 | .AppendString(';') 44 | .AppendString(Identification) 45 | .AppendString(';') 46 | .AppendAny(Value) 47 | .AppendString(';') 48 | .AppendString(Unit) 49 | .ToCustomFormat(''); 50 | ``` 51 | 52 | We can use the helper function `GenerateTimeData`, which returns the current date and time formatted via the `.ToString(Format)` method. With its help we generate the timestamp of the sensor data. 53 | 54 | The `.ToCustomFormat('')` method at the end of the chain causes the message to be logged unchanged. No additional information like further timestamps or instance path will be appended. 55 | 56 | ### The interface `ILog` 57 | 58 | The interface is implemented by passing the logger reference to the `TcLog` instance: 59 | 60 | ```st 61 | METHOD SetLogger : BOOL 62 | VAR_INPUT 63 | ref2Core : REFERENCE TO TcLogCore; 64 | END_VAR 65 | 66 | _logger.SetLogger(ref2Core); 67 | ``` 68 | 69 | ### Calling the wrapper 70 | 71 | Somewhere in our program `TcLogCore` is called cyclically. If there is more than one instance of it, we can tell our logger which instance we want via `.SetLogger(Instance)`. Otherwise the configuration of the logger singleton is used. 72 | 73 | ```st 74 | VAR 75 | _newLogger: TcLogCore; 76 | _rTrigLog : R_TRIG; 77 | _log : BOOL; 78 | _myLog : UserLog; 79 | _myValue: REAL := 1.0; 80 | _myValue2: REAL := 2.0; 81 | END_VAR 82 | 83 | _newLogger 84 | .MinimumLevel(LogLevels.Information) 85 | .SetRollingInterval(RollingIntervals.Hourly) 86 | .WriteToFile('c:\logs\', 'sensor.csv') 87 | .DeleteLogFilesAfterDays(1) 88 | .RunLogger(); 89 | 90 | _myLog.SetLogger(_newLogger); 91 | _rTrigLog(CLK := _log); 92 | 93 | _myLog( 94 | Condition := _rTrigLog.Q, 95 | Identification := '+CC1-B31', 96 | Value := _myValue, 97 | Unit := '°C'); 98 | 99 | _myLog( 100 | Condition := _rTrigLog.Q, 101 | Identification := '+CC1-B32', 102 | Value := _myValue2, 103 | Unit := '°C'); 104 | ``` 105 | 106 | As soon as logging is triggered via `_log`, the csv file and the entries in it are created: 107 | 108 | ![Custom logging](https://www.benediktgeisler.de/CustomLogging.png "Custom logging") 109 | 110 | ## Use of custom loggers 111 | `TcLogCore` implements the `ILogCore` interface which defines the `LogCustomFormat` and `LogStandardFormat` methods. 112 | A custom logger with for example other log sinks can be created in two ways: 113 | 1. create a new FB that inherits from `TcLogCore`. Thereby the new FB can be extended by additional functions and at the same time brings along all methods that `TcLogCore` has. 114 | 2. create a new FB that implements the `ILogCore` interface. This way the logger can be rewritten from scratch. The interface ensures that the existing instances of `TcLog` in the code can still be used. -------------------------------------------------------------------------------- /docs/userguide/getting_started.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | `TcLog` has two main building blocks: 3 | - `TcLog`, which is used to log messages. 4 | - `TcLogCore`, which is the central static logger that takes care of processing the logged messages, such as sending them to ADS ouput or persisting them to the file system. 5 | 6 | You would typically call `TcLogCore` once in your project and configure the logger behaviour. Then, you would use `TcLog` to log messages. Each call of `TcLog` then uses the same configuration specified with `TcLogCore`. While there is normally only one instance of `TcLogCore` in your project, you can create as many instances of `TcLog` as you like, typically one in each POU. Both `TcLog` and `TcLogCore` provide fluent interfaces to make configuration and log message creation as easy as possible. 7 | 8 | ## Example usage 9 | Configure the core logger in your project: 10 | 11 | ```st 12 | VAR 13 | _coreLogger : TcLogCore(bufferSize := 100 * (Tc2_System.MAX_STRING_LENGTH + Constants.FifoOverhead)); 14 | END_VAR 15 | 16 | _coreLogger 17 | .WriteToAds() 18 | .WriteToFile('c:\logs\', 'sensor_data.txt') 19 | .MinimumLevel(LogLevels.Debug) 20 | .RunLogger(); 21 | ``` 22 | 23 | Then, maybe in a different POU, use `TcLog` to log messages: 24 | 25 | ```st 26 | VAR 27 | _logger : TcLog; 28 | END_VAR 29 | 30 | _logger.Debug('This is a debug message.'); 31 | _logger.Error('This is an error message.'); 32 | ``` 33 | 34 | This will log both messages to both the ADS output and the file system. 35 | 36 | Next, see how to [configure TcLog](configuration.md) and how to [use TcLog in detail](logging.md). -------------------------------------------------------------------------------- /docs/userguide/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | There are two ways to install this library: 3 | - download the latest release and install it manually or 4 | - install it via Twinpack. 5 | 6 | ## Manual Installation 7 | 8 | Download the latest release from [here](https://github.com/bengeisler/TcLog/releases/latest) and install it manually in your project. See the [Beckhoff documentation](https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/4218300427.html&id=) on how to do this. 9 | 10 | ## Twinpack 11 | First, [install Twinpack](https://github.com/Zeugwerk/Twinpack#installation). 12 | 13 | Then, search for `TcLog` in the [Twinpack package manager](https://github.com/Zeugwerk/Twinpack#using-a-package) and install it. 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/userguide/license.md: -------------------------------------------------------------------------------- 1 | # License 2 | MIT License 3 | 4 | Copyright (c) 2021 bengeisler 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /docs/userguide/logging.md: -------------------------------------------------------------------------------- 1 | # Logging 2 | Next, we will look at the logging options of TcLog. 3 | 4 | ## Flexible logging 5 | TcLog implements a [StringBuilder](https://www.plccoder.com/fluent-code/) which makes it easy to build your own message text: 6 | 7 | ```st 8 | VAR 9 | _logger: TcLog; 10 | _myInt : INT := 10; 11 | _myVarInfo : __SYSTEM.VAR_INFO := __VARINFO(_myInt); 12 | END_VAR 13 | 14 | _logger 15 | .AppendString('Let´s log some values: ') 16 | .AppendAny(_myInt) 17 | .AppendString(' - or some symbols: ') 18 | .AppendVariable(_myVarInfo, _myInt) 19 | .Error(''); 20 | ``` 21 | ![Using a StringBuilder to generate the message text](https://www.benediktgeisler.de/StringBuilder_in_message_text.png "Using a StringBuilder to generate the message text") 22 | 23 | Thus any amount of information can be appended to the message without having to implement `TcLog` with a large number of input parameters, since TwinCAT (at least in version before build 4026.0) does not allow optional input parameters. 24 | 25 | The methods `AppendString`, `AppendAny` and `AppendVariable` append the passed in data to the message text. 26 | 27 | The methods `Debug`, `Info`, `Warning`, `Error` and `Fatal` log the message with the respective log level. If you only want to log a simple string, you can pass it directly to the respective method, e.g. `_logger.Debug('This is a debug message.')`. 28 | 29 | ## Conditional logging 30 | The most common use of logging will be in the form `IF ... THEN log() END_IF`. Therefore this query is already integrated in TcLog: 31 | 32 | ```st 33 | VAR 34 | _logger: TcLog; 35 | _triggerLogging : R_TRIG; 36 | _log : BOOL; 37 | END_VAR 38 | 39 | _triggerLogging(CLK := _log); 40 | _logger 41 | .OnCondition(_triggerLogging.Q) 42 | .Error('Only logs when OnCondition evaluates to TRUE.'); 43 | ``` 44 | 45 | ## Logging on rising/falling edges 46 | Since a log message is usually to be sent once in the event of a *status change*, TcLog also provides a block for this purpose: `TcLogTrig`. In contrast to `TcLog`, a separate instance must be created for each use of this block, since the edge state is stored internally. The conditional execution can thus be further simplified: 47 | 48 | ```st 49 | VAR 50 | _loggerTrig : TcLogTRIG; 51 | _log : BOOL; 52 | END_VAR 53 | 54 | _loggerTrig 55 | .OnRisingEdge(_log) 56 | .Error('rTrig Test'); 57 | ``` 58 | 59 | Likewise, logging can be triggered on falling edges with `OnFallingEdge(cond)`. 60 | 61 | ## Use of multiple loggers 62 | Even though the logger was primarily designed as a singleton, it is possible to use multiple loggers. For example, sensor data can be collected cyclically and stored in a separate log file. To add another logger, an instance of `TcLogCore` must be created. This is then bound to the desired `TcLog` instance: 63 | 64 | ```st 65 | VAR 66 | _newLogger: TcLogCore(bufferSize := 100 * (Tc2_System.MAX_STRING_LENGTH + Constants.FifoOverhead)); 67 | _logger: TcLog; 68 | _myInt : INT := 10; 69 | END_VAR 70 | 71 | _newLogger 72 | .MinimumLevel(LogLevels.Information) 73 | .SetRollingInterval(RollingIntervals.Hourly) 74 | .WriteToFile('c:\logs\', 'sensor_data.txt') 75 | .DeleteLogFilesAfterDays(7) 76 | .RunLogger(); 77 | 78 | // Bind the new logger to the TcLog instance 79 | _logger.SetLogger(_newLogger); 80 | 81 | _logger.AppendString('Sensor xy: ') 82 | .AppendAny(_myInt) 83 | .Information(''); 84 | ``` 85 | 86 | From now on `_logger` considers the configuration of `_newLogger`. 87 | 88 | > **Note**: If you use multiple loggers, TcLog will always take the **first** initialized logger as default logger. Take this into account if you use multiple loggers. -------------------------------------------------------------------------------- /docs/userguide/performance.md: -------------------------------------------------------------------------------- 1 | # Performance 2 | The logger can persist up to 100 messages per plc cycle. If you log more than that, the persistance mechanism will spread over several plc cycles. When logging less than 100 messages per cycle, it takes roughly `1.5 * # of consecutive cycles with logging` cycles to persist the messages; when logging more than 100 messages per cycle, multiply that value by a factor of 3. -------------------------------------------------------------------------------- /docs/userguide/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Installation 2 | href: installation.md 3 | - name: User guide 4 | items: 5 | - name: Getting started 6 | href: getting_started.md 7 | - name: Logging 8 | href: logging.md 9 | - name: Configuration 10 | href: configuration.md 11 | - name: Customization 12 | href: customization.md 13 | - name: License 14 | href: license.md -------------------------------------------------------------------------------- /src/TcLog.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # TcXaeShell Solution File, Format Version 11.00 4 | VisualStudioVersion = 15.0.34829.251 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{B1E792BE-AA5F-4E3C-8C82-674BF9C0715B}") = "TcLogProj", "TcLogProj\TcLogProj.tsproj", "{84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|TwinCAT CE7 (ARMV7) = Debug|TwinCAT CE7 (ARMV7) 12 | Debug|TwinCAT OS (ARMT2) = Debug|TwinCAT OS (ARMT2) 13 | Debug|TwinCAT OS (ARMV7-A) = Debug|TwinCAT OS (ARMV7-A) 14 | Debug|TwinCAT OS (ARMV7-M) = Debug|TwinCAT OS (ARMV7-M) 15 | Debug|TwinCAT OS (ARMV8-A) = Debug|TwinCAT OS (ARMV8-A) 16 | Debug|TwinCAT OS (x64) = Debug|TwinCAT OS (x64) 17 | Debug|TwinCAT RT (x64) = Debug|TwinCAT RT (x64) 18 | Debug|TwinCAT RT (x86) = Debug|TwinCAT RT (x86) 19 | Release|Any CPU = Release|Any CPU 20 | Release|TwinCAT CE7 (ARMV7) = Release|TwinCAT CE7 (ARMV7) 21 | Release|TwinCAT OS (ARMT2) = Release|TwinCAT OS (ARMT2) 22 | Release|TwinCAT OS (ARMV7-A) = Release|TwinCAT OS (ARMV7-A) 23 | Release|TwinCAT OS (ARMV7-M) = Release|TwinCAT OS (ARMV7-M) 24 | Release|TwinCAT OS (ARMV8-A) = Release|TwinCAT OS (ARMV8-A) 25 | Release|TwinCAT OS (x64) = Release|TwinCAT OS (x64) 26 | Release|TwinCAT RT (x64) = Release|TwinCAT RT (x64) 27 | Release|TwinCAT RT (x86) = Release|TwinCAT RT (x86) 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|Any CPU.ActiveCfg = Debug|TwinCAT RT (x64) 31 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|Any CPU.Build.0 = Debug|TwinCAT RT (x64) 32 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT CE7 (ARMV7).ActiveCfg = Debug|TwinCAT CE7 (ARMV7) 33 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT CE7 (ARMV7).Build.0 = Debug|TwinCAT CE7 (ARMV7) 34 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT OS (ARMT2).ActiveCfg = Debug|TwinCAT OS (ARMV8-A) 35 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT OS (ARMV7-A).ActiveCfg = Debug|TwinCAT OS (ARMV7-A) 36 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT OS (ARMV7-A).Build.0 = Debug|TwinCAT OS (ARMV7-A) 37 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT OS (ARMV7-M).ActiveCfg = Debug|TwinCAT OS (ARMV7-M) 38 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT OS (ARMV7-M).Build.0 = Debug|TwinCAT OS (ARMV7-M) 39 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT OS (ARMV8-A).ActiveCfg = Debug|TwinCAT OS (ARMV8-A) 40 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT OS (ARMV8-A).Build.0 = Debug|TwinCAT OS (ARMV8-A) 41 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT OS (x64).ActiveCfg = Debug|TwinCAT OS (x64) 42 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT OS (x64).Build.0 = Debug|TwinCAT OS (x64) 43 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT RT (x64).ActiveCfg = Debug|TwinCAT RT (x64) 44 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT RT (x64).Build.0 = Debug|TwinCAT RT (x64) 45 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT RT (x86).ActiveCfg = Debug|TwinCAT RT (x86) 46 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Debug|TwinCAT RT (x86).Build.0 = Debug|TwinCAT RT (x86) 47 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|Any CPU.ActiveCfg = Release|TwinCAT RT (x64) 48 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|Any CPU.Build.0 = Release|TwinCAT RT (x64) 49 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT CE7 (ARMV7).ActiveCfg = Release|TwinCAT CE7 (ARMV7) 50 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT CE7 (ARMV7).Build.0 = Release|TwinCAT CE7 (ARMV7) 51 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT OS (ARMT2).ActiveCfg = Release|TwinCAT OS (ARMV8-A) 52 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT OS (ARMV7-A).ActiveCfg = Release|TwinCAT OS (ARMV7-A) 53 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT OS (ARMV7-A).Build.0 = Release|TwinCAT OS (ARMV7-A) 54 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT OS (ARMV7-M).ActiveCfg = Release|TwinCAT OS (ARMV7-M) 55 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT OS (ARMV7-M).Build.0 = Release|TwinCAT OS (ARMV7-M) 56 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT OS (ARMV8-A).ActiveCfg = Release|TwinCAT OS (ARMV8-A) 57 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT OS (ARMV8-A).Build.0 = Release|TwinCAT OS (ARMV8-A) 58 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT OS (x64).ActiveCfg = Release|TwinCAT OS (x64) 59 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT OS (x64).Build.0 = Release|TwinCAT OS (x64) 60 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT RT (x64).ActiveCfg = Release|TwinCAT RT (x64) 61 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT RT (x64).Build.0 = Release|TwinCAT RT (x64) 62 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT RT (x86).ActiveCfg = Release|TwinCAT RT (x86) 63 | {84E2C694-C6B7-48C0-A46A-93E9E79E1E4B}.Release|TwinCAT RT (x86).Build.0 = Release|TwinCAT RT (x86) 64 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|Any CPU.ActiveCfg = Debug|TwinCAT RT (x64) 65 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|Any CPU.Build.0 = Debug|TwinCAT RT (x64) 66 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT CE7 (ARMV7).ActiveCfg = Debug|TwinCAT CE7 (ARMV7) 67 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT CE7 (ARMV7).Build.0 = Debug|TwinCAT CE7 (ARMV7) 68 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT OS (ARMT2).ActiveCfg = Debug|TwinCAT OS (ARMV8-A) 69 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT OS (ARMV7-A).ActiveCfg = Debug|TwinCAT OS (ARMV7-A) 70 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT OS (ARMV7-A).Build.0 = Debug|TwinCAT OS (ARMV7-A) 71 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT OS (ARMV7-M).ActiveCfg = Debug|TwinCAT OS (ARMV7-M) 72 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT OS (ARMV7-M).Build.0 = Debug|TwinCAT OS (ARMV7-M) 73 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT OS (ARMV8-A).ActiveCfg = Debug|TwinCAT OS (ARMV8-A) 74 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT OS (ARMV8-A).Build.0 = Debug|TwinCAT OS (ARMV8-A) 75 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT OS (x64).ActiveCfg = Debug|TwinCAT OS (x64) 76 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT OS (x64).Build.0 = Debug|TwinCAT OS (x64) 77 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT RT (x64).ActiveCfg = Debug|TwinCAT RT (x64) 78 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT RT (x64).Build.0 = Debug|TwinCAT RT (x64) 79 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT RT (x86).ActiveCfg = Debug|TwinCAT RT (x86) 80 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Debug|TwinCAT RT (x86).Build.0 = Debug|TwinCAT RT (x86) 81 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|Any CPU.ActiveCfg = Release|TwinCAT RT (x64) 82 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|Any CPU.Build.0 = Release|TwinCAT RT (x64) 83 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT CE7 (ARMV7).ActiveCfg = Release|TwinCAT CE7 (ARMV7) 84 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT CE7 (ARMV7).Build.0 = Release|TwinCAT CE7 (ARMV7) 85 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT OS (ARMT2).ActiveCfg = Release|TwinCAT OS (ARMV8-A) 86 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT OS (ARMV7-A).ActiveCfg = Release|TwinCAT OS (ARMV7-A) 87 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT OS (ARMV7-A).Build.0 = Release|TwinCAT OS (ARMV7-A) 88 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT OS (ARMV7-M).ActiveCfg = Release|TwinCAT OS (ARMV7-M) 89 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT OS (ARMV7-M).Build.0 = Release|TwinCAT OS (ARMV7-M) 90 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT OS (ARMV8-A).ActiveCfg = Release|TwinCAT OS (ARMV8-A) 91 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT OS (ARMV8-A).Build.0 = Release|TwinCAT OS (ARMV8-A) 92 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT OS (x64).ActiveCfg = Release|TwinCAT OS (x64) 93 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT OS (x64).Build.0 = Release|TwinCAT OS (x64) 94 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT RT (x64).ActiveCfg = Release|TwinCAT RT (x64) 95 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT RT (x64).Build.0 = Release|TwinCAT RT (x64) 96 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT RT (x86).ActiveCfg = Release|TwinCAT RT (x86) 97 | {534B7BB3-9B49-4F2B-86CC-2317930EA23D}.Release|TwinCAT RT (x86).Build.0 = Release|TwinCAT RT (x86) 98 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|Any CPU.ActiveCfg = Debug|TwinCAT RT (x86) 99 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT CE7 (ARMV7).ActiveCfg = Debug|TwinCAT CE7 (ARMV7) 100 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT CE7 (ARMV7).Build.0 = Debug|TwinCAT CE7 (ARMV7) 101 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT OS (ARMT2).ActiveCfg = Debug|TwinCAT OS (ARMV8-A) 102 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT OS (ARMV7-A).ActiveCfg = Debug|TwinCAT OS (ARMV7-A) 103 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT OS (ARMV7-A).Build.0 = Debug|TwinCAT OS (ARMV7-A) 104 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT OS (ARMV7-M).ActiveCfg = Debug|TwinCAT OS (ARMV7-M) 105 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT OS (ARMV7-M).Build.0 = Debug|TwinCAT OS (ARMV7-M) 106 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT OS (ARMV8-A).ActiveCfg = Debug|TwinCAT OS (ARMV8-A) 107 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT OS (ARMV8-A).Build.0 = Debug|TwinCAT OS (ARMV8-A) 108 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT OS (x64).ActiveCfg = Debug|TwinCAT OS (x64) 109 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT OS (x64).Build.0 = Debug|TwinCAT OS (x64) 110 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT RT (x64).ActiveCfg = Debug|TwinCAT RT (x64) 111 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT RT (x64).Build.0 = Debug|TwinCAT RT (x64) 112 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT RT (x86).ActiveCfg = Debug|TwinCAT RT (x86) 113 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Debug|TwinCAT RT (x86).Build.0 = Debug|TwinCAT RT (x86) 114 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|Any CPU.ActiveCfg = Release|TwinCAT RT (x86) 115 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT CE7 (ARMV7).ActiveCfg = Release|TwinCAT CE7 (ARMV7) 116 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT CE7 (ARMV7).Build.0 = Release|TwinCAT CE7 (ARMV7) 117 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT OS (ARMT2).ActiveCfg = Release|TwinCAT OS (ARMV8-A) 118 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT OS (ARMV7-A).ActiveCfg = Release|TwinCAT OS (ARMV7-A) 119 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT OS (ARMV7-A).Build.0 = Release|TwinCAT OS (ARMV7-A) 120 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT OS (ARMV7-M).ActiveCfg = Release|TwinCAT OS (ARMV7-M) 121 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT OS (ARMV7-M).Build.0 = Release|TwinCAT OS (ARMV7-M) 122 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT OS (ARMV8-A).ActiveCfg = Release|TwinCAT OS (ARMV8-A) 123 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT OS (ARMV8-A).Build.0 = Release|TwinCAT OS (ARMV8-A) 124 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT OS (x64).ActiveCfg = Release|TwinCAT OS (x64) 125 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT OS (x64).Build.0 = Release|TwinCAT OS (x64) 126 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT RT (x64).ActiveCfg = Release|TwinCAT RT (x64) 127 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT RT (x64).Build.0 = Release|TwinCAT RT (x64) 128 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT RT (x86).ActiveCfg = Release|TwinCAT RT (x86) 129 | {39E514BF-8337-4E82-99CA-D7D99BEEE7FE}.Release|TwinCAT RT (x86).Build.0 = Release|TwinCAT RT (x86) 130 | EndGlobalSection 131 | GlobalSection(SolutionProperties) = preSolution 132 | HideSolutionNode = FALSE 133 | EndGlobalSection 134 | GlobalSection(ExtensibilityGlobals) = postSolution 135 | SolutionGuid = {9AC818C4-6839-46EB-8C84-95820330651F} 136 | EndGlobalSection 137 | EndGlobal 138 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog.libcat.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | a008f0f9-813d-4727-ad9c-d60bc9412b1d 5 | 1.0.0.0 6 | TcLog 7 | 8 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/CONSTANTS.TcGVL: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Logger/LogLevelToAdsLogMsgType.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14 | 15 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Logger/TcLog.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 39 | 40 | 41 | 42 | 43 | 52 | 53 | 58 | 59 | 60 | 61 | 69 | 70 | 75 | 76 | 77 | 78 | 104 | 105 | 119 | 120 | 121 | 122 | 129 | 130 | InvalidLoggerReference THEN 133 | _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, message), ShortenInstancePath(_instancePath), LogLevels.Debug); 134 | ELSE 135 | Tc2_System.ADSLOGSTR(ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(LogLevels.Error), 'usedLogger: No valid reference', ''); 136 | END_IF 137 | END_IF 138 | _logDataInitialized := FALSE;]]> 139 | 140 | 141 | 142 | 149 | 150 | InvalidLoggerReference THEN 153 | _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, message), ShortenInstancePath(_instancePath), LogLevels.Error); 154 | ELSE 155 | Tc2_System.ADSLOGSTR(ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(LogLevels.Error), 'usedLogger: No valid reference', ''); 156 | END_IF 157 | END_IF 158 | _logDataInitialized := FALSE; 159 | ]]> 160 | 161 | 162 | 163 | 170 | 171 | InvalidLoggerReference THEN 174 | _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, message), ShortenInstancePath(_instancePath), LogLevels.Fatal); 175 | ELSE 176 | Tc2_System.ADSLOGSTR(ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(LogLevels.Error), 'usedLogger: No valid reference', ''); 177 | END_IF 178 | END_IF 179 | _logDataInitialized := FALSE;]]> 180 | 181 | 182 | 183 | 190 | 191 | InvalidLoggerReference THEN 194 | _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, message), ShortenInstancePath(_instancePath), LogLevels.Information); 195 | ELSE 196 | Tc2_System.ADSLOGSTR(ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(LogLevels.Error), 'usedLogger: No valid reference', ''); 197 | END_IF 198 | END_IF 199 | _logDataInitialized := FALSE;]]> 200 | 201 | 202 | 203 | 210 | 211 | 214 | 215 | 216 | 217 | 221 | 222 | 228 | 229 | 230 | 231 | 238 | 239 | 244 | 245 | 246 | 247 | 255 | 256 | 257 | 258 | 259 | 260 | 274 | 275 | 0) DO 279 | positionInString := Tc2_Standard.FIND(shortenedPath, '.'); 280 | shortenedPath := Tc2_Standard.DELETE(shortenedPath, positionInString+1, 0); 281 | positionOfLastDot := positionOfLastDot+positionInString; 282 | END_WHILE 283 | 284 | shortenedPath := Path; 285 | // Delete instantiation of this FB (obvious information) 286 | shortenedPath := Tc2_Standard.DELETE(shortenedPath, Tc2_Standard.LEN(shortenedPath)-positionOfLastDot+1, positionOfLastDot); 287 | // Delete project information 288 | positionInString := Tc2_Standard.FIND(shortenedPath, '.'); 289 | shortenedPath := Tc2_Standard.DELETE(shortenedPath, positionInString+1, 0); 290 | // Delete PLC information 291 | positionInString := Tc2_Standard.FIND(shortenedPath, '.'); 292 | shortenedPath := Tc2_Standard.DELETE(shortenedPath, positionInString+1, 0); 293 | 294 | ShortenInstancePath := shortenedPath;]]> 295 | 296 | 297 | 298 | 305 | 306 | 313 | 314 | 315 | 316 | 324 | 325 | InvalidLoggerReference THEN 328 | _usedLogger.LogCustomFormat(Tc2_Standard.CONCAT(_logData, message)); 329 | ELSE 330 | Tc2_System.ADSLOGSTR(ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(LogLevels.Error), 'usedLogger: No valid reference', ''); 331 | END_IF 332 | END_IF 333 | _logDataInitialized := FALSE;]]> 334 | 335 | 336 | 337 | 344 | 345 | InvalidLoggerReference THEN 348 | _usedLogger.LogStandardFormat(Tc2_Standard.CONCAT(_logData, message), ShortenInstancePath(_instancePath), LogLevels.Warning); 349 | ELSE 350 | Tc2_System.ADSLOGSTR(ADSLOG_MSGTYPE_LOG OR LogLevelToAdsLogMsgType(LogLevels.Error), 'usedLogger: No valid reference', ''); 351 | END_IF 352 | END_IF 353 | _logDataInitialized := FALSE;]]> 354 | 355 | 356 | 357 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Logger/TcLogTrig.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 30 | 31 | 32 | 33 | 44 | 45 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Logger/_DataTypes/Error.TcDUT: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Logger/_DataTypes/ErrorCodes.TcDUT: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 20 | 21 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Logger/_DataTypes/LogLevels.TcDUT: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Logger/_DataTypes/LoggingConfiguration.TcDUT: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 30 | 31 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Logger/_DataTypes/RollingIntervals.TcDUT: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Logger/_Interfaces/ILog.TcIO: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Logger/_Interfaces/ILogCore.TcIO: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 6 | 7 | 14 | 15 | 16 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Logger/_Tests/TestWrapper_NET.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 69 | 70 | = Number_of_log_cycles THEN 167 | _cycles := 0; 168 | Log_in_consecutive_cycles := FALSE; 169 | END_IF 170 | END_IF 171 | 172 | IF Log_multiple_logs_in_one_cycle THEN 173 | FOR _i:= 1 TO DINT_TO_INT(Number_of_logs_per_cycle) DO 174 | _logger. 175 | Error(Tc2_Standard.CONCAT('Logging multiple times per cycle. Current step: ', INT_TO_STRING(_i))); 176 | END_FOR 177 | Log_multiple_logs_in_one_cycle := FALSE; 178 | END_IF 179 | 180 | IF Log_multiple_logs_in_multiple_cycles THEN 181 | IF CycleCount <= Number_of_cycles THEN 182 | FOR _i:= 1 TO DINT_TO_INT(Number_of_logs_per_cycle) DO 183 | _logger 184 | .AppendString('Logging multiple times per cycle. Cycle: ') 185 | .AppendString(DINT_TO_STRING(CycleCount)) 186 | .AppendString(' / Step: ') 187 | .AppendString(INT_TO_STRING(_i)) 188 | .Error(''); 189 | END_FOR 190 | CycleCount := CycleCount + 1; 191 | ELSE 192 | CycleCount := 1; 193 | Log_multiple_logs_in_multiple_cycles := FALSE; 194 | END_IF 195 | END_IF 196 | 197 | _persistenceTimeStaysWithinBounds(CLK := (Persistance_time_stays_within_bounds AND _coreLogger.Busy)); 198 | IF _persistenceTimeStaysWithinBounds.Q THEN 199 | CycleCount := 1; 200 | Persistance_time_stays_within_bounds := FALSE; 201 | END_IF 202 | IF Persistance_time_stays_within_bounds THEN 203 | Duration_in_cylces := Duration_in_cylces + 1; 204 | IF CycleCount <= Number_of_cycles THEN 205 | FOR _i:= 1 TO DINT_TO_INT(Number_of_logs_per_cycle) DO 206 | _logger 207 | .AppendString('Logging multiple times per cycle. Cycle: ') 208 | .AppendString(DINT_TO_STRING(CycleCount)) 209 | .AppendString(' / Step: ') 210 | .AppendString(INT_TO_STRING(_i)) 211 | .Error(''); 212 | END_FOR 213 | CycleCount := CycleCount + 1; 214 | END_IF 215 | END_IF 216 | 217 | // Check first cpu cycle 218 | _coreLoggerFirstCycle 219 | .IncludeInstancePath() 220 | .MinimumLevel(LogLevels.Warning) 221 | .SetDelimiter(Delimiter) 222 | .SetRollingInterval(RollingIntervals.Hourly) 223 | .DeleteLogFilesAfterDays(1) 224 | .TimestampFormat('_YYYYMMDD-hh-mm-ss_') 225 | .WriteToAds() 226 | .WriteToFile('C:\UnitTestFirstCycle\', 'firstCycle.txt') 227 | .RunLogger(); 228 | _loggerFirstCycle.SetLogger(_coreLoggerFirstCycle); 229 | 230 | _coreLoggerFirstCycleValidTimestampOnly 231 | .IncludeInstancePath() 232 | .MinimumLevel(LogLevels.Warning) 233 | .SetDelimiter(Delimiter) 234 | .SetRollingInterval(RollingIntervals.Hourly) 235 | .DeleteLogFilesAfterDays(1) 236 | .TimestampFormat('_YYYYMMDD-hh-mm-ss_') 237 | .WriteToAds() 238 | .WriteToFile('C:\UnitTestFirstCycle\', 'firstCycleValidTimestampOnly.txt') 239 | .ValidTimestampsOnly() 240 | .RunLogger(); 241 | _loggerFirstCycleValidTimestampOnly.SetLogger(_coreLoggerFirstCycleValidTimestampOnly); 242 | 243 | IF _isFirstCycle THEN 244 | _loggerFirstCycle.Error('Log in first cycle'); 245 | _loggerFirstCycleValidTimestampOnly.Error('Log in first cycle'); 246 | _isFirstCycle := FALSE; 247 | END_IF 248 | 249 | 250 | ]]> 251 | 252 | 253 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/StringBuilder/StringBuilder.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 22 | 23 | 24 | 25 | 31 | 32 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 49 | 50 | 51 | 62 | 63 | 73 | 74 | 75 | 76 | 78 | 79 | 81 | 82 | 83 | 84 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/StringBuilder/_Interfaces/ILimitedStringBuilder.TcIO: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 6 | 7 | 12 | 13 | 14 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/StringBuilder/_Interfaces/IStringBuilder.TcIO: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 6 | 7 | 12 | 13 | 14 | 20 | 21 | 22 | 28 | 29 | 30 | 37 | 38 | 39 | 41 | 42 | 43 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/TcLog.plcproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0.0.0 4 | 2.0 5 | {534b7bb3-9b49-4f2b-86cc-2317930ea23d} 6 | True 7 | true 8 | false 9 | false 10 | TcLog 11 | 3.1.4026.12 12 | {b17b6069-df5e-455f-9777-ca2592ee6113} 13 | {827c7b48-6b19-4663-8b59-fefb0b436024} 14 | {39cb5e76-4fe2-46f6-ae40-1fcb2a6531eb} 15 | {74ded4cd-273e-4f0f-8a1f-4c4d89389741} 16 | {d696fa89-a5b7-4359-9420-e6a3f2f66b93} 17 | {e556042a-b034-4e51-bfa4-92060eb4b7b8} 18 | Benedikt Geisler 19 | false 20 | TcLog 21 | 0.6.1 22 | false 23 | TcLogLib 24 | Benedikt Geisler 25 | TcLog provides logging functionality to TwinCAT. 26 | 27 | 28 | {a008f0f9-813d-4727-ad9c-d60bc9412b1d} 29 | 1.0.0.0 30 | TcLog 31 | 32 | 33 | 34 | {a008f0f9-813d-4727-ad9c-d60bc9412b1d} 35 | 36 | false 37 | false 38 | false 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Code 54 | true 55 | 56 | 57 | Code 58 | 59 | 60 | Code 61 | 62 | 63 | Code 64 | 65 | 66 | Code 67 | 68 | 69 | Code 70 | 71 | 72 | Code 73 | 74 | 75 | Code 76 | 77 | 78 | Code 79 | 80 | 81 | Code 82 | 83 | 84 | Code 85 | 86 | 87 | Code 88 | 89 | 90 | Code 91 | 92 | 93 | Code 94 | 95 | 96 | Code 97 | 98 | 99 | Code 100 | 101 | 102 | Code 103 | 104 | 105 | Code 106 | 107 | 108 | Code 109 | 110 | 111 | Code 112 | 113 | 114 | Code 115 | 116 | 117 | Code 118 | 119 | 120 | Code 121 | 122 | 123 | Code 124 | 125 | 126 | Code 127 | 128 | 129 | Code 130 | 131 | 132 | 133 | 134 | IBaseLibrary 135 | 136 | 137 | 138 | 139 | Tc2_Standard, * (Beckhoff Automation GmbH) 140 | Tc2_Standard 141 | 142 | 143 | Tc2_System, * (Beckhoff Automation GmbH) 144 | Tc2_System 145 | 146 | 147 | Tc2_Utilities, * (Beckhoff Automation GmbH) 148 | Tc2_Utilities 149 | 150 | 151 | Tc3_DynamicMemory, * (Beckhoff Automation GmbH) 152 | Tc3_DynamicMemory 153 | 154 | 155 | Tc3_Module, * (Beckhoff Automation GmbH) 156 | Tc3_Module 157 | 158 | 159 | TcUnit, * (www.tcunit.org) 160 | TcUnit 161 | 162 | 163 | 164 | 165 | Content 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | "<ProjectRoot>" 174 | 175 | {192FAD59-8248-4824-A8DE-9177C94C195A} 176 | 177 | "{192FAD59-8248-4824-A8DE-9177C94C195A}" 178 | 179 | 180 | 181 | {246001F4-279D-43AC-B241-948EB31120E1} 182 | 183 | "{246001F4-279D-43AC-B241-948EB31120E1}" 184 | 185 | 186 | GlobalVisuImageFilePath 187 | %APPLICATIONPATH% 188 | 189 | 190 | {29BD8D0C-3586-4548-BB48-497B9A01693F} 191 | 192 | "{29BD8D0C-3586-4548-BB48-497B9A01693F}" 193 | 194 | Rules 195 | 196 | "Rules" 197 | 198 | 199 | 200 | 201 | 202 | 203 | {40450F57-0AA3-4216-96F3-5444ECB29763} 204 | 205 | "{40450F57-0AA3-4216-96F3-5444ECB29763}" 206 | 207 | 208 | ActiveVisuProfile 209 | IR0whWr8bwfwBwAAiD2qpQAAAABVAgAA37x72QAAAAABAAAAAAAAAAEaUwB5AHMAdABlAG0ALgBTAHQAcgBpAG4AZwACTHsAZgA5ADUAYgBiADQAMgA2AC0ANQA1ADIANAAtADQAYgA0ADUALQA5ADQAMAAwAC0AZgBiADAAZgAyAGUANwA3AGUANQAxAGIAfQADCE4AYQBtAGUABDBUAHcAaQBuAEMAQQBUACAAMwAuADEAIABCAHUAaQBsAGQAIAA0ADAAMgA0AC4ANwAFFlAAcgBvAGYAaQBsAGUARABhAHQAYQAGTHsAMQA2AGUANQA1AGIANgAwAC0ANwAwADQAMwAtADQAYQA2ADMALQBiADYANQBiAC0ANgAxADQANwAxADMAOAA3ADgAZAA0ADIAfQAHEkwAaQBiAHIAYQByAGkAZQBzAAhMewAzAGIAZgBkADUANAA1ADkALQBiADAANwBmAC0ANABkADYAZQAtAGEAZQAxAGEALQBhADgAMwAzADUANgBhADUANQAxADQAMgB9AAlMewA5AGMAOQA1ADgAOQA2ADgALQAyAGMAOAA1AC0ANAAxAGIAYgAtADgAOAA3ADEALQA4ADkANQBmAGYAMQBmAGUAZABlADEAYQB9AAoOVgBlAHIAcwBpAG8AbgALBmkAbgB0AAwKVQBzAGEAZwBlAA0KVABpAHQAbABlAA4aVgBpAHMAdQBFAGwAZQBtAE0AZQB0AGUAcgAPDkMAbwBtAHAAYQBuAHkAEAxTAHkAcwB0AGUAbQARElYAaQBzAHUARQBsAGUAbQBzABIwVgBpAHMAdQBFAGwAZQBtAHMAUwBwAGUAYwBpAGEAbABDAG8AbgB0AHIAbwBsAHMAEyhWAGkAcwB1AEUAbABlAG0AcwBXAGkAbgBDAG8AbgB0AHIAbwBsAHMAFCRWAGkAcwB1AEUAbABlAG0AVABlAHgAdABFAGQAaQB0AG8AcgAVIlYAaQBzAHUATgBhAHQAaQB2AGUAQwBvAG4AdAByAG8AbAAWFHYAaQBzAHUAaQBuAHAAdQB0AHMAFwxzAHkAcwB0AGUAbQAYGFYAaQBzAHUARQBsAGUAbQBCAGEAcwBlABkmRABlAHYAUABsAGEAYwBlAGgAbwBsAGQAZQByAHMAVQBzAGUAZAAaCGIAbwBvAGwAGyJQAGwAdQBnAGkAbgBDAG8AbgBzAHQAcgBhAGkAbgB0AHMAHEx7ADQAMwBkADUAMgBiAGMAZQAtADkANAAyAGMALQA0ADQAZAA3AC0AOQBlADkANAAtADEAYgBmAGQAZgAzADEAMABlADYAMwBjAH0AHRxBAHQATABlAGEAcwB0AFYAZQByAHMAaQBvAG4AHhRQAGwAdQBnAGkAbgBHAHUAaQBkAB8WUwB5AHMAdABlAG0ALgBHAHUAaQBkACBIYQBmAGMAZAA1ADQANAA2AC0ANAA5ADEANAAtADQAZgBlADcALQBiAGIANwA4AC0AOQBiAGYAZgBlAGIANwAwAGYAZAAxADcAIRRVAHAAZABhAHQAZQBJAG4AZgBvACJMewBiADAAMwAzADYANgBhADgALQBiADUAYwAwAC0ANABiADkAYQAtAGEAMAAwAGUALQBlAGIAOAA2ADAAMQAxADEAMAA0AGMAMwB9ACMOVQBwAGQAYQB0AGUAcwAkTHsAMQA4ADYAOABmAGYAYwA5AC0AZQA0AGYAYwAtADQANQAzADIALQBhAGMAMAA2AC0AMQBlADMAOQBiAGIANQA1ADcAYgA2ADkAfQAlTHsAYQA1AGIAZAA0ADgAYwAzAC0AMABkADEANwAtADQAMQBiADUALQBiADEANgA0AC0ANQBmAGMANgBhAGQAMgBiADkANgBiADcAfQAmFk8AYgBqAGUAYwB0AHMAVAB5AHAAZQAnVFUAcABkAGEAdABlAEwAYQBuAGcAdQBhAGcAZQBNAG8AZABlAGwARgBvAHIAQwBvAG4AdgBlAHIAdABpAGIAbABlAEwAaQBiAHIAYQByAGkAZQBzACgQTABpAGIAVABpAHQAbABlACkUTABpAGIAQwBvAG0AcABhAG4AeQAqHlUAcABkAGEAdABlAFAAcgBvAHYAaQBkAGUAcgBzACs4UwB5AHMAdABlAG0ALgBDAG8AbABsAGUAYwB0AGkAbwBuAHMALgBIAGEAcwBoAHQAYQBiAGwAZQAsEnYAaQBzAHUAZQBsAGUAbQBzAC1INgBjAGIAMQBjAGQAZQAxAC0AZAA1AGQAYwAtADQAYQAzAGIALQA5ADAANQA0AC0AMgAxAGYAYQA3ADUANgBhADMAZgBhADQALihJAG4AdABlAHIAZgBhAGMAZQBWAGUAcgBzAGkAbwBuAEkAbgBmAG8AL0x7AGMANgAxADEAZQA0ADAAMAAtADcAZgBiADkALQA0AGMAMwA1AC0AYgA5AGEAYwAtADQAZQAzADEANABiADUAOQA5ADYANAAzAH0AMBhNAGEAagBvAHIAVgBlAHIAcwBpAG8AbgAxGE0AaQBuAG8AcgBWAGUAcgBzAGkAbwBuADIMTABlAGcAYQBjAHkAMzBMAGEAbgBnAHUAYQBnAGUATQBvAGQAZQBsAFYAZQByAHMAaQBvAG4ASQBuAGYAbwA0MEwAbwBhAGQATABpAGIAcgBhAHIAaQBlAHMASQBuAHQAbwBQAHIAbwBqAGUAYwB0ADUaQwBvAG0AcABhAHQAaQBiAGkAbABpAHQAeQDQAAIaA9ADAS0E0AUGGgfQBwgaAUUHCQjQAAkaBEUKCwQDAAAABQAAAA0AAAAAAAAA0AwLrQIAAADQDQEtDtAPAS0Q0AAJGgRFCgsEAwAAAAUAAAANAAAAKAAAANAMC60BAAAA0A0BLRHQDwEtENAACRoERQoLBAMAAAAFAAAADQAAAAAAAADQDAutAgAAANANAS0S0A8BLRDQAAkaBEUKCwQDAAAABQAAAA0AAAAUAAAA0AwLrQIAAADQDQEtE9APAS0Q0AAJGgRFCgsEAwAAAAUAAAANAAAAAAAAANAMC60CAAAA0A0BLRTQDwEtENAACRoERQoLBAMAAAAFAAAADQAAAAAAAADQDAutAgAAANANAS0V0A8BLRDQAAkaBEUKCwQDAAAABQAAAA0AAAAAAAAA0AwLrQIAAADQDQEtFtAPAS0X0AAJGgRFCgsEAwAAAAUAAAANAAAAKAAAANAMC60EAAAA0A0BLRjQDwEtENAZGq0BRRscAdAAHBoCRR0LBAMAAAAFAAAADQAAAAAAAADQHh8tINAhIhoCRSMkAtAAJRoFRQoLBAMAAAADAAAAAAAAAAoAAADQJgutAAAAANADAS0n0CgBLRHQKQEtENAAJRoFRQoLBAMAAAADAAAAAAAAAAoAAADQJgutAQAAANADAS0n0CgBLRHQKQEtEJoqKwFFAAEC0AABLSzQAAEtF9AAHy0t0C4vGgPQMAutAQAAANAxC60XAAAA0DIarQDQMy8aA9AwC60CAAAA0DELrQMAAADQMhqtANA0Gq0A0DUarQA= 210 | 211 | 212 | {8A0FB252-96EB-4DCC-A5B4-B4804D05E2D6} 213 | 214 | "{8A0FB252-96EB-4DCC-A5B4-B4804D05E2D6}" 215 | 216 | 217 | WriteLineIDs 218 | False 219 | 220 | 221 | {8F99A816-E488-41E4-9FA3-846536012284} 222 | 223 | "{8F99A816-E488-41E4-9FA3-846536012284}" 224 | 225 | 226 | DisabledWarningIds 227 | 5410 228 | 229 | 230 | {F66C7017-BDD8-4114-926C-81D6D687E35F} 231 | 232 | "{F66C7017-BDD8-4114-926C-81D6D687E35F}" 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | System.Boolean 242 | System.Collections.Hashtable 243 | {54dd0eac-a6d8-46f2-8c27-2f43c7e49861} 244 | System.String 245 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/TcLog.plcproj.bak: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0.0.0 4 | 2.0 5 | {534b7bb3-9b49-4f2b-86cc-2317930ea23d} 6 | True 7 | true 8 | false 9 | false 10 | TcLog 11 | 3.1.4023.0 12 | {b17b6069-df5e-455f-9777-ca2592ee6113} 13 | {827c7b48-6b19-4663-8b59-fefb0b436024} 14 | {39cb5e76-4fe2-46f6-ae40-1fcb2a6531eb} 15 | {74ded4cd-273e-4f0f-8a1f-4c4d89389741} 16 | {d696fa89-a5b7-4359-9420-e6a3f2f66b93} 17 | {e556042a-b034-4e51-bfa4-92060eb4b7b8} 18 | Benedikt Geisler 19 | false 20 | TcLog 21 | 0.2.2 22 | false 23 | TcLog 24 | Benedikt Geisler 25 | TcLog provides logging functionality to TwinCAT. 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Code 40 | true 41 | 42 | 43 | Code 44 | 45 | 46 | Code 47 | 48 | 49 | Code 50 | 51 | 52 | Code 53 | 54 | 55 | Code 56 | 57 | 58 | Code 59 | 60 | 61 | Code 62 | 63 | 64 | Code 65 | 66 | 67 | Code 68 | 69 | 70 | Code 71 | 72 | 73 | Code 74 | 75 | 76 | Code 77 | 78 | 79 | Code 80 | 81 | 82 | Code 83 | 84 | 85 | Code 86 | 87 | 88 | Code 89 | 90 | 91 | Code 92 | 93 | 94 | Code 95 | 96 | 97 | Code 98 | 99 | 100 | Code 101 | 102 | 103 | Code 104 | 105 | 106 | Code 107 | 108 | 109 | 110 | 111 | IBaseLibrary 112 | 113 | 114 | 115 | 116 | Tc2_Standard, * (Beckhoff Automation GmbH) 117 | Tc2_Standard 118 | 119 | 120 | Tc2_System, * (Beckhoff Automation GmbH) 121 | Tc2_System 122 | 123 | 124 | Tc2_Utilities, * (Beckhoff Automation GmbH) 125 | Tc2_Utilities 126 | 127 | 128 | Tc3_DynamicMemory, * (Beckhoff Automation GmbH) 129 | Tc3_DynamicMemory 130 | 131 | 132 | Tc3_Module, * (Beckhoff Automation GmbH) 133 | Tc3_Module 134 | 135 | 136 | 137 | 138 | Content 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | "<ProjectRoot>" 147 | 148 | {8F99A816-E488-41E4-9FA3-846536012284} 149 | 150 | "{8F99A816-E488-41E4-9FA3-846536012284}" 151 | 152 | 153 | 154 | {29BD8D0C-3586-4548-BB48-497B9A01693F} 155 | 156 | "{29BD8D0C-3586-4548-BB48-497B9A01693F}" 157 | 158 | Rules 159 | 160 | "Rules" 161 | 162 | 163 | 164 | 165 | 166 | 167 | {40450F57-0AA3-4216-96F3-5444ECB29763} 168 | 169 | "{40450F57-0AA3-4216-96F3-5444ECB29763}" 170 | 171 | 172 | ActiveVisuProfile 173 | IR0whWr8bwfwBwAAiD2qpQAAAABVAgAA37x72QAAAAABAAAAAAAAAAEaUwB5AHMAdABlAG0ALgBTAHQAcgBpAG4AZwACTHsAZgA5ADUAYgBiADQAMgA2AC0ANQA1ADIANAAtADQAYgA0ADUALQA5ADQAMAAwAC0AZgBiADAAZgAyAGUANwA3AGUANQAxAGIAfQADCE4AYQBtAGUABDBUAHcAaQBuAEMAQQBUACAAMwAuADEAIABCAHUAaQBsAGQAIAA0ADAAMgA0AC4ANwAFFlAAcgBvAGYAaQBsAGUARABhAHQAYQAGTHsAMQA2AGUANQA1AGIANgAwAC0ANwAwADQAMwAtADQAYQA2ADMALQBiADYANQBiAC0ANgAxADQANwAxADMAOAA3ADgAZAA0ADIAfQAHEkwAaQBiAHIAYQByAGkAZQBzAAhMewAzAGIAZgBkADUANAA1ADkALQBiADAANwBmAC0ANABkADYAZQAtAGEAZQAxAGEALQBhADgAMwAzADUANgBhADUANQAxADQAMgB9AAlMewA5AGMAOQA1ADgAOQA2ADgALQAyAGMAOAA1AC0ANAAxAGIAYgAtADgAOAA3ADEALQA4ADkANQBmAGYAMQBmAGUAZABlADEAYQB9AAoOVgBlAHIAcwBpAG8AbgALBmkAbgB0AAwKVQBzAGEAZwBlAA0KVABpAHQAbABlAA4aVgBpAHMAdQBFAGwAZQBtAE0AZQB0AGUAcgAPDkMAbwBtAHAAYQBuAHkAEAxTAHkAcwB0AGUAbQARElYAaQBzAHUARQBsAGUAbQBzABIwVgBpAHMAdQBFAGwAZQBtAHMAUwBwAGUAYwBpAGEAbABDAG8AbgB0AHIAbwBsAHMAEyhWAGkAcwB1AEUAbABlAG0AcwBXAGkAbgBDAG8AbgB0AHIAbwBsAHMAFCRWAGkAcwB1AEUAbABlAG0AVABlAHgAdABFAGQAaQB0AG8AcgAVIlYAaQBzAHUATgBhAHQAaQB2AGUAQwBvAG4AdAByAG8AbAAWFHYAaQBzAHUAaQBuAHAAdQB0AHMAFwxzAHkAcwB0AGUAbQAYGFYAaQBzAHUARQBsAGUAbQBCAGEAcwBlABkmRABlAHYAUABsAGEAYwBlAGgAbwBsAGQAZQByAHMAVQBzAGUAZAAaCGIAbwBvAGwAGyJQAGwAdQBnAGkAbgBDAG8AbgBzAHQAcgBhAGkAbgB0AHMAHEx7ADQAMwBkADUAMgBiAGMAZQAtADkANAAyAGMALQA0ADQAZAA3AC0AOQBlADkANAAtADEAYgBmAGQAZgAzADEAMABlADYAMwBjAH0AHRxBAHQATABlAGEAcwB0AFYAZQByAHMAaQBvAG4AHhRQAGwAdQBnAGkAbgBHAHUAaQBkAB8WUwB5AHMAdABlAG0ALgBHAHUAaQBkACBIYQBmAGMAZAA1ADQANAA2AC0ANAA5ADEANAAtADQAZgBlADcALQBiAGIANwA4AC0AOQBiAGYAZgBlAGIANwAwAGYAZAAxADcAIRRVAHAAZABhAHQAZQBJAG4AZgBvACJMewBiADAAMwAzADYANgBhADgALQBiADUAYwAwAC0ANABiADkAYQAtAGEAMAAwAGUALQBlAGIAOAA2ADAAMQAxADEAMAA0AGMAMwB9ACMOVQBwAGQAYQB0AGUAcwAkTHsAMQA4ADYAOABmAGYAYwA5AC0AZQA0AGYAYwAtADQANQAzADIALQBhAGMAMAA2AC0AMQBlADMAOQBiAGIANQA1ADcAYgA2ADkAfQAlTHsAYQA1AGIAZAA0ADgAYwAzAC0AMABkADEANwAtADQAMQBiADUALQBiADEANgA0AC0ANQBmAGMANgBhAGQAMgBiADkANgBiADcAfQAmFk8AYgBqAGUAYwB0AHMAVAB5AHAAZQAnVFUAcABkAGEAdABlAEwAYQBuAGcAdQBhAGcAZQBNAG8AZABlAGwARgBvAHIAQwBvAG4AdgBlAHIAdABpAGIAbABlAEwAaQBiAHIAYQByAGkAZQBzACgQTABpAGIAVABpAHQAbABlACkUTABpAGIAQwBvAG0AcABhAG4AeQAqHlUAcABkAGEAdABlAFAAcgBvAHYAaQBkAGUAcgBzACs4UwB5AHMAdABlAG0ALgBDAG8AbABsAGUAYwB0AGkAbwBuAHMALgBIAGEAcwBoAHQAYQBiAGwAZQAsEnYAaQBzAHUAZQBsAGUAbQBzAC1INgBjAGIAMQBjAGQAZQAxAC0AZAA1AGQAYwAtADQAYQAzAGIALQA5ADAANQA0AC0AMgAxAGYAYQA3ADUANgBhADMAZgBhADQALihJAG4AdABlAHIAZgBhAGMAZQBWAGUAcgBzAGkAbwBuAEkAbgBmAG8AL0x7AGMANgAxADEAZQA0ADAAMAAtADcAZgBiADkALQA0AGMAMwA1AC0AYgA5AGEAYwAtADQAZQAzADEANABiADUAOQA5ADYANAAzAH0AMBhNAGEAagBvAHIAVgBlAHIAcwBpAG8AbgAxGE0AaQBuAG8AcgBWAGUAcgBzAGkAbwBuADIMTABlAGcAYQBjAHkAMzBMAGEAbgBnAHUAYQBnAGUATQBvAGQAZQBsAFYAZQByAHMAaQBvAG4ASQBuAGYAbwA0MEwAbwBhAGQATABpAGIAcgBhAHIAaQBlAHMASQBuAHQAbwBQAHIAbwBqAGUAYwB0ADUaQwBvAG0AcABhAHQAaQBiAGkAbABpAHQAeQDQAAIaA9ADAS0E0AUGGgfQBwgaAUUHCQjQAAkaBEUKCwQDAAAABQAAAA0AAAAAAAAA0AwLrQIAAADQDQEtDtAPAS0Q0AAJGgRFCgsEAwAAAAUAAAANAAAAKAAAANAMC60BAAAA0A0BLRHQDwEtENAACRoERQoLBAMAAAAFAAAADQAAAAAAAADQDAutAgAAANANAS0S0A8BLRDQAAkaBEUKCwQDAAAABQAAAA0AAAAUAAAA0AwLrQIAAADQDQEtE9APAS0Q0AAJGgRFCgsEAwAAAAUAAAANAAAAAAAAANAMC60CAAAA0A0BLRTQDwEtENAACRoERQoLBAMAAAAFAAAADQAAAAAAAADQDAutAgAAANANAS0V0A8BLRDQAAkaBEUKCwQDAAAABQAAAA0AAAAAAAAA0AwLrQIAAADQDQEtFtAPAS0X0AAJGgRFCgsEAwAAAAUAAAANAAAAKAAAANAMC60EAAAA0A0BLRjQDwEtENAZGq0BRRscAdAAHBoCRR0LBAMAAAAFAAAADQAAAAAAAADQHh8tINAhIhoCRSMkAtAAJRoFRQoLBAMAAAADAAAAAAAAAAoAAADQJgutAAAAANADAS0n0CgBLRHQKQEtENAAJRoFRQoLBAMAAAADAAAAAAAAAAoAAADQJgutAQAAANADAS0n0CgBLRHQKQEtEJoqKwFFAAEC0AABLSzQAAEtF9AAHy0t0C4vGgPQMAutAQAAANAxC60XAAAA0DIarQDQMy8aA9AwC60CAAAA0DELrQMAAADQMhqtANA0Gq0A0DUarQA= 174 | 175 | 176 | {192FAD59-8248-4824-A8DE-9177C94C195A} 177 | 178 | "{192FAD59-8248-4824-A8DE-9177C94C195A}" 179 | 180 | 181 | 182 | {F66C7017-BDD8-4114-926C-81D6D687E35F} 183 | 184 | "{F66C7017-BDD8-4114-926C-81D6D687E35F}" 185 | 186 | 187 | 188 | {246001F4-279D-43AC-B241-948EB31120E1} 189 | 190 | "{246001F4-279D-43AC-B241-948EB31120E1}" 191 | 192 | 193 | GlobalVisuImageFilePath 194 | %APPLICATIONPATH% 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | System.Collections.Hashtable 203 | {54dd0eac-a6d8-46f2-8c27-2f43c7e49861} 204 | System.String 205 | 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Utils/AnyToString.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 35 | 36 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Utils/DateTime.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 16 | 17 | 40 | 41 | 42 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 121 | 122 | Tc2_Standard.LEN(timeString) THEN 138 | FOR i:=1 TO length - Tc2_Standard.LEN(timeString) DO 139 | replaceString := Tc2_Standard.CONCAT(replaceString, '0'); 140 | END_FOR 141 | replaceString := Tc2_Standard.CONCAT(replaceString, timeString); 142 | ELSE 143 | replaceString := Tc2_Standard.RIGHT(timeString, length); 144 | END_IF 145 | 146 | ReplaceFormatPlaceholder := Tc2_Standard.REPLACE(format, replaceString, length, Tc2_Standard.FIND(format, timeType));]]> 147 | 148 | 149 | 150 | _20210812-12:30:22:123_ 162 | {attribute 'hide_all_locals'} 163 | METHOD ToFormatString : Tc2_System.T_MaxString 164 | VAR_INPUT 165 | /// Format the timestamp should have. 166 | format : STRING; 167 | END_VAR 168 | VAR_INST 169 | inputString : STRING; 170 | c : STRING(1); 171 | formattedString : Tc2_System.T_MaxString; 172 | END_VAR 173 | VAR 174 | year : INT := 0; 175 | month : INT := 0; 176 | day : INT := 0; 177 | hour : INT := 0; 178 | minute : INT := 0; 179 | second : INT := 0; 180 | millisecond : INT := 0; 181 | END_VAR 182 | VAR CONSTANT 183 | ValidFormat : STRING := 'YMDhms'; 184 | END_VAR 185 | ]]> 186 | 187 | 0 DO 191 | c := Tc2_Standard.LEFT(inputString, 1); 192 | 193 | IF c = 'Y' THEN year := year + 1; 194 | ELSIF c = 'M' THEN month := month + 1; 195 | ELSIF c = 'D' THEN day := day + 1; 196 | ELSIF c = 'h' THEN hour := hour + 1; 197 | ELSIF c = 'm' THEN minute := minute + 1; 198 | ELSIF c = 's' THEN second := second + 1; 199 | ELSIF c = 'i' THEN millisecond := millisecond + 1; 200 | END_IF 201 | 202 | inputString := Tc2_Standard.DELETE(inputString, 1, 1); 203 | END_WHILE 204 | 205 | formattedString := ReplaceFormatPlaceholder(format, year, 'Y'); 206 | formattedString := ReplaceFormatPlaceholder(formattedString, month, 'M'); 207 | formattedString := ReplaceFormatPlaceholder(formattedString, day, 'D'); 208 | formattedString := ReplaceFormatPlaceholder(formattedString, hour, 'h'); 209 | formattedString := ReplaceFormatPlaceholder(formattedString, minute, 'm'); 210 | formattedString := ReplaceFormatPlaceholder(formattedString, second, 's'); 211 | formattedString := ReplaceFormatPlaceholder(formattedString, millisecond, 'i'); 212 | 213 | ToFormatString := formattedString; 214 | 215 | 216 | ]]> 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Utils/DeleteOldFiles.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 35 | 36 | _enumFindFileList.nFindFiles THEN 72 | _state := DeleteFilesState.Idle; 73 | ELSE 74 | _state := DeleteFilesState.CheckIfFileIsDirectory; 75 | END_IF 76 | 77 | DeleteFilesState.CheckIfFileIsDirectory: 78 | IF NOT _listOfFiles[_deleteFileIndex].fileAttributes.bDirectory THEN 79 | _state := DeleteFilesState.CheckFileTime; 80 | ELSE 81 | _deleteFileIndex := _deleteFileIndex + 1; 82 | _state := DeleteFilesState.ProcessFoundFiles; 83 | END_IF 84 | 85 | DeleteFilesState.CheckFileTime: 86 | IF GetFileAgeInSeconds(CurrentUtcTime, _listOfFiles[_deleteFileIndex]) > (ExpirationInDays * OneDayInSeconds) THEN 87 | _state := DeleteFilesState.DeleteFile; 88 | ELSE 89 | _deleteFileIndex := _deleteFileIndex + 1; 90 | _state := DeleteFilesState.ProcessFoundFiles; 91 | END_IF 92 | 93 | DeleteFilesState.DeleteFile: 94 | _fileNameToDelete := Tc2_Standard.CONCAT(FilePath, _listOfFiles[_deleteFileIndex].sFileName); 95 | _startFileDelete := TRUE; 96 | 97 | IF _fileDeleteBusy.Q THEN 98 | _deleteFileIndex := _deleteFileIndex + 1; 99 | _fileDeleted := TRUE; 100 | _state := DeleteFilesState.FileDeleted; 101 | END_IF 102 | IF _fileDelete.bError THEN 103 | _state := DeleteFilesState.Idle; 104 | END_IF 105 | 106 | DeleteFilesState.FileDeleted: 107 | // This state is needed to generate a rising edge for FB_FileDelete 108 | IF NOT _fileDeleted THEN 109 | _state := DeleteFilesState.ProcessFoundFiles; 110 | END_IF 111 | _fileDeleted := FALSE; 112 | 113 | END_CASE 114 | 115 | // Function blocks used in state machine 116 | 117 | // Search files in given directory 118 | _enumFindFileList(sNetId := '', 119 | eCmd := _commandType, 120 | sPathName := Tc2_Standard.CONCAT(FilePath, Tc2_Standard.CONCAT('*', FileName)), 121 | bExecute := (_state = DeleteFilesState.SearchNextFile) OR (_commandType = eEnumCmd_Abort AND _state <> DeleteFilesState.Idle), 122 | pFindList := ADR(_listOfFiles), 123 | cbFindList := SIZEOF(_listOfFiles), 124 | tTimeout := T#10S); 125 | 126 | _enumBusy(CLK:=_enumFindFileList.bBusy); 127 | 128 | // Delete files 129 | _fileDelete(sNetId := '', 130 | sPathName := _fileNameToDelete, 131 | bExecute := _startFileDelete, 132 | tTimeout := T#10S); 133 | 134 | _fileDeleteBusy(CLK := _fileDelete.bBusy); 135 | 136 | // Error handling 137 | Error.Active := FALSE; 138 | Error.Code := ErrorCodes.None; 139 | Error.Info := ''; 140 | 141 | IF _enumFindFileList.bError THEN 142 | Error.Active := TRUE; 143 | Error.Code := ErrorCodes.EnumeratingFilesInSpecifiedDirectoryFailed; 144 | Error.Info := Tc2_Standard.CONCAT('Enumerating files in specified directory failed. Error thrown by FB_EnumFindFileList. Consult Beckhoff InfoSys. Internal Error: ', UDINT_TO_STRING(_enumFindFileList.nErrId)); 145 | END_IF 146 | 147 | IF _fileDelete.bError THEN 148 | Error.Active := TRUE; 149 | Error.Code := ErrorCodes.DeletingFileFailed; 150 | Error.Info := Tc2_Standard.CONCAT('Deleting expired log file in specified directory failed. Error thrown by FB_FileDelete. Consult Beckhoff InfoSys. Internal Error: ', UDINT_TO_STRING(_fileDelete.nErrId)); 151 | END_IF]]> 152 | 153 | 154 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Utils/DynamicStringBuffer.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | 29 | 30 | 31 | 32 | 37 | 38 | 40 | 41 | 42 | 43 | 48 | 49 | 52 | 53 | 54 | 55 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 78 | 79 | 80 | 81 | 82 | 83 | 85 | 86 | 87 | 88 | 89 | 90 | 95 | 96 | 97 | 98 | 99 | 100 | 131 | 132 | 0 AND enable THEN 141 | lockFileWriteToForceRisingEdge := FALSE; 142 | state := PersistToFileState.OpenFile; 143 | END_IF 144 | END_IF 145 | 146 | // *** O P E N F I L E *** 147 | fileOpen(sNetId := NetId, sPathName := fileName, nMode := FOPEN_MODEAPPEND OR FOPEN_MODETEXT, 148 | ePath := PATH_GENERIC, bExecute := (state = PersistToFileState.OpenFile), tTimeout := Timeout, 149 | hFile => fileHandle); 150 | 151 | IF state = PersistToFileState.OpenFile THEN 152 | IF fileOpen.bError THEN 153 | state := PersistToFileState.CloseFile; 154 | ELSIF NOT fileOpen.bBusy THEN 155 | state := PersistToFileState.GetStringFromFifo; 156 | END_IF 157 | END_IF 158 | 159 | // *** P O P F R O M F I F O *** 160 | IF state = PersistToFileState.GetStringFromFifo THEN 161 | IF NOT lockFileWriteToForceRisingEdge THEN 162 | currentBufferPos := 0; 163 | FOR i:=0 TO MaxLogsPerCycle DO 164 | IF _fifo.nCount <= 0 THEN EXIT; END_IF 165 | IF currentBufferPos >= MaxBufferPosition THEN EXIT; END_IF 166 | 167 | _fifo.A_RemoveHead(getValue => fifoHeadString); 168 | fifoHeadBytes := MAXSTRING_TO_BYTEARR(fifoHeadString); 169 | fifoHeadLength := INT_TO_UDINT(LEN(fifoHeadString)); 170 | MEMCPY( 171 | destAddr := ADR(buffer) + SIZEOF(BYTE) * currentBufferPos, 172 | srcAddr := ADR(fifoHeadBytes), 173 | n := fifoHeadLength); 174 | currentBufferPos := currentBufferPos + fifoHeadLength; 175 | END_FOR 176 | state := PersistToFileState.AppendDataToFile; 177 | END_IF 178 | lockFileWriteToForceRisingEdge := FALSE; 179 | END_IF 180 | 181 | // *** W R I T E T O F I L E *** 182 | fileWrite( 183 | sNetId := NetId, 184 | hFile := fileHandle, 185 | pWriteBuff := ADR(buffer), 186 | cbWriteLen := currentBufferPos, 187 | bExecute := state = PersistToFileState.AppendDataToFile, 188 | tTimeout := Timeout); 189 | 190 | IF state = PersistToFileState.AppendDataToFile THEN 191 | lockFileWriteToForceRisingEdge := TRUE; 192 | IF fileWrite.bError THEN 193 | state := PersistToFileState.CloseFile; 194 | ELSIF _fifo.nCount > 0 AND NOT fileWrite.bBusy THEN 195 | state := PersistToFileState.GetStringFromFifo; 196 | ELSIF _fifo.nCount = 0 THEN 197 | state := PersistToFileState.CloseFile; 198 | END_IF 199 | END_IF 200 | 201 | // *** C L O S E F I L E *** 202 | fileClose( 203 | sNetId := NetId, hFile := fileHandle, 204 | bExecute := (state = PersistToFileState.CloseFile), tTimeout := Timeout); 205 | 206 | IF state = PersistToFileState.CloseFile THEN 207 | IF NOT fileClose.bBusy THEN state := PersistToFileState.Idle; END_IF 208 | END_IF 209 | 210 | // Close file in case of error 211 | IF fileOpen.bError THEN state := PersistToFileState.CloseFile; END_IF 212 | IF fileWrite.bError THEN state := PersistToFileState.CloseFile; END_IF 213 | IF NOT _fifo.bOk AND _fifo.nCount > 0 AND state <> PersistToFileState.Idle THEN 214 | state := PersistToFileState.CloseFile; 215 | END_IF 216 | 217 | // Error handling 218 | IF fileOpen.bError THEN 219 | Error.Active := TRUE; 220 | Error.Code := ErrorCodes.OpeningFileFailed; 221 | Error.Info := Tc2_Standard.CONCAT('Opening file failed. Error thrown by FB_FileOpen. Consult Beckhoff InfoSys. Internal Error: ', UDINT_TO_STRING(fileOpen.nErrId)); 222 | ELSIF fileWrite.bError THEN 223 | Error.Active := TRUE; 224 | Error.Code := ErrorCodes.WritingToFileFailed; 225 | Error.Info := Tc2_Standard.CONCAT('Writing to file failed. Error thrown by FB_FileWrite. Consult Beckhoff InfoSys. Internal Error: ', UDINT_TO_STRING(fileWrite.nErrId)); 226 | ELSIF fileClose.bError THEN 227 | Error.Active := TRUE; 228 | Error.Code := ErrorCodes.ClosingFileFailed; 229 | Error.Info := Tc2_Standard.CONCAT('Closing file failed. Error thrown by FB_FileClose. Consult Beckhoff InfoSys. Internal Error: ', UDINT_TO_STRING(fileClose.nErrId)); 230 | ELSIF NOT _fifo.bOk AND _fifo.nCount > 0 THEN 231 | Error.Active := TRUE; 232 | Error.Code := ErrorCodes.FifoOverflow; 233 | Error.Info := 'FIFO overflow. Check if plc has enough free memory.'; 234 | ELSE 235 | Error.Active := FALSE; 236 | Error.Code := ErrorCodes.None; 237 | Error.Info := ''; 238 | END_IF 239 | 240 | // Set busy state 241 | Busy := (state <> PersistToFileState.Idle OR _fifo.nCount > 0); 242 | 243 | (* Uncomment for cyclic logging of state machine, buffer usage & errors 244 | 245 | ADSLOGSTR(ADSLOG_MSGTYPE_LOG, 246 | Tc2_Standard.CONCAT('****DynamicStringBuffer.state:', 247 | Tc2_Standard.CONCAT(UINT_TO_STRING(state), 248 | Tc2_Standard.CONCAT(', DynamicStringBuffer.hFile:', UINT_TO_STRING(hFile)))), ''); 249 | 250 | ADSLOGSTR(ADSLOG_MSGTYPE_LOG, 251 | Tc2_Standard.CONCAT('****Fifo: used ', 252 | Tc2_Standard.CONCAT(UDINT_TO_STRING(Fifo.cbSize), 253 | Tc2_Standard.CONCAT(' of ', UDINT_TO_STRING(Fifo.cbBuffer)))), ''); 254 | 255 | IF Error.Active THEN 256 | ADSLOGSTR(ADSLOG_MSGTYPE_LOG, 257 | Tc2_Standard.CONCAT('****DynamicStringBuffer.Error: ', Error.Info), 258 | ''); 259 | END_IF 260 | *)]]> 261 | 262 | 263 | 264 | 269 | 270 | headString); 271 | PopHead := headString;]]> 272 | 273 | 274 | 275 | 282 | 283 | _fifo.cbBuffer THEN 286 | _dynamicMemoryBuffer.Resize(nSize := requiredSize*SIZEOF(BYTE), bPreserve:=TRUE, bReset:=TRUE); 287 | _fifo.pBuffer := _dynamicMemoryBuffer.pBuffer; 288 | _fifo.cbBuffer := requiredSize; 289 | END_IF]]> 290 | 291 | 292 | 293 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Utils/GenerateTimeData.TcPOU.bak: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 23 | 24 | 48 | 49 | 50 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 74 | 75 | LEN(timeString) THEN 89 | FOR i:=1 TO Length - LEN(timeString) DO 90 | replaceString := Tc2_Standard.CONCAT(replaceString, '0'); 91 | END_FOR 92 | replaceString := Tc2_Standard.CONCAT(replaceString, timeString); 93 | ELSE 94 | replaceString := RIGHT(timeString, Length); 95 | END_IF 96 | 97 | ReplaceFormatPlaceholder := REPLACE(Format, replaceString, Length, FIND(Format, TimeType));]]> 98 | 99 | 100 | 101 | _20210812-12:30:22:123_ 112 | METHOD ToString : Tc2_System.T_MaxString 113 | VAR_INPUT 114 | Format : STRING; 115 | END_VAR 116 | VAR_INST 117 | inputString : STRING; 118 | char : STRING(1); 119 | formattedString : Tc2_System.T_MaxString; 120 | END_VAR 121 | VAR 122 | year : INT := 0; 123 | month : INT := 0; 124 | day : INT := 0; 125 | hour : INT := 0; 126 | minute : INT := 0; 127 | second : INT := 0; 128 | millisecond : INT := 0; 129 | END_VAR 130 | VAR CONSTANT 131 | VALID_FORMAT : STRING := 'YMDhms'; 132 | END_VAR 133 | ]]> 134 | 135 | 0 DO 139 | char := LEFT(inputString, 1); 140 | 141 | IF char = 'Y' THEN year := year + 1; 142 | ELSIF char = 'M' THEN month := month + 1; 143 | ELSIF char = 'D' THEN day := day + 1; 144 | ELSIF char = 'h' THEN hour := hour + 1; 145 | ELSIF char = 'm' THEN minute := minute + 1; 146 | ELSIF char = 's' THEN second := second + 1; 147 | ELSIF char = 'i' THEN millisecond := millisecond + 1; 148 | END_IF 149 | 150 | inputString := DELETE(inputString, 1, 1); 151 | END_WHILE 152 | 153 | formattedString := ReplaceFormatPlaceholder(Format, year, 'Y'); 154 | formattedString := ReplaceFormatPlaceholder(formattedString, month, 'M'); 155 | formattedString := ReplaceFormatPlaceholder(formattedString, day, 'D'); 156 | formattedString := ReplaceFormatPlaceholder(formattedString, hour, 'h'); 157 | formattedString := ReplaceFormatPlaceholder(formattedString, minute, 'm'); 158 | formattedString := ReplaceFormatPlaceholder(formattedString, second, 's'); 159 | formattedString := ReplaceFormatPlaceholder(formattedString, millisecond, 'i'); 160 | 161 | ToString := formattedString; 162 | 163 | 164 | ]]> 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Utils/GetFileAgeInSeconds.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 19 | 20 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Utils/_DataTypes/DeleteFilesState.TcDUT: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 20 | 21 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Utils/_DataTypes/PersistToFileState.TcDUT: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Utils/_Tests/ConvertAnyToString_TEST.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 6 | 7 | 27 | 28 | 29 | 37 | 38 | 48 | 49 | 50 | 51 | 58 | 59 | 68 | 69 | 70 | 71 | 78 | 79 | 88 | 89 | 90 | 91 | 99 | 100 | 110 | 111 | 112 | 113 | 120 | 121 | 130 | 131 | 132 | 133 | 141 | 142 | 152 | 153 | 154 | 155 | 163 | 164 | 174 | 175 | 176 | 177 | 185 | 186 | 196 | 197 | 198 | 199 | 207 | 208 | 218 | 219 | 220 | 221 | 229 | 230 | 240 | 241 | 242 | 243 | 251 | 252 | 262 | 263 | 264 | 265 | 273 | 274 | 284 | 285 | 286 | 287 | 294 | 295 | 304 | 305 | 306 | 307 | 314 | 315 | 324 | 325 | 326 | 327 | 334 | 335 | 344 | 345 | 346 | 347 | 355 | 356 | 366 | 367 | 368 | 369 | 377 | 378 | 388 | 389 | 390 | 391 | 399 | 400 | 410 | 411 | 412 | 413 | 421 | 422 | 432 | 433 | 434 | 435 | 443 | 444 | 454 | 455 | 456 | 457 | 464 | 465 | 474 | 475 | 476 | 477 | 483 | 484 | 485 | 486 | 487 | 488 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Utils/_Tests/DateTime_TEST.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 20 | 21 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLog/Utils/_Tests/DynamicStringBuffer_TEST.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 26 | 27 | 0 THEN 48 | result := logsManyMessagesForManyCyclesLogger.Error.Info; 49 | END_IF 50 | 51 | IF cycleCount > (Cycles * Messages * 2 + 4) THEN 52 | AssertEquals(Expected := expected, 53 | Actual := result, 54 | Message := result); 55 | 56 | TEST_FINISHED(); 57 | END_IF]]> 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLogProj.tsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | PlcTask1 11 | 12 | 13 | 14 | 15 | 16 | 17 | TcLog Instance 18 | {08500001-0000-0000-F000-000000000064} 19 | 20 | 21 | 1 22 | Default 23 | 24 | 25 | 26 | 27 | 28 | 29 | TcLogTEST Instance 30 | {08500001-0000-0000-F000-000000000064} 31 | 32 | 33 | 0 34 | PlcTask 35 | 36 | #x02010040 37 | 38 | 21 39 | 10000000 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLogTEST/PlcTask.TcTTO: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 10000 6 | 21 7 | 8 | TESTS 9 | 10 | {8c69b6b0-46d1-4425-bb87-a907c774dc33} 11 | {4c28a96c-efe9-4f21-a0c4-de877f1be764} 12 | {3bfdb96d-f68b-400e-8a1d-b0e47fdbf9ff} 13 | {2307b5ee-67e0-4083-b767-e9782c3843a0} 14 | {749f2c86-938a-4721-846a-b2cbd4d2332d} 15 | 16 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLogTEST/TESTS.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 12 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/TcLogProj/TcLogTEST/TcLogTEST.plcproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0.0.0 4 | 2.0 5 | {39e514bf-8337-4e82-99ca-d7d99beee7fe} 6 | True 7 | true 8 | true 9 | false 10 | TcLogTEST 11 | 3.1.4026.12 12 | {8146f613-9e7d-40c5-8b31-c45750c652a0} 13 | {fbd87f5e-6357-460c-8b4e-eca3de6b406a} 14 | {1f1d2e22-83eb-4b46-b927-71cab86c9b53} 15 | {318c4518-ff6d-4f11-9dc5-2c14ca6d4e2f} 16 | {18d37d59-7d3c-4061-9c13-6219439268ab} 17 | {83fa6c17-9059-4e97-8066-14e4551621aa} 18 | 19 | 20 | 21 | Code 22 | 23 | 24 | Code 25 | 26 | 27 | 28 | 29 | Tc2_Standard, * (Beckhoff Automation GmbH) 30 | Tc2_Standard 31 | 32 | 33 | Tc2_System, * (Beckhoff Automation GmbH) 34 | Tc2_System 35 | 36 | 37 | Tc3_Module, * (Beckhoff Automation GmbH) 38 | Tc3_Module 39 | 40 | 41 | TcLog, * (Benedikt Geisler) 42 | 43 | 44 | TcUnit, * (www.tcunit.org) 45 | TcUnit 46 | 47 | 48 | 49 | 50 | Content 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | "<ProjectRoot>" 59 | 60 | {192FAD59-8248-4824-A8DE-9177C94C195A} 61 | 62 | "{192FAD59-8248-4824-A8DE-9177C94C195A}" 63 | 64 | 65 | 66 | {246001F4-279D-43AC-B241-948EB31120E1} 67 | 68 | "{246001F4-279D-43AC-B241-948EB31120E1}" 69 | 70 | 71 | GlobalVisuImageFilePath 72 | %APPLICATIONPATH% 73 | 74 | 75 | {29BD8D0C-3586-4548-BB48-497B9A01693F} 76 | 77 | "{29BD8D0C-3586-4548-BB48-497B9A01693F}" 78 | 79 | Rules 80 | 81 | "Rules" 82 | 83 | 84 | 85 | 86 | 87 | 88 | {40450F57-0AA3-4216-96F3-5444ECB29763} 89 | 90 | "{40450F57-0AA3-4216-96F3-5444ECB29763}" 91 | 92 | 93 | ActiveVisuProfile 94 | IR0whWr8bwfwBwAAiD2qpQAAAABVAgAA37x72QAAAAABAAAAAAAAAAEaUwB5AHMAdABlAG0ALgBTAHQAcgBpAG4AZwACTHsAZgA5ADUAYgBiADQAMgA2AC0ANQA1ADIANAAtADQAYgA0ADUALQA5ADQAMAAwAC0AZgBiADAAZgAyAGUANwA3AGUANQAxAGIAfQADCE4AYQBtAGUABDBUAHcAaQBuAEMAQQBUACAAMwAuADEAIABCAHUAaQBsAGQAIAA0ADAAMgA0AC4ANwAFFlAAcgBvAGYAaQBsAGUARABhAHQAYQAGTHsAMQA2AGUANQA1AGIANgAwAC0ANwAwADQAMwAtADQAYQA2ADMALQBiADYANQBiAC0ANgAxADQANwAxADMAOAA3ADgAZAA0ADIAfQAHEkwAaQBiAHIAYQByAGkAZQBzAAhMewAzAGIAZgBkADUANAA1ADkALQBiADAANwBmAC0ANABkADYAZQAtAGEAZQAxAGEALQBhADgAMwAzADUANgBhADUANQAxADQAMgB9AAlMewA5AGMAOQA1ADgAOQA2ADgALQAyAGMAOAA1AC0ANAAxAGIAYgAtADgAOAA3ADEALQA4ADkANQBmAGYAMQBmAGUAZABlADEAYQB9AAoOVgBlAHIAcwBpAG8AbgALBmkAbgB0AAwKVQBzAGEAZwBlAA0KVABpAHQAbABlAA4aVgBpAHMAdQBFAGwAZQBtAE0AZQB0AGUAcgAPDkMAbwBtAHAAYQBuAHkAEAxTAHkAcwB0AGUAbQARElYAaQBzAHUARQBsAGUAbQBzABIwVgBpAHMAdQBFAGwAZQBtAHMAUwBwAGUAYwBpAGEAbABDAG8AbgB0AHIAbwBsAHMAEyhWAGkAcwB1AEUAbABlAG0AcwBXAGkAbgBDAG8AbgB0AHIAbwBsAHMAFCRWAGkAcwB1AEUAbABlAG0AVABlAHgAdABFAGQAaQB0AG8AcgAVIlYAaQBzAHUATgBhAHQAaQB2AGUAQwBvAG4AdAByAG8AbAAWFHYAaQBzAHUAaQBuAHAAdQB0AHMAFwxzAHkAcwB0AGUAbQAYGFYAaQBzAHUARQBsAGUAbQBCAGEAcwBlABkmRABlAHYAUABsAGEAYwBlAGgAbwBsAGQAZQByAHMAVQBzAGUAZAAaCGIAbwBvAGwAGyJQAGwAdQBnAGkAbgBDAG8AbgBzAHQAcgBhAGkAbgB0AHMAHEx7ADQAMwBkADUAMgBiAGMAZQAtADkANAAyAGMALQA0ADQAZAA3AC0AOQBlADkANAAtADEAYgBmAGQAZgAzADEAMABlADYAMwBjAH0AHRxBAHQATABlAGEAcwB0AFYAZQByAHMAaQBvAG4AHhRQAGwAdQBnAGkAbgBHAHUAaQBkAB8WUwB5AHMAdABlAG0ALgBHAHUAaQBkACBIYQBmAGMAZAA1ADQANAA2AC0ANAA5ADEANAAtADQAZgBlADcALQBiAGIANwA4AC0AOQBiAGYAZgBlAGIANwAwAGYAZAAxADcAIRRVAHAAZABhAHQAZQBJAG4AZgBvACJMewBiADAAMwAzADYANgBhADgALQBiADUAYwAwAC0ANABiADkAYQAtAGEAMAAwAGUALQBlAGIAOAA2ADAAMQAxADEAMAA0AGMAMwB9ACMOVQBwAGQAYQB0AGUAcwAkTHsAMQA4ADYAOABmAGYAYwA5AC0AZQA0AGYAYwAtADQANQAzADIALQBhAGMAMAA2AC0AMQBlADMAOQBiAGIANQA1ADcAYgA2ADkAfQAlTHsAYQA1AGIAZAA0ADgAYwAzAC0AMABkADEANwAtADQAMQBiADUALQBiADEANgA0AC0ANQBmAGMANgBhAGQAMgBiADkANgBiADcAfQAmFk8AYgBqAGUAYwB0AHMAVAB5AHAAZQAnVFUAcABkAGEAdABlAEwAYQBuAGcAdQBhAGcAZQBNAG8AZABlAGwARgBvAHIAQwBvAG4AdgBlAHIAdABpAGIAbABlAEwAaQBiAHIAYQByAGkAZQBzACgQTABpAGIAVABpAHQAbABlACkUTABpAGIAQwBvAG0AcABhAG4AeQAqHlUAcABkAGEAdABlAFAAcgBvAHYAaQBkAGUAcgBzACs4UwB5AHMAdABlAG0ALgBDAG8AbABsAGUAYwB0AGkAbwBuAHMALgBIAGEAcwBoAHQAYQBiAGwAZQAsEnYAaQBzAHUAZQBsAGUAbQBzAC1INgBjAGIAMQBjAGQAZQAxAC0AZAA1AGQAYwAtADQAYQAzAGIALQA5ADAANQA0AC0AMgAxAGYAYQA3ADUANgBhADMAZgBhADQALihJAG4AdABlAHIAZgBhAGMAZQBWAGUAcgBzAGkAbwBuAEkAbgBmAG8AL0x7AGMANgAxADEAZQA0ADAAMAAtADcAZgBiADkALQA0AGMAMwA1AC0AYgA5AGEAYwAtADQAZQAzADEANABiADUAOQA5ADYANAAzAH0AMBhNAGEAagBvAHIAVgBlAHIAcwBpAG8AbgAxGE0AaQBuAG8AcgBWAGUAcgBzAGkAbwBuADIMTABlAGcAYQBjAHkAMzBMAGEAbgBnAHUAYQBnAGUATQBvAGQAZQBsAFYAZQByAHMAaQBvAG4ASQBuAGYAbwA0MEwAbwBhAGQATABpAGIAcgBhAHIAaQBlAHMASQBuAHQAbwBQAHIAbwBqAGUAYwB0ADUaQwBvAG0AcABhAHQAaQBiAGkAbABpAHQAeQDQAAIaA9ADAS0E0AUGGgfQBwgaAUUHCQjQAAkaBEUKCwQDAAAABQAAAA0AAAAAAAAA0AwLrQIAAADQDQEtDtAPAS0Q0AAJGgRFCgsEAwAAAAUAAAANAAAAKAAAANAMC60BAAAA0A0BLRHQDwEtENAACRoERQoLBAMAAAAFAAAADQAAAAAAAADQDAutAgAAANANAS0S0A8BLRDQAAkaBEUKCwQDAAAABQAAAA0AAAAUAAAA0AwLrQIAAADQDQEtE9APAS0Q0AAJGgRFCgsEAwAAAAUAAAANAAAAAAAAANAMC60CAAAA0A0BLRTQDwEtENAACRoERQoLBAMAAAAFAAAADQAAAAAAAADQDAutAgAAANANAS0V0A8BLRDQAAkaBEUKCwQDAAAABQAAAA0AAAAAAAAA0AwLrQIAAADQDQEtFtAPAS0X0AAJGgRFCgsEAwAAAAUAAAANAAAAKAAAANAMC60EAAAA0A0BLRjQDwEtENAZGq0BRRscAdAAHBoCRR0LBAMAAAAFAAAADQAAAAAAAADQHh8tINAhIhoCRSMkAtAAJRoFRQoLBAMAAAADAAAAAAAAAAoAAADQJgutAAAAANADAS0n0CgBLRHQKQEtENAAJRoFRQoLBAMAAAADAAAAAAAAAAoAAADQJgutAQAAANADAS0n0CgBLRHQKQEtEJoqKwFFAAEC0AABLSzQAAEtF9AAHy0t0C4vGgPQMAutAQAAANAxC60XAAAA0DIarQDQMy8aA9AwC60CAAAA0DELrQMAAADQMhqtANA0Gq0A0DUarQA= 95 | 96 | 97 | {8A0FB252-96EB-4DCC-A5B4-B4804D05E2D6} 98 | 99 | "{8A0FB252-96EB-4DCC-A5B4-B4804D05E2D6}" 100 | 101 | 102 | WriteLineIDs 103 | False 104 | 105 | 106 | {8F99A816-E488-41E4-9FA3-846536012284} 107 | 108 | "{8F99A816-E488-41E4-9FA3-846536012284}" 109 | 110 | 111 | DisabledWarningIds 112 | 5410 113 | 114 | 115 | {F66C7017-BDD8-4114-926C-81D6D687E35F} 116 | 117 | "{F66C7017-BDD8-4114-926C-81D6D687E35F}" 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | System.Boolean 127 | System.Collections.Hashtable 128 | {54dd0eac-a6d8-46f2-8c27-2f43c7e49861} 129 | System.String 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /src/TcLogTest.NET/PersistingToFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Xunit; 4 | using System.IO; 5 | using System.Threading; 6 | 7 | namespace TcLogTest.NET 8 | { 9 | [Collection("Plc collection")] 10 | public class PersistingToFileSystem : IDisposable 11 | { 12 | PlcFixture fixture; 13 | readonly string mut = "TESTS.TestWrapper"; 14 | readonly string path = "C:\\UnitTest\\"; 15 | readonly string filename = "UnitTest.txt"; 16 | readonly uint hPath; 17 | readonly uint hFileName; 18 | 19 | readonly uint hSystemTime; 20 | readonly uint hSetSystemTime; 21 | readonly uint hTime; 22 | 23 | public PersistingToFileSystem(PlcFixture fixture) 24 | { 25 | this.fixture = fixture; 26 | 27 | hPath = fixture.TcClient.CreateVariableHandle(mut + ".FilePath"); 28 | hFileName = fixture.TcClient.CreateVariableHandle(mut + ".FileName"); 29 | fixture.TcClient.WriteAny(hPath, path); 30 | fixture.TcClient.WriteAny(hFileName, filename); 31 | 32 | Directory.CreateDirectory(path); 33 | var files = Directory.GetFiles(path); 34 | foreach (var f in files) File.Delete(f); 35 | 36 | hSystemTime = fixture.TcClient.CreateVariableHandle(mut + ".NewLocalSystemTimeToBeSet"); 37 | hSetSystemTime = fixture.TcClient.CreateVariableHandle(mut + ".TriggerNewLocalSystemTime"); 38 | hTime = fixture.TcClient.CreateVariableHandle(mut + ".LocalTimeAsString"); 39 | } 40 | 41 | public void Dispose() 42 | { 43 | fixture.TcClient.DeleteVariableHandle(hPath); 44 | fixture.TcClient.DeleteVariableHandle(hFileName); 45 | fixture.TcClient.DeleteVariableHandle(hSystemTime); 46 | fixture.TcClient.DeleteVariableHandle(hSetSystemTime); 47 | fixture.TcClient.DeleteVariableHandle(hTime); 48 | } 49 | 50 | private async Task SetTime(string setTime, string testTime) 51 | { 52 | fixture.TcClient.WriteAny(hSetSystemTime, false); 53 | fixture.TcClient.WriteAny(hSystemTime, setTime); 54 | fixture.TcClient.WriteAny(hSetSystemTime, true); 55 | while (fixture.TcClient.ReadAnyString(hTime, 80, System.Text.Encoding.ASCII).Contains(testTime) != true) 56 | await Task.Delay(200); 57 | fixture.TcClient.WriteAny(hSetSystemTime, false); 58 | } 59 | 60 | [Fact] 61 | public async void Persist_simple_error_message() 62 | { 63 | string message = "Simple message"; 64 | uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".Persist_simple_error_message_run"); 65 | uint hData = fixture.TcClient.CreateVariableHandle(mut + ".Persist_simple_error_message_data"); 66 | 67 | fixture.TcClient.WriteAny(hData, message); 68 | fixture.TcClient.WriteAny(hRun, true); 69 | await Task.Delay(1000); 70 | var files = Directory.GetFiles(path); 71 | var fileContent = File.ReadAllLines(files[0]); 72 | 73 | Assert.Contains(message, fileContent[0]); 74 | Assert.Contains("Error", fileContent[0]); 75 | 76 | foreach (var f in files) File.Delete(f); 77 | fixture.TcClient.DeleteVariableHandle(hRun); 78 | fixture.TcClient.DeleteVariableHandle(hData); 79 | } 80 | 81 | [Fact] 82 | public async void Persist_long_error_message() 83 | { 84 | string message = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata"; 85 | uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".Persist_long_error_message_run"); 86 | uint hData = fixture.TcClient.CreateVariableHandle(mut + ".Persist_long_error_message_data"); 87 | 88 | fixture.TcClient.WriteAny(hData, message); 89 | fixture.TcClient.WriteAny(hRun, true); 90 | await Task.Delay(1000); 91 | var files = Directory.GetFiles(path); 92 | var fileContent = File.ReadAllText(files[0]); 93 | 94 | Assert.Equal($"{message[..254]}\r\n", fileContent); 95 | 96 | foreach (var f in files) File.Delete(f); 97 | fixture.TcClient.DeleteVariableHandle(hRun); 98 | fixture.TcClient.DeleteVariableHandle(hData); 99 | } 100 | 101 | [Fact] 102 | public async void Linebreak_is_included_when_log_message_has_maximum_length() 103 | { 104 | string message = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata"; 105 | uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".Linebreak_is_included_when_log_message_has_maximum_length_run"); 106 | uint hData = fixture.TcClient.CreateVariableHandle(mut + ".Linebreak_is_included_when_log_message_has_maximum_length_data"); 107 | 108 | fixture.TcClient.WriteAny(hData, message); 109 | fixture.TcClient.WriteAny(hRun, true); 110 | await Task.Delay(1000); 111 | var files = Directory.GetFiles(path); 112 | var fileContent = File.ReadAllText(files[0]); 113 | 114 | Assert.Equal($"{message[..254]}\r\n{message[..254]}\r\n", fileContent); 115 | 116 | foreach (var f in files) File.Delete(f); 117 | fixture.TcClient.DeleteVariableHandle(hRun); 118 | fixture.TcClient.DeleteVariableHandle(hData); 119 | } 120 | 121 | [Fact] 122 | public async void Do_not_persist_logs_below_log_level() 123 | { 124 | uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".Do_not_persist_logs_below_log_level_run"); 125 | 126 | fixture.TcClient.WriteAny(hRun, true); 127 | await Task.Delay(1000); 128 | var files = Directory.GetFiles(path); 129 | 130 | Assert.Empty(files); 131 | 132 | fixture.TcClient.DeleteVariableHandle(hRun); 133 | } 134 | 135 | [Fact] 136 | public async void Log_message_contains_instance_path() 137 | { 138 | uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".Log_message_contains_instance_path_run"); 139 | 140 | fixture.TcClient.WriteAny(hRun, true); 141 | await Task.Delay(1000); 142 | var files = Directory.GetFiles(path); 143 | var fileContent = File.ReadAllLines(files[0]); 144 | 145 | Assert.Contains(mut, fileContent[0]); 146 | 147 | foreach (var f in files) File.Delete(f); 148 | fixture.TcClient.DeleteVariableHandle(hRun); 149 | } 150 | 151 | [Fact] 152 | public async void Log_message_uses_correct_delimiter() 153 | { 154 | string message = "Simple message"; 155 | string delimiter = ";"; 156 | uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".Log_message_uses_correct_delimiter_run"); 157 | uint hData = fixture.TcClient.CreateVariableHandle(mut + ".Log_message_uses_correct_delimiter_data"); 158 | uint hDelimiter = fixture.TcClient.CreateVariableHandle(mut + ".delimiter"); 159 | 160 | fixture.TcClient.WriteAny(hDelimiter, delimiter); 161 | fixture.TcClient.WriteAny(hData, message); 162 | fixture.TcClient.WriteAny(hRun, true); 163 | await Task.Delay(1000); 164 | var files = Directory.GetFiles(path); 165 | var fileContent = File.ReadAllLines(files[0]); 166 | 167 | Assert.Contains(delimiter + message, fileContent[0]); 168 | 169 | delimiter = ""; 170 | fixture.TcClient.WriteAny(hDelimiter, delimiter); 171 | foreach (var f in files) File.Delete(f); 172 | fixture.TcClient.DeleteVariableHandle(hRun); 173 | fixture.TcClient.DeleteVariableHandle(hData); 174 | fixture.TcClient.DeleteVariableHandle(hDelimiter); 175 | } 176 | 177 | [Fact] 178 | public async void Log_message_contains_custom_formatted_timestamp() 179 | { 180 | uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".Log_message_contains_custom_formatted_timestamp_run"); 181 | fixture.TcClient.WriteAny(hRun, false); 182 | 183 | await SetTime("2021-08-17-22:05:01.300", "2021-08-17-22:05:"); 184 | fixture.TcClient.WriteAny(hRun, true); 185 | await Task.Delay(1000); 186 | var files = Directory.GetFiles(path); 187 | var fileContent = File.ReadAllLines(files[0]); 188 | 189 | Assert.Contains("2021-08-17-22:05:", fileContent[0]); 190 | 191 | foreach (var f in files) File.Delete(f); 192 | fixture.TcClient.DeleteVariableHandle(hRun); 193 | } 194 | 195 | [Fact] 196 | public async void Delete_logs_if_expired() 197 | { 198 | uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".Delete_logs_if_expired_run"); 199 | fixture.TcClient.WriteAny(hRun, false); 200 | var files = Directory.GetFiles(path); 201 | foreach (var f in files) File.Delete(f); 202 | 203 | await SetTime("2021-08-17-19:59:58.300", "2021-08-17-19:59:"); 204 | await Task.Delay(3000); 205 | fixture.TcClient.WriteAny(hRun, true); 206 | await Task.Delay(6000); 207 | 208 | await SetTime("2021-08-17-20:59:58.300", "2021-08-17-20:59:"); 209 | await Task.Delay(3000); 210 | fixture.TcClient.WriteAny(hRun, true); 211 | await Task.Delay(6000); 212 | 213 | await SetTime("2021-08-17-21:59:58.300", "2021-08-17-21:59:"); 214 | await Task.Delay(3000); 215 | fixture.TcClient.WriteAny(hRun, true); 216 | await Task.Delay(6000); 217 | 218 | await SetTime("2021-08-19-23:59:58.100", "2021-08-19-23:59:"); 219 | await Task.Delay(4000); 220 | files = Directory.GetFiles(path); 221 | 222 | Assert.Empty(files); 223 | 224 | fixture.TcClient.DeleteVariableHandle(hRun); 225 | } 226 | 227 | [Fact] 228 | public async void New_logfile_is_created_if_rolling_interval_rolls() 229 | { 230 | uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".New_logfile_is_created_if_rolling_interval_rolls_run"); 231 | fixture.TcClient.WriteAny(hRun, false); 232 | 233 | await SetTime("2021-08-17-20:05:01.300", "2021-08-17-20:05:"); 234 | fixture.TcClient.WriteAny(hRun, true); 235 | await Task.Delay(1000); 236 | await SetTime("2021-08-17-21:59:58.300", "2021-08-17-21:59:"); 237 | await Task.Delay(3000); 238 | fixture.TcClient.WriteAny(hRun, true); 239 | await Task.Delay(3000); 240 | var files = Directory.GetFiles(path); 241 | 242 | Assert.True(files.Length == 2); 243 | 244 | foreach (var f in files) File.Delete(f); 245 | fixture.TcClient.DeleteVariableHandle(hRun); 246 | } 247 | 248 | [Fact] 249 | public async void Same_log_file_is_used_until_rolling_interval_rolls() 250 | { 251 | uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".Same_log_file_is_used_until_rolling_interval_rolls"); 252 | fixture.TcClient.WriteAny(hRun, false); 253 | 254 | await SetTime("2021-08-17-22:05:01.300", "2021-08-17-22:05:"); 255 | fixture.TcClient.WriteAny(hRun, true); 256 | await Task.Delay(1000); 257 | fixture.TcClient.WriteAny(hRun, false); 258 | await Task.Delay(1000); 259 | fixture.TcClient.WriteAny(hRun, true); 260 | await Task.Delay(1000); 261 | var files = Directory.GetFiles(path); 262 | 263 | Assert.True(files.Length == 1); 264 | 265 | foreach (var f in files) File.Delete(f); 266 | fixture.TcClient.DeleteVariableHandle(hRun); 267 | } 268 | 269 | [Theory] 270 | [InlineData(1, 1000)] 271 | [InlineData(5, 1000)] 272 | [InlineData(10, 1000)] 273 | [InlineData(20, 2000)] 274 | [InlineData(100, 5000)] 275 | public async void Log_in_consecutive_cycles(int cycleCount, int waitTime) 276 | { 277 | uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".Log_in_consecutive_cycles"); 278 | uint hCycles = fixture.TcClient.CreateVariableHandle(mut + ".Number_of_log_cycles"); 279 | 280 | fixture.TcClient.WriteAny(hCycles, cycleCount); 281 | fixture.TcClient.WriteAny(hRun, true); 282 | await Task.Delay(waitTime); 283 | var files = Directory.GetFiles(path); 284 | 285 | var fileContent = File.ReadAllLines(files[0]); 286 | 287 | Assert.Equal(cycleCount, fileContent.Length); 288 | 289 | fixture.TcClient.WriteAny(hCycles, 0); 290 | foreach (var f in files) File.Delete(f); 291 | fixture.TcClient.DeleteVariableHandle(hRun); 292 | fixture.TcClient.DeleteVariableHandle(hCycles); 293 | } 294 | 295 | [Theory] 296 | [InlineData(1, 1000)] 297 | [InlineData(5, 1000)] 298 | [InlineData(10, 1000)] 299 | [InlineData(20, 2000)] 300 | [InlineData(100, 5000)] 301 | public async void Log_multiple_times_in_one_cycle(int logCount, int waitTime) 302 | { 303 | uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".Log_multiple_logs_in_one_cycle"); 304 | uint hLogCount = fixture.TcClient.CreateVariableHandle(mut + ".Number_of_logs_per_cycle"); 305 | 306 | fixture.TcClient.WriteAny(hLogCount, logCount); 307 | fixture.TcClient.WriteAny(hRun, true); 308 | await Task.Delay(waitTime); 309 | var files = Directory.GetFiles(path); 310 | 311 | var fileContent = File.ReadAllLines(files[0]); 312 | 313 | Assert.Equal(logCount, fileContent.Length); 314 | 315 | fixture.TcClient.WriteAny(hLogCount, 0); 316 | foreach (var f in files) File.Delete(f); 317 | fixture.TcClient.DeleteVariableHandle(hRun); 318 | fixture.TcClient.DeleteVariableHandle(hLogCount); 319 | } 320 | 321 | [Theory] 322 | [InlineData(5, 5, 500)] 323 | [InlineData(10, 10, 1_000)] 324 | [InlineData(20, 20, 2_000)] 325 | [InlineData(50, 50, 5_000)] 326 | [InlineData(5, 200, 2_000)] 327 | [InlineData(5, 500, 5_000)] 328 | [InlineData(20, 200, 10_000)] 329 | public async void Log_multiple_times_in_multiple_cycles(int cycleCount, int logCount, int waitTime) 330 | { 331 | uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".Log_multiple_logs_in_multiple_cycles"); 332 | uint hCyclesToLog = fixture.TcClient.CreateVariableHandle(mut + ".Number_of_cycles"); 333 | uint hLogCount = fixture.TcClient.CreateVariableHandle(mut + ".Number_of_logs_per_cycle"); 334 | 335 | fixture.TcClient.WriteAny(hCyclesToLog, cycleCount); 336 | fixture.TcClient.WriteAny(hLogCount, logCount); 337 | fixture.TcClient.WriteAny(hRun, true); 338 | await Task.Delay(waitTime); 339 | var files = Directory.GetFiles(path); 340 | var fileContent = File.ReadAllLines(files[0]); 341 | 342 | Assert.Equal(cycleCount * logCount, fileContent.Length); 343 | 344 | fixture.TcClient.WriteAny(hCyclesToLog, 0); 345 | fixture.TcClient.WriteAny(hLogCount, 0); 346 | foreach (var f in files) File.Delete(f); 347 | fixture.TcClient.DeleteVariableHandle(hRun); 348 | fixture.TcClient.DeleteVariableHandle(hCyclesToLog); 349 | fixture.TcClient.DeleteVariableHandle(hLogCount); 350 | } 351 | 352 | [Theory] 353 | [InlineData(1, 1, 1_000)] 354 | [InlineData(5, 5, 1_000)] 355 | [InlineData(10, 1, 1_000)] 356 | [InlineData(1, 10, 1_000)] 357 | [InlineData(10, 10, 5_000)] 358 | [InlineData(20, 20, 5_000)] 359 | [InlineData(50, 50, 5_000)] 360 | [InlineData(100, 50, 10_000)] 361 | [InlineData(5, 200, 2_000)] 362 | [InlineData(5, 500, 5_000)] 363 | public async void Persistance_time_stays_within_bounds(int cycleCount, int logCount, int waitTime) 364 | { 365 | const int MAX_LOGS_PER_CYCLE = 100; 366 | uint hRun = fixture.TcClient.CreateVariableHandle(mut + ".Persistance_time_stays_within_bounds"); 367 | uint hCyclesToLog = fixture.TcClient.CreateVariableHandle(mut + ".Number_of_cycles"); 368 | uint hLogCount = fixture.TcClient.CreateVariableHandle(mut + ".Number_of_logs_per_cycle"); 369 | uint hDurationInCycles = fixture.TcClient.CreateVariableHandle(mut + ".Duration_in_cylces"); 370 | int expectedCycles = 371 | logCount > MAX_LOGS_PER_CYCLE ? 372 | logCount / MAX_LOGS_PER_CYCLE * 3 * cycleCount : 373 | cycleCount; 374 | 375 | fixture.TcClient.WriteAny(hCyclesToLog, cycleCount); 376 | fixture.TcClient.WriteAny(hLogCount, logCount); 377 | fixture.TcClient.WriteAny(hDurationInCycles, 0); 378 | fixture.TcClient.WriteAny(hRun, true); 379 | await Task.Delay(waitTime); 380 | var files = Directory.GetFiles(path); 381 | var durationInCycles = fixture.TcClient.ReadAny(hDurationInCycles); 382 | 383 | Assert.InRange(durationInCycles, expectedCycles, expectedCycles * 1.5 + 2); 384 | 385 | fixture.TcClient.WriteAny(hCyclesToLog, 0); 386 | fixture.TcClient.WriteAny(hLogCount, 0); 387 | fixture.TcClient.WriteAny(hDurationInCycles, 0); 388 | foreach (var f in files) File.Delete(f); 389 | fixture.TcClient.DeleteVariableHandle(hRun); 390 | fixture.TcClient.DeleteVariableHandle(hCyclesToLog); 391 | fixture.TcClient.DeleteVariableHandle(hLogCount); 392 | fixture.TcClient.DeleteVariableHandle(hDurationInCycles); 393 | } 394 | 395 | [Fact] 396 | public void Persistence_in_first_cpu_cycle_has_correct_time_data_in_filename() 397 | { 398 | var files = Directory.GetFiles("C:\\UnitTestFirstCycle\\"); 399 | var year = DateTime.Now.Year; 400 | var month = DateTime.Now.Month; 401 | var day = DateTime.Now.Day; 402 | 403 | foreach (var f in files) 404 | { 405 | Assert.DoesNotContain("1970", f); 406 | Assert.Contains(year.ToString(), f); 407 | Assert.Contains(month.ToString(), f); 408 | Assert.Contains(day.ToString(), f); 409 | 410 | // No file will be created if option ValidTimestampsOnly is set 411 | // as the first PLC cycle will not yet have valid time information 412 | Assert.DoesNotContain("firstCycleValidTimestampOnly", f); 413 | } 414 | 415 | foreach (var f in files) File.Delete(f); 416 | } 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /src/TcLogTest.NET/PlcFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using TwinCAT.Ads; 5 | using Xunit; 6 | 7 | namespace TcLogTest.NET 8 | { 9 | public class PlcFixture : IDisposable 10 | { 11 | public PlcFixture() 12 | { 13 | TcClient = new AdsClient(); 14 | TcClient.Connect(851); 15 | } 16 | 17 | public void Dispose() 18 | { 19 | TcClient.Dispose(); 20 | } 21 | 22 | public AdsClient TcClient { get; private set; } 23 | } 24 | 25 | [CollectionDefinition("Plc collection")] 26 | public class PlcCollection : ICollectionFixture 27 | { 28 | // This class has no code, and is never created. Its purpose is simply 29 | // to be the place to apply [CollectionDefinition] and all the 30 | // ICollectionFixture<> interfaces. 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/TcLogTest.NET/TcLogTest.NET.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tools/release_library.ps1: -------------------------------------------------------------------------------- 1 | # This script prepares the library for release. 2 | # It takes a single parameter: the version number. 3 | # Usage: .\release_library.ps1 -version "1.0.0" 4 | # It: 5 | # - removes the TcUnit reference from the project 6 | # - sets the version number 7 | # - sets the release flag 8 | # - saves & installs the library 9 | 10 | param ( 11 | [string]$version 12 | ) 13 | 14 | if (-not $version) { 15 | Write-Host "Error: Version argument is required." 16 | exit 1 17 | } 18 | 19 | $ErrorActionPreference = "Stop" 20 | 21 | $plcProjectSourceDir = "C:\repos\TcLog\src\TcLogProj" 22 | $plcProjectTargetDir = "C:\TcLogLibraryRelease\TcLogProj" 23 | $plcProjectPath = "C:\TcLogLibraryRelease\TcLogProj\TcLogProj.tsproj" 24 | 25 | $slnDir = "C:\TcLogLibraryRelease" 26 | $slnName = "TcLogLibRelease" 27 | 28 | $librarySavePath = Join-Path -Path (Get-Location) -ChildPath "..\library\TcLog.library" 29 | 30 | # Copy the PLC project files to the target directory 31 | if (-not (Test-Path $plcProjectTargetDir)) { 32 | New-Item -ItemType Directory -Path $plcProjectTargetDir | Out-Null 33 | } 34 | Write-Host "Copying files from $plcProjectSourceDir to $plcProjectTargetDir..." 35 | Copy-Item -Path "$plcProjectSourceDir\*" -Destination $plcProjectTargetDir -Recurse -Force 36 | Write-Host "Files copied successfully." 37 | 38 | if ($null -eq $plcProjectPath -or -not (Test-Path $plcProjectPath)) { 39 | Write-Host "Error: PLC project path does not exist or is undefined: $plcProjectPath" 40 | exit 1 41 | } 42 | 43 | # Open Visual Studio and create a new solution 44 | $dte = new-object -com TcXaeShell.DTE.17.0 45 | try { 46 | $dte.SuppressUI = $false 47 | $dte.MainWindow.Visible = $true 48 | $solution = $dte.Solution 49 | Write-Host "Creating new solution..." 50 | 51 | # !! Variable assignments are needed to avoid some weird PS error !! 52 | $a = $solution.Create($slnDir, $slnName) 53 | Write-Host "Adding PLC project to solution..." 54 | $a = $solution.AddFromFile($plcProjectPath) 55 | Write-Host "Solution created and PLC project added successfully." 56 | 57 | $project = $solution.Projects.Item(1) 58 | 59 | # Remove the reference to TcUnit from the project 60 | Write-Host "Removing reference to TcUnit from the project..." 61 | $a = $project.Object.LookupTreeItem("TIPC^TcLog^TcLog Projekt^References").RemoveReference("TcUnit") 62 | 63 | # Set the version number 64 | $xml = "$version" 65 | $a = $project.Object.LookupTreeItem("TIPC^TcLog^TcLog Projekt").ConsumeXml($xml) 66 | 67 | # Set the release flag 68 | $xml = "true" 69 | $a = $project.Object.LookupTreeItem("TIPC^TcLog^TcLog Projekt").ConsumeXml($xml) 70 | 71 | # Save as library 72 | Write-Host "Saving PLC project as library..." 73 | $a = $project.Object.LookupTreeItem("TIPC^TcLog^TcLog Projekt").SaveAsLibrary($librarySavePath, $true) 74 | Write-Host "PLC project saved as library successfully." 75 | 76 | # Close the project 77 | Write-Host "Closing the project." 78 | $a = $dte.Quit() 79 | 80 | # Delete the project files 81 | Write-Host "Deleting the project files..." 82 | Remove-Item -Path "$plcProjectTargetDir\*" -Recurse -Force 83 | Write-Host "Project files deleted successfully." 84 | } 85 | catch { 86 | Write-Host "An error occurred: $_" 87 | $dte.Quit() 88 | } 89 | --------------------------------------------------------------------------------