├── .github ├── FUNDING.yml └── workflows │ ├── codeql.yml │ └── dotnet.yml ├── .gitignore ├── BACnet.csproj ├── BACnet.csproj.DotSettings ├── BACnet.sln ├── BACnet.sln.DotSettings ├── BACnetClient.cs ├── BacnetAsyncResult.cs ├── Base ├── BaCnetCalendarEntry.cs ├── BacnetAbortReason.cs ├── BacnetAddress.cs ├── BacnetAddressTypes.cs ├── BacnetApplicationTags.cs ├── BacnetBinaryPv.cs ├── BacnetBitString.cs ├── BacnetBvlcFunctions.cs ├── BacnetBvlcResults.cs ├── BacnetBvlcV6Functions.cs ├── BacnetBvlcV6Results.cs ├── BacnetCOVSubscription.cs ├── BacnetCharacterStringEncodings.cs ├── BacnetConfirmedServices.cs ├── BacnetDate.cs ├── BacnetDateRange.cs ├── BacnetDeviceObjectPropertyReference.cs ├── BacnetDeviceStatus.cs ├── BacnetError.cs ├── BacnetErrorClasses.cs ├── BacnetErrorCodes.cs ├── BacnetEventNotificationData.cs ├── BacnetGenericTime.cs ├── BacnetGetEventInformationData.cs ├── BacnetLogRecord.cs ├── BacnetMaxAdpu.cs ├── BacnetMaxSegments.cs ├── BacnetMstpFrameTypes.cs ├── BacnetNetworkMessageTypes.cs ├── BacnetNodeTypes.cs ├── BacnetNpduControls.cs ├── BacnetObjectDescription.cs ├── BacnetObjectId.cs ├── BacnetObjectTypes.cs ├── BacnetPduTypes.cs ├── BacnetPolarity.cs ├── BacnetProgramError.cs ├── BacnetProgramRequest.cs ├── BacnetProgramState.cs ├── BacnetPropertyIds.cs ├── BacnetPropertyReference.cs ├── BacnetPropertyState.cs ├── BacnetPropertyValue.cs ├── BacnetPtpDisconnectReasons.cs ├── BacnetPtpFrameTypes.cs ├── BacnetReadAccessResult.cs ├── BacnetReadAccessSpecification.cs ├── BacnetReadRangeRequestTypes.cs ├── BacnetReinitializedStates.cs ├── BacnetRejectReason.cs ├── BacnetReliability.cs ├── BacnetRestartReason.cs ├── BacnetResultFlags.cs ├── BacnetSegmentations.cs ├── BacnetServicesSupported.cs ├── BacnetStatusFlags.cs ├── BacnetTimestampTags.cs ├── BacnetTrendLogValueType.cs ├── BacnetUnconfirmedServices.cs ├── BacnetUnitsId.cs ├── BacnetValue.cs ├── BacnetWritePriority.cs ├── BacnetweekNDay.cs ├── DeviceReportingRecipient.cs └── Enums │ ├── BacnetBackupState.cs │ ├── BacnetCOVTypes.cs │ ├── BacnetEventEnable.cs │ ├── BacnetEventStates.cs │ ├── BacnetEventTypes.cs │ ├── BacnetFileAccessMethod.cs │ ├── BacnetLifeSafetyModes.cs │ ├── BacnetLifeSafetyOperations.cs │ ├── BacnetLifeSafetyStates.cs │ ├── BacnetLimitEnable.cs │ └── BacnetNotifyTypes.cs ├── GlobalUsings.cs ├── Helpers └── BacnetValuesExtensions.cs ├── MIT_license.txt ├── README.md ├── Serialize ├── APDU.cs ├── ASN1.cs ├── BVLC.cs ├── EncodeBuffer.cs ├── EncodeResult.cs ├── MSTP.cs ├── NPDU.cs ├── PTP.cs └── Services.cs ├── Storage ├── DeviceStorage.cs ├── Object.cs └── Property.cs ├── Transport ├── BVLCV6.cs ├── BacnetIpUdpProtocolTransport.cs ├── BacnetIpV6UdpProtocolTransport.cs ├── BacnetMstpProtocolTransport.cs ├── BacnetPipeTransport.cs ├── BacnetPtpProtocolTransport.cs ├── BacnetSerialPortTransport.cs ├── BacnetTransportBase.cs ├── BacnetTransportEthernet.cs ├── IBacnetSerialTransport.cs └── IBacnetTransport.cs └── logo.png /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [gralin] 4 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '41 18 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'csharp' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v2 18 | with: 19 | dotnet-version: 6.0.x 20 | - name: Restore dependencies 21 | run: dotnet restore 22 | - name: Build 23 | run: dotnet build --no-restore 24 | - name: Test 25 | run: dotnet test --no-build --verbosity normal 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml -------------------------------------------------------------------------------- /BACnet.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | latest 4 | net48;netstandard2.0 5 | Ela-compil sp. z o. o. 6 | Ela-compil and contributors 7 | BACnet 8 | Copyright (c) Ela-compil sp. z o. o. 9 | BACnet protocol library for .NET 10 | Library 11 | true 12 | MIT 13 | System.IO.BACnet 14 | true 15 | bacnet;btl;automation 16 | git 17 | https://github.com/ela-compil/BACnet 18 | logo.png 19 | https://raw.githubusercontent.com/ela-compil/BACnet/master/logo.png 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /BACnet.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | NEVER 6 | True 7 | <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> 8 | True 9 | True 10 | True 11 | True 12 | True 13 | True 14 | True 15 | True 16 | True 17 | -------------------------------------------------------------------------------- /BACnet.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.31911.260 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BACnet", "BACnet.csproj", "{66832876-01FC-4B7C-8D92-54195773FABF}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {66832876-01FC-4B7C-8D92-54195773FABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {66832876-01FC-4B7C-8D92-54195773FABF}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {66832876-01FC-4B7C-8D92-54195773FABF}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {66832876-01FC-4B7C-8D92-54195773FABF}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {4BD775C7-0626-4B2B-969B-285F36768D4D} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /BACnet.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | APDU 3 | ASN 4 | BBMD 5 | BVLC 6 | COV 7 | FDR 8 | IP 9 | IPV 10 | MSTP 11 | NPDU 12 | PTP 13 | VMAC 14 | <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> 15 | <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> 16 | -------------------------------------------------------------------------------- /BacnetAsyncResult.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public class BacnetAsyncResult : IAsyncResult, IDisposable 4 | { 5 | private BacnetClient _comm; 6 | private readonly byte _waitInvokeId; 7 | private Exception _error; 8 | private readonly byte[] _transmitBuffer; 9 | private readonly int _transmitLength; 10 | private readonly bool _waitForTransmit; 11 | private readonly int _transmitTimeout; 12 | private ManualResetEvent _waitHandle; 13 | private readonly BacnetAddress _address; 14 | 15 | public bool Segmented { get; private set; } 16 | public byte[] Result { get; private set; } 17 | public object AsyncState { get; set; } 18 | public bool CompletedSynchronously { get; private set; } 19 | public WaitHandle AsyncWaitHandle => _waitHandle; 20 | public bool IsCompleted => _waitHandle.WaitOne(0); 21 | public BacnetAddress Address => _address; 22 | 23 | public Exception Error 24 | { 25 | get => _error; 26 | set 27 | { 28 | _error = value; 29 | CompletedSynchronously = true; 30 | _waitHandle.Set(); 31 | } 32 | } 33 | 34 | public BacnetAsyncResult(BacnetClient comm, BacnetAddress adr, byte invokeId, byte[] transmitBuffer, int transmitLength, bool waitForTransmit, int transmitTimeout) 35 | { 36 | _transmitTimeout = transmitTimeout; 37 | _address = adr; 38 | _waitForTransmit = waitForTransmit; 39 | _transmitBuffer = transmitBuffer; 40 | _transmitLength = transmitLength; 41 | _comm = comm; 42 | _waitInvokeId = invokeId; 43 | _comm.OnComplexAck += OnComplexAck; 44 | _comm.OnError += OnError; 45 | _comm.OnAbort += OnAbort; 46 | _comm.OnReject += OnReject; 47 | _comm.OnSimpleAck += OnSimpleAck; 48 | _comm.OnSegment += OnSegment; 49 | _waitHandle = new ManualResetEvent(false); 50 | } 51 | 52 | public void Resend() 53 | { 54 | try 55 | { 56 | if (_comm.Transport.Send(_transmitBuffer, _comm.Transport.HeaderLength, _transmitLength, _address, _waitForTransmit, _transmitTimeout) < 0) 57 | { 58 | Error = new IOException("Write Timeout"); 59 | } 60 | } 61 | catch (Exception ex) 62 | { 63 | Error = new Exception($"Write Exception: {ex.Message}"); 64 | } 65 | } 66 | 67 | private void OnSegment(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, byte sequenceNumber, byte[] buffer, int offset, int length) 68 | { 69 | if (invokeId != _waitInvokeId || !adr.Equals(_address)) 70 | return; 71 | 72 | Segmented = true; 73 | _waitHandle.Set(); 74 | } 75 | 76 | private void OnSimpleAck(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte[] data, int dataOffset, int dataLength) 77 | { 78 | if (invokeId != _waitInvokeId || !adr.Equals(_address)) 79 | return; 80 | 81 | _waitHandle.Set(); 82 | } 83 | 84 | private void OnAbort(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, byte invokeId, BacnetAbortReason reason, byte[] buffer, int offset, int length) 85 | { 86 | if (invokeId != _waitInvokeId || !adr.Equals(_address)) 87 | return; 88 | 89 | Error = new Exception($"Abort from device, reason: {reason}"); 90 | } 91 | 92 | private void OnReject(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, byte invokeId, BacnetRejectReason reason, byte[] buffer, int offset, int length) 93 | { 94 | if (invokeId != _waitInvokeId || !adr.Equals(_address)) 95 | return; 96 | 97 | Error = new Exception($"Reject from device, reason: {reason}"); 98 | } 99 | 100 | private void OnError(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, BacnetErrorClasses errorClass, BacnetErrorCodes errorCode, byte[] buffer, int offset, int length) 101 | { 102 | if (invokeId != _waitInvokeId || !adr.Equals(_address)) 103 | return; 104 | 105 | Error = new Exception($"Error from device: {errorClass} - {errorCode}"); 106 | } 107 | 108 | private void OnComplexAck(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte[] buffer, int offset, int length) 109 | { 110 | if (invokeId != _waitInvokeId || !adr.Equals(_address)) 111 | return; 112 | 113 | Segmented = false; 114 | Result = new byte[length]; 115 | 116 | if (length > 0) 117 | Array.Copy(buffer, offset, Result, 0, length); 118 | 119 | //notify waiter even if segmented 120 | _waitHandle.Set(); 121 | } 122 | 123 | /// 124 | /// Will continue waiting until all segments are recieved 125 | /// 126 | public bool WaitForDone(int timeout) 127 | { 128 | while (true) 129 | { 130 | if (!AsyncWaitHandle.WaitOne(timeout)) 131 | return false; 132 | if (Segmented) 133 | _waitHandle.Reset(); 134 | else 135 | return true; 136 | } 137 | } 138 | 139 | public void Dispose() 140 | { 141 | if (_comm != null) 142 | { 143 | _comm.OnComplexAck -= OnComplexAck; 144 | _comm.OnError -= OnError; 145 | _comm.OnAbort -= OnAbort; 146 | _comm.OnReject -= OnReject; 147 | _comm.OnSimpleAck -= OnSimpleAck; 148 | _comm.OnSegment -= OnSegment; 149 | _comm = null; 150 | } 151 | 152 | if (_waitHandle != null) 153 | { 154 | _waitHandle.Dispose(); 155 | _waitHandle = null; 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Base/BaCnetCalendarEntry.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BACnetCalendarEntry : ASN1.IEncode, ASN1.IDecode 4 | { 5 | public List Entries; // BacnetDate or BacnetDateRange or BacnetweekNDay 6 | 7 | public void Encode(EncodeBuffer buffer) 8 | { 9 | if (Entries == null) 10 | return; 11 | 12 | foreach (ASN1.IEncode entry in Entries) 13 | { 14 | if (entry is BacnetDate) 15 | { 16 | ASN1.encode_tag(buffer, 0, true, 4); 17 | entry.Encode(buffer); 18 | } 19 | 20 | if (entry is BacnetDateRange) 21 | { 22 | ASN1.encode_opening_tag(buffer, 1); 23 | entry.Encode(buffer); 24 | ASN1.encode_closing_tag(buffer, 1); 25 | } 26 | 27 | if (entry is BacnetweekNDay) 28 | { 29 | ASN1.encode_tag(buffer, 2, true, 3); 30 | entry.Encode(buffer); 31 | } 32 | } 33 | } 34 | 35 | public int Decode(byte[] buffer, int offset, uint count) 36 | { 37 | var len = 0; 38 | 39 | Entries = new List(); 40 | 41 | while (true) 42 | { 43 | len += ASN1.decode_tag_number(buffer, offset + len, out byte tagNumber); 44 | 45 | switch (tagNumber) 46 | { 47 | case 0: 48 | var bdt = new BacnetDate(); 49 | len += bdt.Decode(buffer, offset + len, count); 50 | Entries.Add(bdt); 51 | break; 52 | case 1: 53 | var bdr = new BacnetDateRange(); 54 | len += bdr.Decode(buffer, offset + len, count); 55 | Entries.Add(bdr); 56 | len++; // closing tag 57 | break; 58 | case 2: 59 | var bwd = new BacnetweekNDay(); 60 | len += bwd.Decode(buffer, offset + len, count); 61 | Entries.Add(bwd); 62 | break; 63 | default: 64 | return len - 1; // closing Tag 65 | } 66 | } 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Base/BacnetAbortReason.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | /// 4 | /// Reason the transaction with the indicated invoke ID is being aborted. 5 | /// 6 | /// 7 | /// Enumerated values 0-63 are reserved for definition by ASHRAE. 8 | /// Enumerated values 64-255 may be used by others. 9 | /// 10 | public enum BacnetAbortReason 11 | { 12 | /// 13 | /// This abort reason is returned for a reason other than any of those previously enumerated. 14 | /// 15 | OTHER = 0, 16 | 17 | /// 18 | /// A buffer capacity has been exceeded. 19 | /// 20 | BUFFER_OVERFLOW = 1, 21 | 22 | /// 23 | /// Generated in response to an APDU that is not expected in the present 24 | /// state of the Transaction State Machine. 25 | /// 26 | INVALID_APDU_IN_THIS_STATE = 2, 27 | 28 | /// 29 | /// The transaction shall be aborted to permit higher priority processing. 30 | /// 31 | PREEMPTED_BY_HIGHER_PRIORITY_TASK = 3, 32 | 33 | /// 34 | /// Generated in response to an APDU that has its segmentation bit set to TRUE 35 | /// when the receiving device does not support segmentation. It is also generated 36 | /// when a BACnet-ComplexACKPDU is large enough to require segmentation but it 37 | /// cannot be transmitted because either the transmitting device or the receiving 38 | /// device does not support segmentation. 39 | /// 40 | SEGMENTATION_NOT_SUPPORTED = 4, 41 | 42 | /// 43 | /// The Transaction is aborted due to receipt of a security error. 44 | /// 45 | SECURITY_ERROR = 5, 46 | 47 | /// 48 | /// The transaction is aborted due to receipt of a PDU secured differently 49 | /// than the original PDU of the transaction. 50 | /// 51 | INSUFFICIENT_SECURITY = 6, 52 | 53 | /// 54 | /// A device receives a request that is segmented, or receives any segment of 55 | /// a segmented request, where the Proposed Window Size field of the PDU header 56 | /// is either zero or greater than 127. 57 | /// 58 | WINDOW_SIZE_OUT_OF_RANGE = 7, 59 | 60 | /// 61 | /// A device receives a confirmed request but its application layer has 62 | /// not responded within the published APDU Timeout period. 63 | /// 64 | APPLICATION_EXCEEDED_REPLY_TIME = 8, 65 | 66 | /// 67 | /// A device receives a request but cannot start processing because it has run 68 | /// out of some internal resource. 69 | /// 70 | OUT_OF_RESOURCES = 9, 71 | 72 | /// 73 | /// A transaction state machine timer exceeded the timeout applicable for the 74 | /// current state, causing the transaction machine to abort the transaction. 75 | /// 76 | TSM_TIMEOUT = 10, 77 | 78 | /// 79 | /// An APDU was received from the local application program whose overall 80 | /// size exceeds the maximum transmittable length or exceeds the maximum 81 | /// number of segments accepted by the server. 82 | /// 83 | APDU_TOO_LONG = 11 84 | } 85 | -------------------------------------------------------------------------------- /Base/BacnetAddress.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public class BacnetAddress : ASN1.IEncode 4 | { 5 | public ushort net; 6 | public byte[] adr; 7 | public byte[] VMac = new byte[3]; // for IP V6, could be integrated also as 3 additional bytes in adr 8 | public BacnetAddressTypes type; 9 | 10 | // Modif FC 11 | public BacnetAddress RoutedSource = null; 12 | 13 | // DAL 14 | public BacnetAddress RoutedDestination = null; 15 | 16 | public BacnetAddress(BacnetAddressTypes addressType, ushort network = 0, byte[] address = null) 17 | { 18 | type = addressType; 19 | net = network; 20 | adr = address; 21 | } 22 | 23 | public BacnetAddress(BacnetAddressTypes addressType, string address = null, ushort network = 0) 24 | : this(addressType, network) 25 | { 26 | if (address == null) 27 | return; 28 | 29 | switch (type) 30 | { 31 | case BacnetAddressTypes.IP: 32 | adr = new byte[6]; 33 | var addressParts = address.Split(':'); 34 | var addressBytes = IPAddress.Parse(addressParts[0]).GetAddressBytes(); 35 | Array.Copy(addressBytes, adr, addressBytes.Length); 36 | 37 | var portBytes = BitConverter.GetBytes(addressParts.Length > 1 38 | ? ushort.Parse(addressParts[1]) 39 | : (ushort)0xBAC0); 40 | 41 | if (BitConverter.IsLittleEndian) 42 | portBytes = portBytes.Reverse().ToArray(); 43 | 44 | Array.Copy(portBytes, 0, adr, addressBytes.Length, portBytes.Length); 45 | break; 46 | 47 | case BacnetAddressTypes.Ethernet: 48 | adr = PhysicalAddress.Parse(address).GetAddressBytes(); 49 | break; 50 | 51 | default: 52 | throw new NotSupportedException("String format is not supported for address type " + type); 53 | } 54 | } 55 | 56 | public override int GetHashCode() 57 | { 58 | // DAL this was originally broken... 59 | var str = Convert.ToBase64String(adr); 60 | return str.GetHashCode(); 61 | } 62 | 63 | public override string ToString() 64 | { 65 | return ToString(type); 66 | } 67 | 68 | public string ToString(BacnetAddressTypes addressType) 69 | { 70 | while (true) 71 | { 72 | switch (addressType) 73 | { 74 | case BacnetAddressTypes.IP: 75 | return adr != null && adr.Length >= 6 76 | ? $"{adr[0]}.{adr[1]}.{adr[2]}.{adr[3]}:{(adr[4] << 8) | adr[5]}" 77 | : "0.0.0.0"; 78 | 79 | case BacnetAddressTypes.MSTP: 80 | return adr != null && adr.Length >= 1 81 | ? $"{adr[0]}" 82 | : "-1"; 83 | 84 | case BacnetAddressTypes.PTP: 85 | return "x"; 86 | 87 | case BacnetAddressTypes.Ethernet: 88 | return $"{new PhysicalAddress(adr)}"; 89 | 90 | case BacnetAddressTypes.IPV6: 91 | return adr != null && adr.Length == 18 92 | ? $"{new IPAddress(adr.Take(16).ToArray())}:{(adr[16] << 8) | adr[17]}" 93 | : "[::]"; 94 | 95 | default: // Routed @ are always like this, NPDU do not contains the MAC type, only the lenght 96 | if (adr == null || adr.Length == 0) 97 | return "?"; 98 | 99 | switch (adr.Length) 100 | { 101 | case 6: // certainly IP, but not sure (Newron System send it for internal usage with 4*0 bytes) 102 | addressType = BacnetAddressTypes.IP; 103 | continue; 104 | 105 | case 18: // Not sure it could appears, since NPDU may contains Vmac ? 106 | addressType = BacnetAddressTypes.IPV6; 107 | continue; 108 | 109 | case 3: 110 | return $"IPv6 VMac : {adr[0] << 16 | (adr[1] << 8) | adr[2]}"; 111 | 112 | default: 113 | return string.Join(" ", adr); 114 | } 115 | } 116 | } 117 | } 118 | 119 | public string ToString(bool sourceOnly) 120 | { 121 | if (RoutedSource == null) 122 | return ToString(); 123 | 124 | return sourceOnly 125 | ? RoutedSource.ToString() 126 | : $"{RoutedSource} via {ToString()}"; 127 | } 128 | 129 | public bool HasAddress(IPAddress ipAddress) 130 | { 131 | if (type != BacnetAddressTypes.IP || adr == null || ipAddress == null) 132 | return false; 133 | 134 | return adr.Take(4).SequenceEqual(ipAddress.GetAddressBytes()); 135 | } 136 | 137 | public override bool Equals(object obj) 138 | { 139 | if (obj is not BacnetAddress) return false; 140 | var d = (BacnetAddress)obj; 141 | if (adr == null && d.adr == null) return true; 142 | if (adr == null || d.adr == null) return false; 143 | if (adr.Length != d.adr.Length) return false; 144 | if (adr.Where((t, i) => t != d.adr[i]).Any()) 145 | return false; 146 | 147 | // Modif FC 148 | if (RoutedSource == null && d.RoutedSource != null) 149 | return false; 150 | 151 | // DAL 152 | if (RoutedDestination == null && d.RoutedDestination != null) 153 | return false; 154 | 155 | if (d.RoutedSource == null && RoutedSource == null && 156 | d.RoutedDestination == null && RoutedDestination == null) 157 | return true; 158 | 159 | bool rv = RoutedSource?.Equals(d.RoutedSource) ?? false; 160 | rv |= RoutedDestination?.Equals(d.RoutedDestination) ?? false; 161 | return rv; 162 | } 163 | 164 | // checked if device is routed by curent equipement 165 | public bool IsMyRouter(BacnetAddress device) 166 | { 167 | if (device.RoutedSource == null || RoutedSource != null) 168 | return false; 169 | 170 | if (adr.Length != device.adr.Length) 171 | return false; 172 | 173 | return !adr.Where((t, i) => t != device.adr[i]).Any(); 174 | } 175 | 176 | public void Encode(EncodeBuffer buffer) 177 | { 178 | ASN1.encode_opening_tag(buffer, 1); 179 | ASN1.encode_application_unsigned(buffer, net); 180 | ASN1.encode_application_octet_string(buffer, adr, 0, adr.Length); 181 | ASN1.encode_closing_tag(buffer, 1); 182 | } 183 | 184 | public string FullHashString() 185 | { 186 | var hash = $"{(uint)type}.{net}.{string.Concat(adr.Select(a => a.ToString("X2")))}"; 187 | 188 | if (RoutedSource != null) 189 | hash += $":{RoutedSource.FullHashString()}"; 190 | 191 | if (RoutedDestination != null) 192 | hash += $":{RoutedDestination.FullHashString()}"; 193 | 194 | return hash; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /Base/BacnetAddressTypes.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetAddressTypes 4 | { 5 | None, 6 | IP, 7 | MSTP, 8 | Ethernet, 9 | ArcNet, 10 | LonTalk, 11 | PTP, 12 | IPV6 13 | } 14 | -------------------------------------------------------------------------------- /Base/BacnetApplicationTags.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetApplicationTags 4 | { 5 | BACNET_APPLICATION_TAG_NULL = 0, 6 | BACNET_APPLICATION_TAG_BOOLEAN = 1, 7 | BACNET_APPLICATION_TAG_UNSIGNED_INT = 2, 8 | BACNET_APPLICATION_TAG_SIGNED_INT = 3, 9 | BACNET_APPLICATION_TAG_REAL = 4, 10 | BACNET_APPLICATION_TAG_DOUBLE = 5, 11 | BACNET_APPLICATION_TAG_OCTET_STRING = 6, 12 | BACNET_APPLICATION_TAG_CHARACTER_STRING = 7, 13 | BACNET_APPLICATION_TAG_BIT_STRING = 8, 14 | BACNET_APPLICATION_TAG_ENUMERATED = 9, 15 | BACNET_APPLICATION_TAG_DATE = 10, 16 | BACNET_APPLICATION_TAG_TIME = 11, 17 | BACNET_APPLICATION_TAG_OBJECT_ID = 12, 18 | BACNET_APPLICATION_TAG_RESERVE1 = 13, 19 | BACNET_APPLICATION_TAG_RESERVE2 = 14, 20 | BACNET_APPLICATION_TAG_RESERVE3 = 15, 21 | MAX_BACNET_APPLICATION_TAG = 16, 22 | 23 | /* Extra stuff - complex tagged data - not specifically enumerated */ 24 | 25 | /* Means : "nothing", an empty list, not even a null character */ 26 | BACNET_APPLICATION_TAG_EMPTYLIST, 27 | /* BACnetWeeknday */ 28 | BACNET_APPLICATION_TAG_WEEKNDAY, 29 | /* BACnetDateRange */ 30 | BACNET_APPLICATION_TAG_DATERANGE, 31 | /* BACnetDateTime */ 32 | BACNET_APPLICATION_TAG_DATETIME, 33 | /* BACnetTimeStamp */ 34 | BACNET_APPLICATION_TAG_TIMESTAMP, 35 | /* Error Class, Error Code */ 36 | BACNET_APPLICATION_TAG_ERROR, 37 | /* BACnetDeviceObjectPropertyReference */ 38 | BACNET_APPLICATION_TAG_DEVICE_OBJECT_PROPERTY_REFERENCE, 39 | /* BACnetDeviceObjectReference */ 40 | BACNET_APPLICATION_TAG_DEVICE_OBJECT_REFERENCE, 41 | /* BACnetObjectPropertyReference */ 42 | BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE, 43 | /* BACnetDestination (Recipient_List) */ 44 | BACNET_APPLICATION_TAG_DESTINATION, 45 | /* BACnetRecipient */ 46 | BACNET_APPLICATION_TAG_RECIPIENT, 47 | /* BACnetCOVSubscription */ 48 | BACNET_APPLICATION_TAG_COV_SUBSCRIPTION, 49 | /* BACnetCalendarEntry */ 50 | BACNET_APPLICATION_TAG_CALENDAR_ENTRY, 51 | /* BACnetWeeklySchedule */ 52 | BACNET_APPLICATION_TAG_WEEKLY_SCHEDULE, 53 | /* BACnetSpecialEvent */ 54 | BACNET_APPLICATION_TAG_SPECIAL_EVENT, 55 | /* BACnetReadAccessSpecification */ 56 | BACNET_APPLICATION_TAG_READ_ACCESS_SPECIFICATION, 57 | /* BACnetReadAccessResult */ 58 | BACNET_APPLICATION_TAG_READ_ACCESS_RESULT, 59 | /* BACnetLightingCommand */ 60 | BACNET_APPLICATION_TAG_LIGHTING_COMMAND, 61 | BACNET_APPLICATION_TAG_CONTEXT_SPECIFIC_DECODED, 62 | BACNET_APPLICATION_TAG_CONTEXT_SPECIFIC_ENCODED, 63 | /* BACnetLogRecord */ 64 | BACNET_APPLICATION_TAG_LOG_RECORD 65 | } 66 | -------------------------------------------------------------------------------- /Base/BacnetBinaryPv.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet.Base; 2 | 3 | public enum BacnetBinaryPv : byte 4 | { 5 | MIN_BINARY_PV = 0, /* for validating incoming values */ 6 | BINARY_INACTIVE = 0, 7 | BINARY_ACTIVE = 1, 8 | MAX_BINARY_PV = 1, /* for validating incoming values */ 9 | BINARY_NULL = 255 /* our homemade way of storing this info */ 10 | } 11 | -------------------------------------------------------------------------------- /Base/BacnetBitString.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetBitString 4 | { 5 | public byte bits_used; 6 | public byte[] value; 7 | 8 | public byte Length => bits_used; 9 | public bool this[byte bitNumber] => GetBit(bitNumber); 10 | 11 | public override string ToString() 12 | { 13 | var ret = ""; 14 | for (var i = 0; i < bits_used; i++) 15 | { 16 | ret += ((value[i / 8] & (1 << (i % 8))) > 0 ? "1" : "0"); 17 | } 18 | return ret; 19 | } 20 | 21 | public void SetBit(byte bitNumber, bool v) 22 | { 23 | var byteNumber = (byte)(bitNumber / 8); 24 | byte bitMask = 1; 25 | 26 | if (value == null) 27 | value = new byte[ASN1.MAX_BITSTRING_BYTES]; 28 | 29 | if (byteNumber < ASN1.MAX_BITSTRING_BYTES) 30 | { 31 | /* set max bits used */ 32 | if (bits_used < bitNumber + 1) 33 | bits_used = (byte)(bitNumber + 1); 34 | bitMask = (byte)(bitMask << (bitNumber - byteNumber * 8)); 35 | if (v) 36 | value[byteNumber] |= bitMask; 37 | else 38 | value[byteNumber] &= (byte)~bitMask; 39 | } 40 | } 41 | 42 | public bool GetBit(byte bitNumber) 43 | { 44 | var byteNumber = (byte)(bitNumber / 8); 45 | 46 | if (byteNumber >= ASN1.MAX_BITSTRING_BYTES || bitNumber >= bits_used) 47 | throw new ArgumentOutOfRangeException(nameof(bitNumber)); 48 | 49 | if (value == null) 50 | return false; 51 | 52 | var bitMask = (byte)(1 << (bitNumber - byteNumber * 8)); 53 | return (value[byteNumber] & bitMask) > 0; 54 | } 55 | 56 | public static BacnetBitString Parse(string str) 57 | { 58 | var ret = new BacnetBitString 59 | { 60 | value = new byte[ASN1.MAX_BITSTRING_BYTES] 61 | }; 62 | 63 | if (string.IsNullOrEmpty(str)) 64 | return ret; 65 | 66 | ret.bits_used = (byte)str.Length; 67 | for (var i = 0; i < ret.bits_used; i++) 68 | { 69 | var isSet = str[i] == '1'; 70 | if (isSet) ret.value[i / 8] |= (byte)(1 << (i % 8)); 71 | } 72 | 73 | return ret; 74 | } 75 | 76 | public uint ConvertToInt() 77 | { 78 | return value != null 79 | ? BitConverter.ToUInt32(value, 0) 80 | : 0; 81 | } 82 | 83 | public static BacnetBitString ConvertFromInt(uint value) 84 | { 85 | return new BacnetBitString 86 | { 87 | value = BitConverter.GetBytes(value), 88 | bits_used = (byte)Math.Ceiling(Math.Log(value, 2)) 89 | }; 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /Base/BacnetBvlcFunctions.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetBvlcFunctions : byte 4 | { 5 | BVLC_RESULT = 0, 6 | BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE = 1, 7 | BVLC_READ_BROADCAST_DIST_TABLE = 2, 8 | BVLC_READ_BROADCAST_DIST_TABLE_ACK = 3, 9 | BVLC_FORWARDED_NPDU = 4, 10 | BVLC_REGISTER_FOREIGN_DEVICE = 5, 11 | BVLC_READ_FOREIGN_DEVICE_TABLE = 6, 12 | BVLC_READ_FOREIGN_DEVICE_TABLE_ACK = 7, 13 | BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY = 8, 14 | BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK = 9, 15 | BVLC_ORIGINAL_UNICAST_NPDU = 10, 16 | BVLC_ORIGINAL_BROADCAST_NPDU = 11, 17 | MAX_BVLC_FUNCTION = 12 18 | } 19 | -------------------------------------------------------------------------------- /Base/BacnetBvlcResults.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetBvlcResults : ushort 4 | { 5 | BVLC_RESULT_SUCCESSFUL_COMPLETION = 0x0000, 6 | BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK = 0x0010, 7 | BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK = 0x0020, 8 | BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK = 0X0030, 9 | BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK = 0x0040, 10 | BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK = 0x0050, 11 | BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK = 0x0060 12 | } 13 | -------------------------------------------------------------------------------- /Base/BacnetBvlcV6Functions.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetBvlcV6Functions : byte 4 | { 5 | BVLC_RESULT = 0, 6 | BVLC_ORIGINAL_UNICAST_NPDU = 1, 7 | BVLC_ORIGINAL_BROADCAST_NPDU = 2, 8 | BVLC_ADDRESS_RESOLUTION = 3, 9 | BVLC_FORWARDED_ADDRESS_RESOLUTION = 4, 10 | BVLC_ADDRESS_RESOLUTION_ACK = 5, 11 | BVLC_VIRTUAL_ADDRESS_RESOLUTION = 6, 12 | BVLC_VIRTUAL_ADDRESS_RESOLUTION_ACK = 7, 13 | BVLC_FORWARDED_NPDU = 8, 14 | BVLC_REGISTER_FOREIGN_DEVICE = 9, 15 | BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY = 0xA, 16 | BVLC_SECURE_BVLC = 0xB, 17 | BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK = 0xC 18 | } 19 | -------------------------------------------------------------------------------- /Base/BacnetBvlcV6Results.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetBvlcV6Results : ushort 4 | { 5 | SUCCESSFUL_COMPLETION = 0x0000, 6 | ADDRESS_RESOLUTION_NAK = 0x0030, 7 | VIRTUAL_ADDRESS_RESOLUTION_NAK = 0x0060, 8 | REGISTER_FOREIGN_DEVICE_NAK = 0X0090, 9 | DISTRIBUTE_BROADCAST_TO_NETWORK_NAK = 0x00B0 10 | } 11 | -------------------------------------------------------------------------------- /Base/BacnetCOVSubscription.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetCOVSubscription 4 | { 5 | /* BACnetRecipientProcess */ 6 | public BacnetAddress Recipient; 7 | public uint subscriptionProcessIdentifier; 8 | /* BACnetObjectPropertyReference */ 9 | public BacnetObjectId monitoredObjectIdentifier; 10 | public BacnetPropertyReference monitoredProperty; 11 | /* BACnetCOVSubscription */ 12 | public bool IssueConfirmedNotifications; 13 | public uint TimeRemaining; 14 | public float COVIncrement; 15 | } 16 | -------------------------------------------------------------------------------- /Base/BacnetCharacterStringEncodings.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetCharacterStringEncodings 4 | { 5 | CHARACTER_ANSI_X34 = 0, /* deprecated : Addendum 135-2008k */ 6 | CHARACTER_UTF8 = 0, 7 | CHARACTER_MS_DBCS = 1, 8 | CHARACTER_JISC_6226 = 2, /* deprecated : Addendum 135-2008k */ 9 | CHARACTER_JISX_0208 = 2, 10 | CHARACTER_UCS4 = 3, 11 | CHARACTER_UCS2 = 4, 12 | CHARACTER_ISO8859 = 5 13 | } 14 | -------------------------------------------------------------------------------- /Base/BacnetConfirmedServices.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetConfirmedServices : byte 4 | { 5 | /* Alarm and Event Services */ 6 | SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM = 0, 7 | SERVICE_CONFIRMED_COV_NOTIFICATION = 1, 8 | SERVICE_CONFIRMED_EVENT_NOTIFICATION = 2, 9 | SERVICE_CONFIRMED_GET_ALARM_SUMMARY = 3, 10 | SERVICE_CONFIRMED_GET_ENROLLMENT_SUMMARY = 4, 11 | SERVICE_CONFIRMED_GET_EVENT_INFORMATION = 29, 12 | SERVICE_CONFIRMED_SUBSCRIBE_COV = 5, 13 | SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY = 28, 14 | SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION = 27, 15 | /* File Access Services */ 16 | SERVICE_CONFIRMED_ATOMIC_READ_FILE = 6, 17 | SERVICE_CONFIRMED_ATOMIC_WRITE_FILE = 7, 18 | /* Object Access Services */ 19 | SERVICE_CONFIRMED_ADD_LIST_ELEMENT = 8, 20 | SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT = 9, 21 | SERVICE_CONFIRMED_CREATE_OBJECT = 10, 22 | SERVICE_CONFIRMED_DELETE_OBJECT = 11, 23 | SERVICE_CONFIRMED_READ_PROPERTY = 12, 24 | SERVICE_CONFIRMED_READ_PROP_CONDITIONAL = 13, 25 | SERVICE_CONFIRMED_READ_PROP_MULTIPLE = 14, 26 | SERVICE_CONFIRMED_READ_RANGE = 26, 27 | SERVICE_CONFIRMED_WRITE_PROPERTY = 15, 28 | SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE = 16, 29 | /* Remote Device Management Services */ 30 | SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL = 17, 31 | SERVICE_CONFIRMED_PRIVATE_TRANSFER = 18, 32 | SERVICE_CONFIRMED_TEXT_MESSAGE = 19, 33 | SERVICE_CONFIRMED_REINITIALIZE_DEVICE = 20, 34 | /* Virtual Terminal Services */ 35 | SERVICE_CONFIRMED_VT_OPEN = 21, 36 | SERVICE_CONFIRMED_VT_CLOSE = 22, 37 | SERVICE_CONFIRMED_VT_DATA = 23, 38 | /* Security Services */ 39 | SERVICE_CONFIRMED_AUTHENTICATE = 24, 40 | SERVICE_CONFIRMED_REQUEST_KEY = 25, 41 | /* Services added after 1995 */ 42 | /* readRange (26) see Object Access Services */ 43 | /* lifeSafetyOperation (27) see Alarm and Event Services */ 44 | /* subscribeCOVProperty (28) see Alarm and Event Services */ 45 | /* getEventInformation (29) see Alarm and Event Services */ 46 | MAX_BACNET_CONFIRMED_SERVICE = 30 47 | } 48 | -------------------------------------------------------------------------------- /Base/BacnetDate.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetDate : ASN1.IEncode, ASN1.IDecode 4 | { 5 | public byte year; /* 255 any */ 6 | public byte month; /* 1=Jan; 255 any, 13 Odd, 14 Even */ 7 | public byte day; /* 1..31; 32 last day of the month; 255 any */ 8 | public byte wday; /* 1=Monday-7=Sunday, 255 any */ 9 | 10 | public BacnetDate(byte year, byte month, byte day, byte wday = 255) 11 | { 12 | this.year = year; 13 | this.month = month; 14 | this.day = day; 15 | this.wday = wday; 16 | } 17 | 18 | public void Encode(EncodeBuffer buffer) 19 | { 20 | buffer.Add(year); 21 | buffer.Add(month); 22 | buffer.Add(day); 23 | buffer.Add(wday); 24 | } 25 | 26 | public int Decode(byte[] buffer, int offset, uint count) 27 | { 28 | year = buffer[offset]; 29 | month = buffer[offset + 1]; 30 | day = buffer[offset + 2]; 31 | wday = buffer[offset + 3]; 32 | return 4; 33 | } 34 | 35 | public bool IsPeriodic => year == 255 || month > 12 || day == 255; 36 | 37 | public bool IsAFittingDate(DateTime date) 38 | { 39 | if (date.Year != year + 1900 && year != 255) 40 | return false; 41 | 42 | if (date.Month != month && month != 255 && month != 13 && month != 14) 43 | return false; 44 | if (month == 13 && (date.Month & 1) != 1) 45 | return false; 46 | if (month == 14 && (date.Month & 1) == 1) 47 | return false; 48 | 49 | if (date.Day != day && day != 255) 50 | return false; 51 | // day 32 todo 52 | 53 | if (wday == 255) 54 | return true; 55 | 56 | if (wday == 7 && date.DayOfWeek == 0) // Sunday 7 for Bacnet, 0 for .NET 57 | return true; 58 | 59 | if (wday == (int)date.DayOfWeek) 60 | return true; 61 | 62 | return false; 63 | } 64 | 65 | public DateTime toDateTime() // Not every time possible, too much complex (any month, any year ...) 66 | { 67 | try 68 | { 69 | return IsPeriodic 70 | ? new DateTime(1, 1, 1) 71 | : new DateTime(year + 1900, month, day); 72 | } 73 | catch 74 | { 75 | return DateTime.Now; // or anything else why not ! 76 | } 77 | } 78 | 79 | private static string GetDayName(int day) 80 | { 81 | if (day == 7) 82 | day = 0; 83 | 84 | return CultureInfo.CurrentCulture.DateTimeFormat.DayNames[day]; 85 | } 86 | 87 | public override string ToString() 88 | { 89 | string ret; 90 | 91 | if (wday != 255) 92 | ret = GetDayName(wday) + " "; 93 | else 94 | ret = ""; 95 | 96 | if (day != 255) 97 | ret = ret + day + "/"; 98 | else 99 | ret += "**/"; 100 | 101 | switch (month) 102 | { 103 | case 13: 104 | ret += "odd/"; 105 | break; 106 | case 14: 107 | ret += "even/"; 108 | break; 109 | case 255: 110 | ret += "**/"; 111 | break; 112 | default: 113 | ret = ret + month + "/"; 114 | break; 115 | } 116 | 117 | 118 | if (year != 255) 119 | ret += year + 1900; 120 | else 121 | ret += "****"; 122 | 123 | return ret; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Base/BacnetDateRange.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetDateRange : ASN1.IEncode, ASN1.IDecode 4 | { 5 | public BacnetDate startDate; 6 | public BacnetDate endDate; 7 | 8 | public BacnetDateRange(BacnetDate start, BacnetDate end) 9 | { 10 | startDate = start; 11 | endDate = end; 12 | } 13 | 14 | public void Encode(EncodeBuffer buffer) 15 | { 16 | ASN1.encode_tag(buffer, (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_DATE, false, 4); 17 | startDate.Encode(buffer); 18 | ASN1.encode_tag(buffer, (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_DATE, false, 4); 19 | endDate.Encode(buffer); 20 | } 21 | 22 | public int Decode(byte[] buffer, int offset, uint count) 23 | { 24 | var len = 1; // opening tag 25 | len += startDate.Decode(buffer, offset + len, count); 26 | len++; 27 | len += endDate.Decode(buffer, offset + len, count); 28 | return len; 29 | } 30 | 31 | public bool IsAFittingDate(DateTime date) 32 | { 33 | date = new DateTime(date.Year, date.Month, date.Day); 34 | return date >= startDate.toDateTime() && date <= endDate.toDateTime(); 35 | } 36 | 37 | public override string ToString() 38 | { 39 | string ret; 40 | 41 | if (startDate.day != 255) 42 | ret = "From " + startDate; 43 | else 44 | ret = "From **/**/**"; 45 | 46 | if (endDate.day != 255) 47 | ret = ret + " to " + endDate; 48 | else 49 | ret += " to **/**/**"; 50 | 51 | return ret; 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /Base/BacnetDeviceObjectPropertyReference.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetDeviceObjectPropertyReference : ASN1.IEncode 4 | { 5 | public BacnetObjectId objectIdentifier; 6 | public BacnetPropertyIds propertyIdentifier; 7 | public uint arrayIndex; 8 | public BacnetObjectId deviceIndentifier; 9 | 10 | public BacnetDeviceObjectPropertyReference(BacnetObjectId objectIdentifier, BacnetPropertyIds propertyIdentifier, BacnetObjectId? deviceIndentifier = null, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) 11 | { 12 | this.objectIdentifier = objectIdentifier; 13 | this.propertyIdentifier = propertyIdentifier; 14 | this.arrayIndex = arrayIndex; 15 | this.deviceIndentifier = deviceIndentifier ?? new BacnetObjectId(BacnetObjectTypes.MAX_BACNET_OBJECT_TYPE, 0); 16 | } 17 | 18 | public void Encode(EncodeBuffer buffer) 19 | { 20 | ASN1.bacapp_encode_device_obj_property_ref(buffer, this); 21 | } 22 | 23 | public BacnetObjectId ObjectId 24 | { 25 | get => objectIdentifier; 26 | set => objectIdentifier = value; 27 | } 28 | 29 | public int ArrayIndex // shows -1 when it's ASN1.BACNET_ARRAY_ALL 30 | { 31 | get => arrayIndex != ASN1.BACNET_ARRAY_ALL 32 | ? (int)arrayIndex 33 | : -1; 34 | set => arrayIndex = value < 0 35 | ? ASN1.BACNET_ARRAY_ALL 36 | : (uint)value; 37 | } 38 | 39 | public BacnetObjectId? DeviceId // shows null when it's not OBJECT_DEVICE 40 | { 41 | get 42 | { 43 | return deviceIndentifier.type == BacnetObjectTypes.OBJECT_DEVICE 44 | ? (BacnetObjectId?)deviceIndentifier 45 | : null; 46 | } 47 | set 48 | { 49 | deviceIndentifier = value ?? new BacnetObjectId(); 50 | } 51 | } 52 | 53 | public BacnetPropertyIds PropertyId 54 | { 55 | get => propertyIdentifier; 56 | set => propertyIdentifier = value; 57 | } 58 | 59 | public static object Parse(string value) 60 | { 61 | var parts = value.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); 62 | 63 | BacnetObjectId? deviceId = null; 64 | BacnetObjectId objectId; 65 | 66 | switch (parts.Length) 67 | { 68 | case 2: 69 | objectId = BacnetObjectId.Parse(parts[0]); 70 | break; 71 | 72 | case 3: 73 | deviceId = BacnetObjectId.Parse(parts[0]); 74 | objectId = BacnetObjectId.Parse(parts[1]); 75 | break; 76 | 77 | default: 78 | throw new ArgumentException("Invalid format", nameof(value)); 79 | } 80 | 81 | if (!Enum.TryParse(parts.Last(), out BacnetPropertyIds propertyId)) 82 | { 83 | if (!uint.TryParse(parts.Last(), out var vendorSpecificPropertyId)) 84 | throw new ArgumentException("Invalid format of property id", nameof(value)); 85 | 86 | propertyId = (BacnetPropertyIds)vendorSpecificPropertyId; 87 | } 88 | 89 | return new BacnetDeviceObjectPropertyReference 90 | { 91 | DeviceId = deviceId, 92 | ObjectId = objectId, 93 | PropertyId = propertyId, 94 | ArrayIndex = -1 95 | }; 96 | } 97 | 98 | public override string ToString() 99 | { 100 | return DeviceId != null 101 | ? $"{DeviceId}.{ObjectId}.{PropertyId}" 102 | : $"{ObjectId}.{PropertyId}"; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Base/BacnetDeviceStatus.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetDeviceStatus : byte 4 | { 5 | OPERATIONAL = 0, 6 | OPERATIONAL_READONLY = 1, 7 | DOWNLOAD_REQUIRED = 2, 8 | DOWNLOAD_IN_PROGRESS = 3, 9 | NON_OPERATIONAL = 4, 10 | BACKUP_IN_PROGRESS = 5 11 | } 12 | -------------------------------------------------------------------------------- /Base/BacnetError.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetError 4 | { 5 | public BacnetErrorClasses error_class; 6 | public BacnetErrorCodes error_code; 7 | 8 | public BacnetError(BacnetErrorClasses errorClass, BacnetErrorCodes errorCode) 9 | { 10 | error_class = errorClass; 11 | error_code = errorCode; 12 | } 13 | public BacnetError(uint errorClass, uint errorCode) 14 | { 15 | error_class = (BacnetErrorClasses)errorClass; 16 | error_code = (BacnetErrorCodes)errorCode; 17 | } 18 | public override string ToString() 19 | { 20 | return $"{error_class}: {error_code}"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Base/BacnetErrorClasses.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetErrorClasses 4 | { 5 | ERROR_CLASS_DEVICE = 0, 6 | ERROR_CLASS_OBJECT = 1, 7 | ERROR_CLASS_PROPERTY = 2, 8 | ERROR_CLASS_RESOURCES = 3, 9 | ERROR_CLASS_SECURITY = 4, 10 | ERROR_CLASS_SERVICES = 5, 11 | ERROR_CLASS_VT = 6, 12 | ERROR_CLASS_COMMUNICATION = 7, 13 | /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ 14 | /* Enumerated values 64-65535 may be used by others subject to */ 15 | /* the procedures and constraints described in Clause 23. */ 16 | MAX_BACNET_ERROR_CLASS = 8, 17 | /* do the MAX here instead of outside of enum so that 18 | compilers will allocate adequate sized datatype for enum */ 19 | ERROR_CLASS_PROPRIETARY_FIRST = 64, 20 | ERROR_CLASS_PROPRIETARY_LAST = 65535 21 | } 22 | -------------------------------------------------------------------------------- /Base/BacnetErrorCodes.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | // These are sorted in the order given in Clause 18. 4 | // ERROR, REJECT AND ABORT CODES The Class and Code pairings 5 | // are required to be used in accordance with Clause 18. 6 | public enum BacnetErrorCodes 7 | { 8 | /* valid for all classes */ 9 | ERROR_CODE_OTHER = 0, 10 | 11 | /* Error Class - Device */ 12 | ERROR_CODE_DEVICE_BUSY = 3, 13 | ERROR_CODE_CONFIGURATION_IN_PROGRESS = 2, 14 | ERROR_CODE_OPERATIONAL_PROBLEM = 25, 15 | 16 | /* Error Class - Object */ 17 | ERROR_CODE_DYNAMIC_CREATION_NOT_SUPPORTED = 4, 18 | ERROR_CODE_NO_OBJECTS_OF_SPECIFIED_TYPE = 17, 19 | ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED = 23, 20 | ERROR_CODE_OBJECT_IDENTIFIER_ALREADY_EXISTS = 24, 21 | ERROR_CODE_READ_ACCESS_DENIED = 27, 22 | ERROR_CODE_UNKNOWN_OBJECT = 31, 23 | ERROR_CODE_UNSUPPORTED_OBJECT_TYPE = 36, 24 | 25 | /* Error Class - Property */ 26 | ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED = 41, 27 | ERROR_CODE_DATATYPE_NOT_SUPPORTED = 47, 28 | ERROR_CODE_INCONSISTENT_SELECTION_CRITERION = 8, 29 | ERROR_CODE_INVALID_ARRAY_INDEX = 42, 30 | ERROR_CODE_INVALID_DATA_TYPE = 9, 31 | ERROR_CODE_NOT_COV_PROPERTY = 44, 32 | ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED = 45, 33 | ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY = 50, 34 | /* ERROR_CODE_READ_ACCESS_DENIED = 27, */ 35 | ERROR_CODE_UNKNOWN_PROPERTY = 32, 36 | ERROR_CODE_VALUE_OUT_OF_RANGE = 37, 37 | ERROR_CODE_WRITE_ACCESS_DENIED = 40, 38 | 39 | /* Error Class - Resources */ 40 | ERROR_CODE_NO_SPACE_FOR_OBJECT = 18, 41 | ERROR_CODE_NO_SPACE_TO_ADD_LIST_ELEMENT = 19, 42 | ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY = 20, 43 | 44 | /* Error Class - Security */ 45 | ERROR_CODE_AUTHENTICATION_FAILED = 1, 46 | /* ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED = 41, */ 47 | ERROR_CODE_INCOMPATIBLE_SECURITY_LEVELS = 6, 48 | ERROR_CODE_INVALID_OPERATOR_NAME = 12, 49 | ERROR_CODE_KEY_GENERATION_ERROR = 15, 50 | ERROR_CODE_PASSWORD_FAILURE = 26, 51 | ERROR_CODE_SECURITY_NOT_SUPPORTED = 28, 52 | ERROR_CODE_TIMEOUT = 30, 53 | 54 | /* Error Class - Services */ 55 | /* ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED = 41, */ 56 | ERROR_CODE_COV_SUBSCRIPTION_FAILED = 43, 57 | ERROR_CODE_DUPLICATE_NAME = 48, 58 | ERROR_CODE_DUPLICATE_OBJECT_ID = 49, 59 | ERROR_CODE_FILE_ACCESS_DENIED = 5, 60 | ERROR_CODE_INCONSISTENT_PARAMETERS = 7, 61 | ERROR_CODE_INVALID_CONFIGURATION_DATA = 46, 62 | ERROR_CODE_INVALID_FILE_ACCESS_METHOD = 10, 63 | ERROR_CODE_INVALID_FILE_START_POSITION = 11, 64 | ERROR_CODE_INVALID_PARAMETER_DATA_TYPE = 13, 65 | ERROR_CODE_INVALID_TIME_STAMP = 14, 66 | ERROR_CODE_MISSING_REQUIRED_PARAMETER = 16, 67 | /* ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED = 45, */ 68 | ERROR_CODE_PROPERTY_IS_NOT_A_LIST = 22, 69 | ERROR_CODE_SERVICE_REQUEST_DENIED = 29, 70 | 71 | /* Error Class - VT */ 72 | ERROR_CODE_UNKNOWN_VT_CLASS = 34, 73 | ERROR_CODE_UNKNOWN_VT_SESSION = 35, 74 | ERROR_CODE_NO_VT_SESSIONS_AVAILABLE = 21, 75 | ERROR_CODE_VT_SESSION_ALREADY_CLOSED = 38, 76 | ERROR_CODE_VT_SESSION_TERMINATION_FAILURE = 39, 77 | 78 | /* unused */ 79 | ERROR_CODE_RESERVED1 = 33, 80 | /* new error codes from new addenda */ 81 | ERROR_CODE_ABORT_BUFFER_OVERFLOW = 51, 82 | ERROR_CODE_ABORT_INVALID_APDU_IN_THIS_STATE = 52, 83 | ERROR_CODE_ABORT_PREEMPTED_BY_HIGHER_PRIORITY_TASK = 53, 84 | ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED = 54, 85 | ERROR_CODE_ABORT_PROPRIETARY = 55, 86 | ERROR_CODE_ABORT_OTHER = 56, 87 | ERROR_CODE_INVALID_TAG = 57, 88 | ERROR_CODE_NETWORK_DOWN = 58, 89 | ERROR_CODE_REJECT_BUFFER_OVERFLOW = 59, 90 | ERROR_CODE_REJECT_INCONSISTENT_PARAMETERS = 60, 91 | ERROR_CODE_REJECT_INVALID_PARAMETER_DATA_TYPE = 61, 92 | ERROR_CODE_REJECT_INVALID_TAG = 62, 93 | ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER = 63, 94 | ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE = 64, 95 | ERROR_CODE_REJECT_TOO_MANY_ARGUMENTS = 65, 96 | ERROR_CODE_REJECT_UNDEFINED_ENUMERATION = 66, 97 | ERROR_CODE_REJECT_UNRECOGNIZED_SERVICE = 67, 98 | ERROR_CODE_REJECT_PROPRIETARY = 68, 99 | ERROR_CODE_REJECT_OTHER = 69, 100 | ERROR_CODE_UNKNOWN_DEVICE = 70, 101 | ERROR_CODE_UNKNOWN_ROUTE = 71, 102 | ERROR_CODE_VALUE_NOT_INITIALIZED = 72, 103 | ERROR_CODE_INVALID_EVENT_STATE = 73, 104 | ERROR_CODE_NO_ALARM_CONFIGURED = 74, 105 | ERROR_CODE_LOG_BUFFER_FULL = 75, 106 | ERROR_CODE_LOGGED_VALUE_PURGED = 76, 107 | ERROR_CODE_NO_PROPERTY_SPECIFIED = 77, 108 | ERROR_CODE_NOT_CONFIGURED_FOR_TRIGGERED_LOGGING = 78, 109 | ERROR_CODE_UNKNOWN_SUBSCRIPTION = 79, 110 | ERROR_CODE_PARAMETER_OUT_OF_RANGE = 80, 111 | ERROR_CODE_LIST_ELEMENT_NOT_FOUND = 81, 112 | ERROR_CODE_BUSY = 82, 113 | ERROR_CODE_COMMUNICATION_DISABLED = 83, 114 | ERROR_CODE_SUCCESS = 84, 115 | ERROR_CODE_ACCESS_DENIED = 85, 116 | ERROR_CODE_BAD_DESTINATION_ADDRESS = 86, 117 | ERROR_CODE_BAD_DESTINATION_DEVICE_ID = 87, 118 | ERROR_CODE_BAD_SIGNATURE = 88, 119 | ERROR_CODE_BAD_SOURCE_ADDRESS = 89, 120 | ERROR_CODE_BAD_TIMESTAMP = 90, 121 | ERROR_CODE_CANNOT_USE_KEY = 91, 122 | ERROR_CODE_CANNOT_VERIFY_MESSAGE_ID = 92, 123 | ERROR_CODE_CORRECT_KEY_REVISION = 93, 124 | ERROR_CODE_DESTINATION_DEVICE_ID_REQUIRED = 94, 125 | ERROR_CODE_DUPLICATE_MESSAGE = 95, 126 | ERROR_CODE_ENCRYPTION_NOT_CONFIGURED = 96, 127 | ERROR_CODE_ENCRYPTION_REQUIRED = 97, 128 | ERROR_CODE_INCORRECT_KEY = 98, 129 | ERROR_CODE_INVALID_KEY_DATA = 99, 130 | ERROR_CODE_KEY_UPDATE_IN_PROGRESS = 100, 131 | ERROR_CODE_MALFORMED_MESSAGE = 101, 132 | ERROR_CODE_NOT_KEY_SERVER = 102, 133 | ERROR_CODE_SECURITY_NOT_CONFIGURED = 103, 134 | ERROR_CODE_SOURCE_SECURITY_REQUIRED = 104, 135 | ERROR_CODE_TOO_MANY_KEYS = 105, 136 | ERROR_CODE_UNKNOWN_AUTHENTICATION_TYPE = 106, 137 | ERROR_CODE_UNKNOWN_KEY = 107, 138 | ERROR_CODE_UNKNOWN_KEY_REVISION = 108, 139 | ERROR_CODE_UNKNOWN_SOURCE_MESSAGE = 109, 140 | ERROR_CODE_NOT_ROUTER_TO_DNET = 110, 141 | ERROR_CODE_ROUTER_BUSY = 111, 142 | ERROR_CODE_UNKNOWN_NETWORK_MESSAGE = 112, 143 | ERROR_CODE_MESSAGE_TOO_LONG = 113, 144 | ERROR_CODE_SECURITY_ERROR = 114, 145 | ERROR_CODE_ADDRESSING_ERROR = 115, 146 | ERROR_CODE_WRITE_BDT_FAILED = 116, 147 | ERROR_CODE_READ_BDT_FAILED = 117, 148 | ERROR_CODE_REGISTER_FOREIGN_DEVICE_FAILED = 118, 149 | ERROR_CODE_READ_FDT_FAILED = 119, 150 | ERROR_CODE_DELETE_FDT_ENTRY_FAILED = 120, 151 | ERROR_CODE_DISTRIBUTE_BROADCAST_FAILED = 121, 152 | ERROR_CODE_UNKNOWN_FILE_SIZE = 122, 153 | ERROR_CODE_ABORT_APDU_TOO_LONG = 123, 154 | ERROR_CODE_ABORT_APPLICATION_EXCEEDED_REPLY_TIME = 124, 155 | ERROR_CODE_ABORT_OUT_OF_RESOURCES = 125, 156 | ERROR_CODE_ABORT_TSM_TIMEOUT = 126, 157 | ERROR_CODE_ABORT_WINDOW_SIZE_OUT_OF_RANGE = 127, 158 | ERROR_CODE_FILE_FULL = 128, 159 | ERROR_CODE_INCONSISTENT_CONFIGURATION = 129, 160 | ERROR_CODE_INCONSISTENT_OBJECT_TYPE = 130, 161 | ERROR_CODE_INTERNAL_ERROR = 131, 162 | ERROR_CODE_NOT_CONFIGURED = 132, 163 | ERROR_CODE_OUT_OF_MEMORY = 133, 164 | ERROR_CODE_VALUE_TOO_LONG = 134, 165 | ERROR_CODE_ABORT_INSUFFICIENT_SECURITY = 135, 166 | ERROR_CODE_ABORT_SECURITY_ERROR = 136, 167 | ERROR_CODE_DUPLICATE_ENTRY = 137, 168 | ERROR_CODE_INVALID_VALUE_IN_THIS_STATE = 138, 169 | ERROR_CODE_INVALID_OPERATION_IN_THIS_STATE = 139, 170 | ERROR_CODE_LIST_ITEM_NOT_NUMBERED = 140, 171 | ERROR_CODE_LIST_ITEM_NOT_TIMESTAMPED = 141, 172 | ERROR_CODE_INVALID_DATA_ENCODING = 142, 173 | ERROR_CODE_BVLC_FUNCTION_UNKNOWN = 143, 174 | ERROR_CODE_BVLC_PROPRIETARY_FUNCTION_UNKNOWN = 144, 175 | ERROR_CODE_HEADER_ENCODING_ERROR = 145, 176 | ERROR_CODE_HEADER_NOT_UNDERSTOOD = 146, 177 | ERROR_CODE_MESSAGE_INCOMPLETE = 147, 178 | ERROR_CODE_NOT_A_BACNET_SC_HUB = 148, 179 | ERROR_CODE_PAYLOAD_EXPECTED = 149, 180 | ERROR_CODE_UNEXPECTED_DATA = 150, 181 | ERROR_CODE_NODE_DUPLICATE_VMAC = 151, 182 | ERROR_CODE_HTTP_UNEXPECTED_RESPONSE_CODE = 152, 183 | ERROR_CODE_HTTP_NO_UPGRADE = 153, 184 | ERROR_CODE_HTTP_RESOURCE_NOT_LOCAL = 154, 185 | ERROR_CODE_HTTP_PROXY_AUTHENTICATION_FAILED = 155, 186 | ERROR_CODE_HTTP_RESPONSE_TIMEOUT = 156, 187 | ERROR_CODE_HTTP_RESPONSE_SYNTAX_ERROR = 157, 188 | ERROR_CODE_HTTP_RESPONSE_VALUE_ERROR = 158, 189 | ERROR_CODE_HTTP_RESPONSE_MISSING_HEADER = 159, 190 | ERROR_CODE_HTTP_WEBSOCKET_HEADER_ERROR = 160, 191 | ERROR_CODE_HTTP_UPGRADE_REQUIRED = 161, 192 | ERROR_CODE_HTTP_UPGRADE_ERROR = 162, 193 | ERROR_CODE_HTTP_TEMPORARY_UNAVAILABLE = 163, 194 | ERROR_CODE_HTTP_NOT_A_SERVER = 164, 195 | ERROR_CODE_HTTP_ERROR = 165, 196 | ERROR_CODE_WEBSOCKET_SCHEME_NOT_SUPPORTED = 166, 197 | ERROR_CODE_WEBSOCKET_UNKNOWN_CONTROL_MESSAGE = 167, 198 | ERROR_CODE_WEBSOCKET_CLOSE_ERROR = 168, 199 | ERROR_CODE_WEBSOCKET_CLOSED_BY_PEER = 169, 200 | ERROR_CODE_WEBSOCKET_ENDPOINT_LEAVES = 170, 201 | ERROR_CODE_WEBSOCKET_PROTOCOL_ERROR = 171, 202 | ERROR_CODE_WEBSOCKET_DATA_NOT_ACCEPTED = 172, 203 | ERROR_CODE_WEBSOCKET_CLOSED_ABNORMALLY = 173, 204 | ERROR_CODE_WEBSOCKET_DATA_INCONSISTENT = 174, 205 | ERROR_CODE_WEBSOCKET_DATA_AGAINST_POLICY = 175, 206 | ERROR_CODE_WEBSOCKET_FRAME_TOO_LONG = 176, 207 | ERROR_CODE_WEBSOCKET_EXTENSION_MISSING = 177, 208 | ERROR_CODE_WEBSOCKET_REQUEST_UNAVAILABLE = 178, 209 | ERROR_CODE_WEBSOCKET_ERROR = 179, 210 | ERROR_CODE_TLS_CLIENT_CERTIFICATE_ERROR = 180, 211 | ERROR_CODE_TLS_SERVER_CERTIFICATE_ERROR = 181, 212 | ERROR_CODE_TLS_CLIENT_AUTHENTICATION_FAILED = 182, 213 | ERROR_CODE_TLS_SERVER_AUTHENTICATION_FAILED = 183, 214 | ERROR_CODE_TLS_CLIENT_CERTIFICATE_EXPIRED = 184, 215 | ERROR_CODE_TLS_SERVER_CERTIFICATE_EXPIRED = 185, 216 | ERROR_CODE_TLS_CLIENT_CERTIFICATE_REVOKED = 186, 217 | ERROR_CODE_TLS_SERVER_CERTIFICATE_REVOKED = 187, 218 | ERROR_CODE_TLS_ERROR = 188, 219 | ERROR_CODE_DNS_UNAVAILABLE = 189, 220 | ERROR_CODE_DNS_NAME_RESOLUTION_FAILED = 190, 221 | ERROR_CODE_DNS_RESOLVER_FAILURE = 191, 222 | ERROR_CODE_DNS_ERROR = 192, 223 | ERROR_CODE_TCP_CONNECT_TIMEOUT = 193, 224 | ERROR_CODE_TCP_CONNECTION_REFUSED = 194, 225 | ERROR_CODE_TCP_CLOSED_BY_LOCAL = 195, 226 | ERROR_CODE_TCP_CLOSED_OTHER = 196, 227 | ERROR_CODE_TCP_ERROR = 197, 228 | ERROR_CODE_IP_ADDRESS_NOT_REACHABLE = 198, 229 | ERROR_CODE_IP_ERROR = 199, 230 | ERROR_CODE_CERTIFICATE_EXPIRED = 200, 231 | ERROR_CODE_CERTIFICATE_INVALID = 201, 232 | ERROR_CODE_CERTIFICATE_MALFORMED = 202, 233 | ERROR_CODE_CERTIFICATE_REVOKED = 203, 234 | ERROR_CODE_UNKNOWN_SECURITY_KEY = 204, 235 | ERROR_CODE_REFERENCED_PORT_IN_ERROR = 205, 236 | MAX_BACNET_ERROR_CODE = 206, 237 | ERROR_CODE_RESERVED_MAX = 255, 238 | /* Enumerated values 0-255 are reserved for definition by ASHRAE. */ 239 | /* Enumerated values 256-65535 may be used by others subject to */ 240 | /* the procedures and constraints described in Clause 23. */ 241 | /* do the max range inside of enum so that 242 | compilers will allocate adequate sized datatype for enum 243 | which is used to store decoding */ 244 | ERROR_CODE_PROPRIETARY_FIRST = 256, 245 | ERROR_CODE_PROPRIETARY_LAST = 65535 246 | } 247 | -------------------------------------------------------------------------------- /Base/BacnetEventNotificationData.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetEventNotificationData 4 | { 5 | public uint processIdentifier; 6 | public BacnetObjectId initiatingObjectIdentifier; 7 | public BacnetObjectId eventObjectIdentifier; 8 | public BacnetGenericTime timeStamp; 9 | public uint notificationClass; 10 | public byte priority; 11 | public BacnetEventTypes eventType; 12 | public string messageText; /* OPTIONAL - Set to NULL if not being used */ 13 | public BacnetNotifyTypes notifyType; 14 | public bool ackRequired; 15 | public BacnetEventStates fromState; 16 | public BacnetEventStates toState; 17 | 18 | /* 19 | ** Each of these structures in the union maps to a particular eventtype 20 | ** Based on BACnetNotificationParameters 21 | */ 22 | 23 | /* 24 | ** EVENT_CHANGE_OF_BITSTRING 25 | */ 26 | public BacnetBitString changeOfBitstring_referencedBitString; 27 | public BacnetBitString changeOfBitstring_statusFlags; 28 | /* 29 | ** EVENT_CHANGE_OF_STATE 30 | */ 31 | public BacnetPropertyState changeOfState_newState; 32 | public BacnetBitString changeOfState_statusFlags; 33 | /* 34 | ** EVENT_CHANGE_OF_VALUE 35 | */ 36 | public BacnetBitString changeOfValue_changedBits; 37 | public float changeOfValue_changeValue; 38 | public BacnetCOVTypes? changeOfValue_tag; 39 | public BacnetBitString changeOfValue_statusFlags; 40 | /* 41 | ** EVENT_COMMAND_FAILURE 42 | */ 43 | public uint commandFailure_commandValue; 44 | public BacnetBitString commandFailure_statusFlags; 45 | public uint commandFailure_feedbackValue; 46 | /* 47 | ** EVENT_FLOATING_LIMIT 48 | */ 49 | public float floatingLimit_referenceValue; 50 | public BacnetBitString floatingLimit_statusFlags; 51 | public float floatingLimit_setPointValue; 52 | public float floatingLimit_errorLimit; 53 | /* 54 | ** EVENT_OUT_OF_RANGE 55 | */ 56 | public float outOfRange_exceedingValue; 57 | public BacnetBitString outOfRange_statusFlags; 58 | public float outOfRange_deadband; 59 | public float outOfRange_exceededLimit; 60 | /* 61 | ** EVENT_CHANGE_OF_LIFE_SAFETY 62 | */ 63 | public BacnetLifeSafetyStates? changeOfLifeSafety_newState; 64 | public BacnetLifeSafetyModes? changeOfLifeSafety_newMode; 65 | public BacnetBitString changeOfLifeSafety_statusFlags; 66 | public BacnetLifeSafetyOperations? changeOfLifeSafety_operationExpected; 67 | /* 68 | ** EVENT_EXTENDED 69 | ** 70 | ** Not Supported! 71 | */ 72 | /* 73 | ** EVENT_BUFFER_READY 74 | */ 75 | public BacnetDeviceObjectPropertyReference bufferReady_bufferProperty; 76 | public uint bufferReady_previousNotification; 77 | public uint bufferReady_currentNotification; 78 | /* 79 | ** EVENT_UNSIGNED_RANGE 80 | */ 81 | public uint unsignedRange_exceedingValue; 82 | public BacnetBitString unsignedRange_statusFlags; 83 | public uint unsignedRange_exceededLimit; 84 | /* 85 | ** EVENT_EXTENDED 86 | */ 87 | public uint extended_vendorId; 88 | public uint extended_eventType; 89 | public object[] extended_parameters; 90 | /* 91 | ** EVENT_CHANGE_OF_RELIABILITY 92 | */ 93 | public BacnetReliability changeOfReliability_reliability; 94 | public BacnetBitString changeOfReliability_statusFlags; 95 | public BacnetPropertyValue[] changeOfReliability_propertyValues; 96 | 97 | public override string ToString() 98 | { 99 | return $"initiatingObject: {initiatingObjectIdentifier}, eventObject: {eventObjectIdentifier}, " 100 | + $"eventType: {eventType}, notifyType: {notifyType}, timeStamp: {timeStamp}, " 101 | + $"fromState: {fromState}, toState: {toState}" 102 | + (notifyType != BacnetNotifyTypes.NOTIFY_ACK_NOTIFICATION ? $", {GetEventDetails()}" : ""); 103 | } 104 | 105 | private string GetEventDetails() 106 | { 107 | switch (eventType) 108 | { 109 | case BacnetEventTypes.EVENT_CHANGE_OF_BITSTRING: 110 | return $"referencedBitString: {changeOfBitstring_referencedBitString}, statusFlags: {changeOfBitstring_statusFlags}"; 111 | 112 | case BacnetEventTypes.EVENT_CHANGE_OF_STATE: 113 | return $"newState: {changeOfState_newState}, statusFlags: {changeOfState_statusFlags}"; 114 | 115 | case BacnetEventTypes.EVENT_CHANGE_OF_VALUE: 116 | return $"changedBits: {changeOfValue_changedBits}, changeValue: {changeOfValue_changeValue}, " 117 | + $"tag: {changeOfValue_tag}, statusFlags: {changeOfValue_statusFlags}"; 118 | 119 | case BacnetEventTypes.EVENT_FLOATING_LIMIT: 120 | return $"referenceValue: {floatingLimit_referenceValue}, statusFlags: {floatingLimit_statusFlags}, " 121 | + $"setPointValue: {floatingLimit_setPointValue}, errorLimit: {floatingLimit_errorLimit}"; 122 | 123 | case BacnetEventTypes.EVENT_OUT_OF_RANGE: 124 | return $"exceedingValue: {outOfRange_exceedingValue}, statusFlags: {outOfRange_statusFlags}, " 125 | + $"deadband: {outOfRange_deadband}, exceededLimit: {outOfRange_exceededLimit}"; 126 | 127 | case BacnetEventTypes.EVENT_CHANGE_OF_LIFE_SAFETY: 128 | return $"newState: {changeOfLifeSafety_newState}, newMode: {changeOfLifeSafety_newMode}, " 129 | + 130 | $"statusFlags: {changeOfLifeSafety_statusFlags}, operationExpected: {changeOfLifeSafety_operationExpected}"; 131 | 132 | case BacnetEventTypes.EVENT_BUFFER_READY: 133 | return $"bufferProperty: {bufferReady_bufferProperty}, previousNotification: {bufferReady_previousNotification}, " 134 | + $"currentNotification: {bufferReady_currentNotification}"; 135 | 136 | case BacnetEventTypes.EVENT_UNSIGNED_RANGE: 137 | return $"exceedingValue: {unsignedRange_exceedingValue}, statusFlags: {unsignedRange_statusFlags}, " 138 | + $"exceededLimit: {unsignedRange_exceededLimit}"; 139 | 140 | case BacnetEventTypes.EVENT_EXTENDED: 141 | return $"vendorId: {extended_vendorId}, extendedEventType: {extended_eventType}, parameters: [{extended_parameters?.Length ?? 0}]"; 142 | 143 | case BacnetEventTypes.EVENT_CHANGE_OF_RELIABILITY: 144 | var properties = string.Join(", ", changeOfReliability_propertyValues?.Select(p => $"{p.property}")); 145 | return $"reliability: {changeOfReliability_reliability}, statusFlags: {changeOfReliability_statusFlags}, properties: [{properties}]"; 146 | 147 | default: 148 | return "no details"; 149 | } 150 | } 151 | }; 152 | -------------------------------------------------------------------------------- /Base/BacnetGenericTime.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetGenericTime 4 | { 5 | public BacnetTimestampTags Tag; 6 | public DateTime Time; 7 | public ushort Sequence; 8 | 9 | public BacnetGenericTime(DateTime time, BacnetTimestampTags tag, ushort sequence = 0) 10 | { 11 | Time = time; 12 | Tag = tag; 13 | Sequence = sequence; 14 | } 15 | 16 | public override string ToString() 17 | { 18 | return $"{Time}"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Base/BacnetGetEventInformationData.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetGetEventInformationData 4 | { 5 | public BacnetObjectId objectIdentifier; 6 | public BacnetEventStates eventState; 7 | public BacnetBitString acknowledgedTransitions; 8 | public BacnetGenericTime[] eventTimeStamps; //3 9 | public BacnetNotifyTypes notifyType; 10 | public BacnetBitString eventEnable; 11 | public uint[] eventPriorities; //3 12 | } 13 | -------------------------------------------------------------------------------- /Base/BacnetLogRecord.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetLogRecord 4 | { 5 | public DateTime timestamp; 6 | 7 | /* logDatum: CHOICE { */ 8 | public BacnetTrendLogValueType type; 9 | //private BacnetBitString log_status; 10 | //private bool boolean_value; 11 | //private float real_value; 12 | //private uint enum_value; 13 | //private uint unsigned_value; 14 | //private int signed_value; 15 | //private BacnetBitString bitstring_value; 16 | //private bool null_value; 17 | //private BacnetError failure; 18 | //private float time_change; 19 | private object any_value; 20 | /* } */ 21 | 22 | public BacnetBitString statusFlags; 23 | 24 | public BacnetLogRecord(BacnetTrendLogValueType type, object value, DateTime stamp, uint status) 25 | { 26 | this.type = type; 27 | timestamp = stamp; 28 | statusFlags = BacnetBitString.ConvertFromInt(status); 29 | any_value = null; 30 | Value = value; 31 | } 32 | 33 | public object Value 34 | { 35 | get 36 | { 37 | switch (type) 38 | { 39 | case BacnetTrendLogValueType.TL_TYPE_ANY: 40 | return any_value; 41 | case BacnetTrendLogValueType.TL_TYPE_BITS: 42 | return (BacnetBitString)Convert.ChangeType(any_value, typeof(BacnetBitString)); 43 | case BacnetTrendLogValueType.TL_TYPE_BOOL: 44 | return (bool)Convert.ChangeType(any_value, typeof(bool)); 45 | case BacnetTrendLogValueType.TL_TYPE_DELTA: 46 | return (float)Convert.ChangeType(any_value, typeof(float)); 47 | case BacnetTrendLogValueType.TL_TYPE_ENUM: 48 | return (uint)Convert.ChangeType(any_value, typeof(uint)); 49 | case BacnetTrendLogValueType.TL_TYPE_ERROR: 50 | if (any_value != null) 51 | return (BacnetError)Convert.ChangeType(any_value, typeof(BacnetError)); 52 | else 53 | return new BacnetError(BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); 54 | case BacnetTrendLogValueType.TL_TYPE_NULL: 55 | return null; 56 | case BacnetTrendLogValueType.TL_TYPE_REAL: 57 | return (float)Convert.ChangeType(any_value, typeof(float)); 58 | case BacnetTrendLogValueType.TL_TYPE_SIGN: 59 | return (int)Convert.ChangeType(any_value, typeof(int)); 60 | case BacnetTrendLogValueType.TL_TYPE_STATUS: 61 | return (BacnetBitString)Convert.ChangeType(any_value, typeof(BacnetBitString)); 62 | case BacnetTrendLogValueType.TL_TYPE_UNSIGN: 63 | return (uint)Convert.ChangeType(any_value, typeof(uint)); 64 | default: 65 | throw new NotSupportedException(); 66 | } 67 | } 68 | set 69 | { 70 | switch (type) 71 | { 72 | case BacnetTrendLogValueType.TL_TYPE_ANY: 73 | any_value = value; 74 | break; 75 | case BacnetTrendLogValueType.TL_TYPE_BITS: 76 | if (value == null) value = new BacnetBitString(); 77 | if (value.GetType() != typeof(BacnetBitString)) 78 | value = BacnetBitString.ConvertFromInt((uint)Convert.ChangeType(value, typeof(uint))); 79 | any_value = (BacnetBitString)value; 80 | break; 81 | case BacnetTrendLogValueType.TL_TYPE_BOOL: 82 | if (value == null) value = false; 83 | if (value.GetType() != typeof(bool)) 84 | value = (bool)Convert.ChangeType(value, typeof(bool)); 85 | any_value = (bool)value; 86 | break; 87 | case BacnetTrendLogValueType.TL_TYPE_DELTA: 88 | if (value == null) value = (float)0; 89 | if (value.GetType() != typeof(float)) 90 | value = (float)Convert.ChangeType(value, typeof(float)); 91 | any_value = (float)value; 92 | break; 93 | case BacnetTrendLogValueType.TL_TYPE_ENUM: 94 | if (value == null) value = (uint)0; 95 | if (value.GetType() != typeof(uint)) 96 | value = (uint)Convert.ChangeType(value, typeof(uint)); 97 | any_value = (uint)value; 98 | break; 99 | case BacnetTrendLogValueType.TL_TYPE_ERROR: 100 | if (value == null) value = new BacnetError(); 101 | if (value.GetType() != typeof(BacnetError)) 102 | throw new ArgumentException(); 103 | any_value = (BacnetError)value; 104 | break; 105 | case BacnetTrendLogValueType.TL_TYPE_NULL: 106 | if (value != null) throw new ArgumentException(); 107 | any_value = value; 108 | break; 109 | case BacnetTrendLogValueType.TL_TYPE_REAL: 110 | if (value == null) value = (float)0; 111 | if (value.GetType() != typeof(float)) 112 | value = (float)Convert.ChangeType(value, typeof(float)); 113 | any_value = (float)value; 114 | break; 115 | case BacnetTrendLogValueType.TL_TYPE_SIGN: 116 | if (value == null) value = 0; 117 | if (value.GetType() != typeof(int)) 118 | value = (int)Convert.ChangeType(value, typeof(int)); 119 | any_value = (int)value; 120 | break; 121 | case BacnetTrendLogValueType.TL_TYPE_STATUS: 122 | if (value == null) value = new BacnetBitString(); 123 | if (value.GetType() != typeof(BacnetBitString)) 124 | value = BacnetBitString.ConvertFromInt((uint)Convert.ChangeType(value, typeof(uint))); 125 | any_value = (BacnetBitString)value; 126 | break; 127 | case BacnetTrendLogValueType.TL_TYPE_UNSIGN: 128 | if (value == null) value = (uint)0; 129 | if (value.GetType() != typeof(uint)) 130 | value = (uint)Convert.ChangeType(value, typeof(uint)); 131 | any_value = (uint)value; 132 | break; 133 | default: 134 | throw new NotSupportedException(); 135 | } 136 | } 137 | } 138 | 139 | public T GetValue() 140 | { 141 | return (T)Convert.ChangeType(Value, typeof(T)); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Base/BacnetMaxAdpu.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetMaxAdpu : byte 4 | { 5 | MAX_APDU50 = 0, 6 | MAX_APDU128 = 1, 7 | MAX_APDU206 = 2, 8 | MAX_APDU480 = 3, 9 | MAX_APDU1024 = 4, 10 | MAX_APDU1476 = 5 11 | } 12 | -------------------------------------------------------------------------------- /Base/BacnetMaxSegments.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetMaxSegments : byte 4 | { 5 | MAX_SEG0 = 0, 6 | MAX_SEG2 = 0x10, 7 | MAX_SEG4 = 0x20, 8 | MAX_SEG8 = 0x30, 9 | MAX_SEG16 = 0x40, 10 | MAX_SEG32 = 0x50, 11 | MAX_SEG64 = 0x60, 12 | MAX_SEG65 = 0x70 13 | } 14 | -------------------------------------------------------------------------------- /Base/BacnetMstpFrameTypes.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | /* MS/TP Frame Type */ 4 | public enum BacnetMstpFrameTypes : byte 5 | { 6 | /* Frame Types 8 through 127 are reserved by ASHRAE. */ 7 | FRAME_TYPE_TOKEN = 0, 8 | FRAME_TYPE_POLL_FOR_MASTER = 1, 9 | FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER = 2, 10 | FRAME_TYPE_TEST_REQUEST = 3, 11 | FRAME_TYPE_TEST_RESPONSE = 4, 12 | FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY = 5, 13 | FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY = 6, 14 | FRAME_TYPE_REPLY_POSTPONED = 7, 15 | /* Frame Types 128 through 255: Proprietary Frames */ 16 | /* These frames are available to vendors as proprietary (non-BACnet) frames. */ 17 | /* The first two octets of the Data field shall specify the unique vendor */ 18 | /* identification code, most significant octet first, for the type of */ 19 | /* vendor-proprietary frame to be conveyed. The length of the data portion */ 20 | /* of a Proprietary frame shall be in the range of 2 to 501 octets. */ 21 | FRAME_TYPE_PROPRIETARY_MIN = 128, 22 | FRAME_TYPE_PROPRIETARY_MAX = 255 23 | } 24 | -------------------------------------------------------------------------------- /Base/BacnetNetworkMessageTypes.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | /*Network Layer Message Type */ 4 | /*If Bit 7 of the control octet described in 6.2.2 is 1, */ 5 | /* a message type octet shall be present as shown in Figure 6-1. */ 6 | /* The following message types are indicated: */ 7 | public enum BacnetNetworkMessageTypes : byte 8 | { 9 | NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK = 0, 10 | NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK = 1, 11 | NETWORK_MESSAGE_I_COULD_BE_ROUTER_TO_NETWORK = 2, 12 | NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK = 3, 13 | NETWORK_MESSAGE_ROUTER_BUSY_TO_NETWORK = 4, 14 | NETWORK_MESSAGE_ROUTER_AVAILABLE_TO_NETWORK = 5, 15 | NETWORK_MESSAGE_INIT_RT_TABLE = 6, 16 | NETWORK_MESSAGE_INIT_RT_TABLE_ACK = 7, 17 | NETWORK_MESSAGE_ESTABLISH_CONNECTION_TO_NETWORK = 8, 18 | NETWORK_MESSAGE_DISCONNECT_CONNECTION_TO_NETWORK = 9, 19 | NETWORK_MESSAGE_CHALLENGE_REQUEST = 10, 20 | NETWORK_MESSAGE_SECURITY_PAYLOAD = 11, 21 | NETWORK_MESSAGE_SECURITY_RESPONSE = 12, 22 | NETWORK_MESSAGE_REQUEST_KEY_UPDATE = 13, 23 | NETWORK_MESSAGE_UPDATE_KEY_SET = 14, 24 | NETWORK_MESSAGE_UPDATE_DISTRIBUTION_KEY = 15, 25 | NETWORK_MESSAGE_REQUEST_MASTER_KEY = 16, 26 | NETWORK_MESSAGE_SET_MASTER_KEY = 17, 27 | NETWORK_MESSAGE_WHAT_IS_NETWORK_NUMBER = 18, 28 | NETWORK_MESSAGE_NETWORK_NUMBER_IS = 19 29 | /* X'0A' to X'7F': Reserved for use by ASHRAE, */ 30 | /* X'80' to X'FF': Available for vendor proprietary messages */ 31 | } 32 | -------------------------------------------------------------------------------- /Base/BacnetNodeTypes.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetNodeTypes 4 | { 5 | NT_UNKNOWN, 6 | NT_SYSTEM, 7 | NT_NETWORK, 8 | NT_DEVICE, 9 | NT_ORGANIZATIONAL, 10 | NT_AREA, 11 | NT_EQUIPMENT, 12 | NT_POINT, 13 | NT_COLLECTION, 14 | NT_PROPERTY, 15 | NT_FUNCTIONAL, 16 | NT_OTHER, 17 | } 18 | -------------------------------------------------------------------------------- /Base/BacnetNpduControls.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | [Flags] 4 | public enum BacnetNpduControls : byte 5 | { 6 | PriorityNormalMessage = 0, 7 | PriorityUrgentMessage = 1, 8 | PriorityCriticalMessage = 2, 9 | PriorityLifeSafetyMessage = 3, 10 | ExpectingReply = 4, 11 | SourceSpecified = 8, 12 | DestinationSpecified = 32, 13 | NetworkLayerMessage = 128 14 | } 15 | -------------------------------------------------------------------------------- /Base/BacnetObjectDescription.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetObjectDescription 4 | { 5 | public BacnetObjectTypes typeId; 6 | public List propsId; 7 | } 8 | -------------------------------------------------------------------------------- /Base/BacnetObjectId.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | [Serializable] 4 | public struct BacnetObjectId : IComparable 5 | { 6 | public BacnetObjectTypes type; 7 | public uint instance; 8 | 9 | public BacnetObjectTypes Type 10 | { 11 | get => type; 12 | set => type = value; 13 | } 14 | 15 | public uint Instance 16 | { 17 | get => instance; 18 | set => instance = value; 19 | } 20 | 21 | public BacnetObjectId(BacnetObjectTypes type, uint instance) 22 | { 23 | this.type = type; 24 | this.instance = instance; 25 | } 26 | 27 | public override string ToString() 28 | { 29 | return $"{Type}:{Instance}"; 30 | } 31 | 32 | public override int GetHashCode() 33 | { 34 | return ToString().GetHashCode(); 35 | } 36 | 37 | public override bool Equals(object obj) 38 | { 39 | return obj != null && obj.ToString().Equals(ToString()); 40 | } 41 | 42 | public int CompareTo(BacnetObjectId other) 43 | { 44 | if (Type == other.Type) 45 | return Instance.CompareTo(other.Instance); 46 | 47 | if (Type == BacnetObjectTypes.OBJECT_DEVICE) 48 | return -1; 49 | 50 | if (other.Type == BacnetObjectTypes.OBJECT_DEVICE) 51 | return 1; 52 | 53 | // cast to int for comparison otherwise unpredictable behaviour with outbound enum (proprietary type) 54 | return ((int)Type).CompareTo((int)other.Type); 55 | } 56 | 57 | public static bool operator ==(BacnetObjectId a, BacnetObjectId b) 58 | { 59 | return a.Equals(b); 60 | } 61 | 62 | public static bool operator !=(BacnetObjectId a, BacnetObjectId b) 63 | { 64 | return !(a == b); 65 | } 66 | 67 | public static BacnetObjectId Parse(string value) 68 | { 69 | var pattern = new Regex($"(?<{nameof(Type)}>.+):(?<{nameof(Instance)}>.+)"); 70 | 71 | if (string.IsNullOrEmpty(value) || !pattern.IsMatch(value)) 72 | return new BacnetObjectId(); 73 | 74 | var objectType = (BacnetObjectTypes)Enum.Parse(typeof(BacnetObjectTypes), 75 | pattern.Match(value).Groups[nameof(Type)].Value); 76 | 77 | var objectInstance = uint.Parse(pattern.Match(value).Groups[nameof(Instance)].Value); 78 | 79 | return new BacnetObjectId(objectType, objectInstance); 80 | } 81 | 82 | }; 83 | -------------------------------------------------------------------------------- /Base/BacnetObjectTypes.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetObjectTypes : uint 4 | { 5 | OBJECT_ANALOG_INPUT = 0, 6 | OBJECT_ANALOG_OUTPUT = 1, 7 | OBJECT_ANALOG_VALUE = 2, 8 | OBJECT_BINARY_INPUT = 3, 9 | OBJECT_BINARY_OUTPUT = 4, 10 | OBJECT_BINARY_VALUE = 5, 11 | OBJECT_CALENDAR = 6, 12 | OBJECT_COMMAND = 7, 13 | OBJECT_DEVICE = 8, 14 | OBJECT_EVENT_ENROLLMENT = 9, 15 | OBJECT_FILE = 10, 16 | OBJECT_GROUP = 11, 17 | OBJECT_LOOP = 12, 18 | OBJECT_MULTI_STATE_INPUT = 13, 19 | OBJECT_MULTI_STATE_OUTPUT = 14, 20 | OBJECT_NOTIFICATION_CLASS = 15, 21 | OBJECT_PROGRAM = 16, 22 | OBJECT_SCHEDULE = 17, 23 | OBJECT_AVERAGING = 18, 24 | OBJECT_MULTI_STATE_VALUE = 19, 25 | OBJECT_TRENDLOG = 20, 26 | OBJECT_LIFE_SAFETY_POINT = 21, 27 | OBJECT_LIFE_SAFETY_ZONE = 22, 28 | OBJECT_ACCUMULATOR = 23, 29 | OBJECT_PULSE_CONVERTER = 24, 30 | OBJECT_EVENT_LOG = 25, 31 | OBJECT_GLOBAL_GROUP = 26, 32 | OBJECT_TREND_LOG_MULTIPLE = 27, 33 | OBJECT_LOAD_CONTROL = 28, 34 | OBJECT_STRUCTURED_VIEW = 29, 35 | OBJECT_ACCESS_DOOR = 30, 36 | OBJECT_TIMER = 31, /* Addendum 135-2012ay */ 37 | OBJECT_ACCESS_CREDENTIAL = 32, /* Addendum 2008-j */ 38 | OBJECT_ACCESS_POINT = 33, 39 | OBJECT_ACCESS_RIGHTS = 34, 40 | OBJECT_ACCESS_USER = 35, 41 | OBJECT_ACCESS_ZONE = 36, 42 | OBJECT_CREDENTIAL_DATA_INPUT = 37, /* authentication-factor-input */ 43 | OBJECT_NETWORK_SECURITY = 38, /* Addendum 2008-g */ 44 | OBJECT_BITSTRING_VALUE = 39, /* Addendum 2008-w */ 45 | OBJECT_CHARACTERSTRING_VALUE = 40, /* Addendum 2008-w */ 46 | OBJECT_DATE_PATTERN_VALUE = 41, /* Addendum 2008-w */ 47 | OBJECT_DATE_VALUE = 42, /* Addendum 2008-w */ 48 | OBJECT_DATETIME_PATTERN_VALUE = 43, /* Addendum 2008-w */ 49 | OBJECT_DATETIME_VALUE = 44, /* Addendum 2008-w */ 50 | OBJECT_INTEGER_VALUE = 45, /* Addendum 2008-w */ 51 | OBJECT_LARGE_ANALOG_VALUE = 46, /* Addendum 2008-w */ 52 | OBJECT_OCTETSTRING_VALUE = 47, /* Addendum 2008-w */ 53 | OBJECT_POSITIVE_INTEGER_VALUE = 48, /* Addendum 2008-w */ 54 | OBJECT_TIME_PATTERN_VALUE = 49, /* Addendum 2008-w */ 55 | OBJECT_TIME_VALUE = 50, /* Addendum 2008-w */ 56 | OBJECT_NOTIFICATION_FORWARDER = 51, /* Addendum 2010-af */ 57 | OBJECT_ALERT_ENROLLMENT = 52, /* Addendum 2010-af */ 58 | OBJECT_CHANNEL = 53, /* Addendum 2010-aa */ 59 | OBJECT_LIGHTING_OUTPUT = 54, /* Addendum 2010-i */ 60 | OBJECT_BINARY_LIGHTING_OUTPUT = 55, /* Addendum 135-2012az */ 61 | OBJECT_NETWORK_PORT = 56, /* Addendum 135-2012az */ 62 | OBJECT_ELEVATOR_GROUP = 57, /* Addendum 135-2012aq */ 63 | OBJECT_ESCALATOR = 58, /* Addendum 135-2012aq */ 64 | OBJECT_LIFT = 59, /* Addendum 135-2012aq */ 65 | OBJECT_STAGING = 60, /* Addendum 135-2016bd */ 66 | OBJECT_AUDIT_LOG = 61, /* Addendum 135-2016bi */ 67 | OBJECT_AUDIT_REPORTER = 62, /* Addendum 135-2016bi */ 68 | OBJECT_COLOR = 63, /* Addendum 135-2020ca */ 69 | OBJECT_COLOR_TEMPERATURE = 64, /* Addendum 135-2020ca */ 70 | 71 | /* Enumerated values 0-127 are reserved for definition by ASHRAE. */ 72 | /* Enumerated values 128-1023 may be used by others subject to */ 73 | /* the procedures and constraints described in Clause 23. */ 74 | /* do the max range inside of enum so that 75 | compilers will allocate adequate sized datatype for enum 76 | which is used to store decoding */ 77 | 78 | OBJECT_PROPRIETARY_MIN = 128, 79 | OBJECT_PROPRIETARY_MAX = 1023, 80 | MAX_BACNET_OBJECT_TYPE = 1024, 81 | MAX_ASHRAE_OBJECT_TYPE = 65 82 | } 83 | -------------------------------------------------------------------------------- /Base/BacnetPduTypes.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | [Flags] 4 | /* note: these are not the real values, */ 5 | /* but are shifted left for easy encoding */ 6 | public enum BacnetPduTypes : byte 7 | { 8 | PDU_TYPE_CONFIRMED_SERVICE_REQUEST = 0, 9 | SERVER = 1, 10 | NEGATIVE_ACK = 2, 11 | SEGMENTED_RESPONSE_ACCEPTED = 2, 12 | MORE_FOLLOWS = 4, 13 | SEGMENTED_MESSAGE = 8, 14 | PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST = 0x10, 15 | PDU_TYPE_SIMPLE_ACK = 0x20, 16 | PDU_TYPE_COMPLEX_ACK = 0x30, 17 | PDU_TYPE_SEGMENT_ACK = 0x40, 18 | PDU_TYPE_ERROR = 0x50, 19 | PDU_TYPE_REJECT = 0x60, 20 | PDU_TYPE_ABORT = 0x70, 21 | PDU_TYPE_MASK = 0xF0, 22 | } 23 | -------------------------------------------------------------------------------- /Base/BacnetPolarity.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetPolarity : byte 4 | { 5 | POLARITY_NORMAL = 0, 6 | POLARITY_REVERSE = 1 7 | } 8 | -------------------------------------------------------------------------------- /Base/BacnetProgramError.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet.Base; 2 | 3 | public enum BacnetProgramError : ushort 4 | { 5 | PROGRAM_ERROR_NORMAL = 0, 6 | PROGRAM_ERROR_LOAD_FAILED = 1, 7 | PROGRAM_ERROR_INTERNAL = 2, 8 | PROGRAM_ERROR_PROGRAM = 3, 9 | PROGRAM_ERROR_OTHER = 4, 10 | /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ 11 | /* Enumerated values 64-65535 may be used by others subject to */ 12 | /* the procedures and constraints described in Clause 23. */ 13 | /* do the max range inside of enum so that 14 | compilers will allocate adequate sized datatype for enum 15 | which is used to store decoding */ 16 | PROGRAM_ERROR_PROPRIETARY_MIN = 64, 17 | PROGRAM_ERROR_PROPRIETARY_MAX = 65535 18 | } 19 | -------------------------------------------------------------------------------- /Base/BacnetProgramRequest.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet.Base; 2 | 3 | public enum BacnetProgramRequest 4 | { 5 | PROGRAM_REQUEST_READY = 0, 6 | PROGRAM_REQUEST_LOAD = 1, 7 | PROGRAM_REQUEST_RUN = 2, 8 | PROGRAM_REQUEST_HALT = 3, 9 | PROGRAM_REQUEST_RESTART = 4, 10 | PROGRAM_REQUEST_UNLOAD = 5 11 | } 12 | -------------------------------------------------------------------------------- /Base/BacnetProgramState.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet.Base; 2 | 3 | public enum BacnetProgramState 4 | { 5 | PROGRAM_STATE_IDLE = 0, 6 | PROGRAM_STATE_LOADING = 1, 7 | PROGRAM_STATE_RUNNING = 2, 8 | PROGRAM_STATE_WAITING = 3, 9 | PROGRAM_STATE_HALTED = 4, 10 | PROGRAM_STATE_UNLOADING = 5 11 | } 12 | -------------------------------------------------------------------------------- /Base/BacnetPropertyReference.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetPropertyReference 4 | { 5 | public uint propertyIdentifier; 6 | public uint propertyArrayIndex; /* optional */ 7 | 8 | public BacnetPropertyReference(uint id, uint arrayIndex) 9 | { 10 | propertyIdentifier = id; 11 | propertyArrayIndex = arrayIndex; 12 | } 13 | 14 | public BacnetPropertyIds GetPropertyId() 15 | { 16 | return (BacnetPropertyIds)propertyIdentifier; 17 | } 18 | 19 | public override string ToString() 20 | { 21 | return $"{GetPropertyId()}"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Base/BacnetPropertyState.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetPropertyState 4 | { 5 | public enum BacnetPropertyStateTypes 6 | { 7 | BOOLEAN_VALUE, 8 | BINARY_VALUE, 9 | EVENT_TYPE, 10 | POLARITY, 11 | PROGRAM_CHANGE, 12 | PROGRAM_STATE, 13 | REASON_FOR_HALT, 14 | RELIABILITY, 15 | STATE, 16 | SYSTEM_STATUS, 17 | UNITS, 18 | UNSIGNED_VALUE, 19 | LIFE_SAFETY_MODE, 20 | LIFE_SAFETY_STATE 21 | } 22 | 23 | public struct State 24 | { 25 | public bool boolean_value; 26 | public BacnetBinaryPv binaryValue; 27 | public BacnetEventTypes eventType; 28 | public BacnetPolarity polarity; 29 | public BacnetProgramRequest programChange; 30 | public BacnetProgramState programState; 31 | public BacnetProgramError programError; 32 | public BacnetReliability reliability; 33 | public BacnetEventStates state; 34 | public BacnetDeviceStatus systemStatus; 35 | public BacnetUnitsId units; 36 | public uint unsignedValue; 37 | public BacnetLifeSafetyModes lifeSafetyMode; 38 | public BacnetLifeSafetyStates lifeSafetyState; 39 | } 40 | 41 | public BacnetPropertyStateTypes tag; 42 | public State state; 43 | 44 | public override string ToString() 45 | { 46 | return $"{tag}:{state}"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Base/BacnetPropertyValue.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetPropertyValue 4 | { 5 | public BacnetPropertyReference property; 6 | public IList value; 7 | public byte priority; 8 | 9 | public override string ToString() 10 | { 11 | return property.ToString(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Base/BacnetPtpDisconnectReasons.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetPtpDisconnectReasons : byte 4 | { 5 | PTP_DISCONNECT_NO_MORE_DATA = 0, 6 | PTP_DISCONNECT_PREEMPTED = 1, 7 | PTP_DISCONNECT_INVALID_PASSWORD = 2, 8 | PTP_DISCONNECT_OTHER = 3, 9 | } 10 | -------------------------------------------------------------------------------- /Base/BacnetPtpFrameTypes.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetPtpFrameTypes : byte 4 | { 5 | FRAME_TYPE_HEARTBEAT_XOFF = 0, 6 | FRAME_TYPE_HEARTBEAT_XON = 1, 7 | FRAME_TYPE_DATA0 = 2, 8 | FRAME_TYPE_DATA1 = 3, 9 | FRAME_TYPE_DATA_ACK0_XOFF = 4, 10 | FRAME_TYPE_DATA_ACK1_XOFF = 5, 11 | FRAME_TYPE_DATA_ACK0_XON = 6, 12 | FRAME_TYPE_DATA_ACK1_XON = 7, 13 | FRAME_TYPE_DATA_NAK0_XOFF = 8, 14 | FRAME_TYPE_DATA_NAK1_XOFF = 9, 15 | FRAME_TYPE_DATA_NAK0_XON = 0x0A, 16 | FRAME_TYPE_DATA_NAK1_XON = 0x0B, 17 | FRAME_TYPE_CONNECT_REQUEST = 0x0C, 18 | FRAME_TYPE_CONNECT_RESPONSE = 0x0D, 19 | FRAME_TYPE_DISCONNECT_REQUEST = 0x0E, 20 | FRAME_TYPE_DISCONNECT_RESPONSE = 0x0F, 21 | FRAME_TYPE_TEST_REQUEST = 0x14, 22 | FRAME_TYPE_TEST_RESPONSE = 0x15, 23 | FRAME_TYPE_GREETING = 0xFF //special invention 24 | } 25 | -------------------------------------------------------------------------------- /Base/BacnetReadAccessResult.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetReadAccessResult 4 | { 5 | public BacnetObjectId objectIdentifier; 6 | public IList values; 7 | 8 | public BacnetReadAccessResult(BacnetObjectId objectIdentifier, IList values) 9 | { 10 | this.objectIdentifier = objectIdentifier; 11 | this.values = values; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Base/BacnetReadAccessSpecification.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetReadAccessSpecification 4 | { 5 | public BacnetObjectId objectIdentifier; 6 | public IList propertyReferences; 7 | 8 | public BacnetReadAccessSpecification(BacnetObjectId objectIdentifier, IList propertyReferences) 9 | { 10 | this.objectIdentifier = objectIdentifier; 11 | this.propertyReferences = propertyReferences; 12 | } 13 | 14 | public static object Parse(string value) 15 | { 16 | var ret = new BacnetReadAccessSpecification(); 17 | if (string.IsNullOrEmpty(value)) return ret; 18 | var tmp = value.Split(':'); 19 | if (tmp.Length < 2) return ret; 20 | ret.objectIdentifier.type = (BacnetObjectTypes)Enum.Parse(typeof(BacnetObjectTypes), tmp[0]); 21 | ret.objectIdentifier.instance = uint.Parse(tmp[1]); 22 | var refs = new List(); 23 | for (var i = 2; i < tmp.Length; i++) 24 | { 25 | refs.Add(new BacnetPropertyReference 26 | { 27 | propertyArrayIndex = ASN1.BACNET_ARRAY_ALL, 28 | propertyIdentifier = (uint)(BacnetPropertyIds)Enum.Parse(typeof(BacnetPropertyIds), tmp[i]) 29 | }); 30 | } 31 | ret.propertyReferences = refs; 32 | return ret; 33 | } 34 | 35 | public override string ToString() 36 | { 37 | return propertyReferences.Aggregate(objectIdentifier.ToString(), (current, r) => 38 | $"{current}:{(BacnetPropertyIds)r.propertyIdentifier}"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Base/BacnetReadRangeRequestTypes.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet.Serialize; 2 | 3 | public enum BacnetReadRangeRequestTypes 4 | { 5 | RR_BY_POSITION = 1, 6 | RR_BY_SEQUENCE = 2, 7 | RR_BY_TIME = 4, 8 | RR_READ_ALL = 8 9 | } 10 | -------------------------------------------------------------------------------- /Base/BacnetReinitializedStates.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetReinitializedStates 4 | { 5 | BACNET_REINIT_COLDSTART = 0, 6 | BACNET_REINIT_WARMSTART = 1, 7 | BACNET_REINIT_STARTBACKUP = 2, 8 | BACNET_REINIT_ENDBACKUP = 3, 9 | BACNET_REINIT_STARTRESTORE = 4, 10 | BACNET_REINIT_ENDRESTORE = 5, 11 | BACNET_REINIT_ABORTRESTORE = 6, 12 | BACNET_REINIT_ACTIVATE_CHANGES = 7, 13 | BACNET_REINIT_IDLE = 255 14 | } 15 | -------------------------------------------------------------------------------- /Base/BacnetRejectReason.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | /// 4 | /// Possible reason for rejecting the PDU. 5 | /// 6 | /// 7 | /// Enumerated values 0-63 are reserved for definition by ASHRAE. 8 | /// Enumerated values 64-255 may be used by others. 9 | /// 10 | public enum BacnetRejectReason : byte 11 | { 12 | /// 13 | /// Generated in response to a confirmed request APDU that contains a syntax error 14 | /// for which an error code has not been explicitly defined. 15 | /// 16 | OTHER = 0, 17 | 18 | /// 19 | /// A buffer capacity has been exceeded. 20 | /// 21 | BUFFER_OVERFLOW = 1, 22 | 23 | /// 24 | /// Generated in response to a confirmed request APDU that omits a conditional 25 | /// service argument that should be present or contains a conditional service 26 | /// argument that should not be present. This condition could also elicit 27 | /// a Reject PDU with a Reject Reason of . 28 | /// 29 | INCONSISTENT_PARAMETERS = 2, 30 | 31 | /// 32 | /// Generated in response to a confirmed request APDU in which the encoding 33 | /// of one or more of the service parameters does not follow the correct 34 | /// type specification. This condition could also elicit a Reject PDU 35 | /// with a Reject Reason of . 36 | /// 37 | INVALID_PARAMETER_DATA_TYPE = 3, 38 | 39 | /// 40 | /// While parsing a message, an invalid tag was encountered. Since an invalid tag 41 | /// could confuse the parsing logic, any of the following Reject Reasons may also 42 | /// be generated in response to a confirmed request containing an invalid tag: 43 | /// 44 | /// 45 | /// 46 | /// 47 | /// 48 | /// 49 | /// 50 | INVALID_TAG = 4, 51 | 52 | /// 53 | /// Generated in response to a confirmed request APDU that is missing at least one 54 | /// mandatory service argument. This condition could also elicit a Reject PDU with 55 | /// a Reject Reason of . 56 | /// 57 | MISSING_REQUIRED_PARAMETER = 5, 58 | 59 | /// 60 | /// Generated in response to a confirmed request APDU that conveys a parameter 61 | /// whose value is outside the range defined for this service. 62 | /// 63 | PARAMETER_OUT_OF_RANGE = 6, 64 | 65 | /// 66 | /// Generated in response to a confirmed request APDU in which the total number 67 | /// of service arguments is greater than specified for the service. This condition 68 | /// could also elicit a Reject PDU with a Reject Reason of . 69 | /// 70 | TOO_MANY_ARGUMENTS = 7, 71 | 72 | /// 73 | /// Generated in response to a confirmed request APDU in which one or more of 74 | /// the service parameters is decoded as an enumeration that is not defined by 75 | /// the type specification of this parameter. 76 | /// 77 | UNDEFINED_ENUMERATION = 8, 78 | 79 | /// 80 | /// Generated in response to a confirmed request APDU in which the Service Choice 81 | /// field specifies an unknown or unsupported service 82 | /// 83 | RECOGNIZED_SERVICE = 9 84 | } 85 | -------------------------------------------------------------------------------- /Base/BacnetReliability.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetReliability : uint 4 | { 5 | RELIABILITY_NO_FAULT_DETECTED = 0, 6 | RELIABILITY_NO_SENSOR = 1, 7 | RELIABILITY_OVER_RANGE = 2, 8 | RELIABILITY_UNDER_RANGE = 3, 9 | RELIABILITY_OPEN_LOOP = 4, 10 | RELIABILITY_SHORTED_LOOP = 5, 11 | RELIABILITY_NO_OUTPUT = 6, 12 | RELIABILITY_UNRELIABLE_OTHER = 7, 13 | RELIABILITY_PROCESS_ERROR = 8, 14 | RELIABILITY_MULTI_STATE_FAULT = 9, 15 | RELIABILITY_CONFIGURATION_ERROR = 10, 16 | RELIABILITY_MEMBER_FAULT = 11, 17 | RELIABILITY_COMMUNICATION_FAILURE = 12, 18 | RELIABILITY_TRIPPED = 13, 19 | /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ 20 | /* Enumerated values 64-65535 may be used by others subject to */ 21 | /* the procedures and constraints described in Clause 23. */ 22 | /* do the max range inside of enum so that 23 | compilers will allocate adequate sized datatype for enum 24 | which is used to store decoding */ 25 | RELIABILITY_PROPRIETARY_MIN = 64, 26 | RELIABILITY_PROPRIETARY_MAX = 65535 27 | } 28 | -------------------------------------------------------------------------------- /Base/BacnetRestartReason.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | // From Loren Van Spronsen csharp-bacnet 4 | public enum BacnetRestartReason 5 | { 6 | UNKNOWN = 0, 7 | COLD_START = 1, 8 | WARM_START = 2, 9 | DETECTED_POWER_LOST = 3, 10 | DETECTED_POWER_OFF = 4, 11 | HARDWARE_WATCHDOG = 5, 12 | SOFTWARE_WATCHDOG = 6, 13 | SUSPENDED = 7 14 | } 15 | -------------------------------------------------------------------------------- /Base/BacnetResultFlags.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | [Flags] 4 | public enum BacnetResultFlags 5 | { 6 | NONE = 0, 7 | FIRST_ITEM = 1, 8 | LAST_ITEM = 2, 9 | MORE_ITEMS = 4, 10 | } 11 | -------------------------------------------------------------------------------- /Base/BacnetSegmentations.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetSegmentations 4 | { 5 | SEGMENTATION_BOTH = 0, 6 | SEGMENTATION_TRANSMIT = 1, 7 | SEGMENTATION_RECEIVE = 2, 8 | SEGMENTATION_NONE = 3, 9 | } 10 | -------------------------------------------------------------------------------- /Base/BacnetServicesSupported.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetServicesSupported 4 | { 5 | /* Alarm and Event Services */ 6 | SERVICE_SUPPORTED_ACKNOWLEDGE_ALARM = 0, 7 | SERVICE_SUPPORTED_CONFIRMED_COV_NOTIFICATION = 1, 8 | SERVICE_SUPPORTED_CONFIRMED_EVENT_NOTIFICATION = 2, 9 | SERVICE_SUPPORTED_GET_ALARM_SUMMARY = 3, 10 | SERVICE_SUPPORTED_GET_ENROLLMENT_SUMMARY = 4, 11 | SERVICE_SUPPORTED_GET_EVENT_INFORMATION = 39, 12 | SERVICE_SUPPORTED_SUBSCRIBE_COV = 5, 13 | SERVICE_SUPPORTED_SUBSCRIBE_COV_PROPERTY = 38, 14 | SERVICE_SUPPORTED_LIFE_SAFETY_OPERATION = 37, 15 | SERVICE_SUPPORTED_CONFIRMED_AUDIT_NOTIFICATION = 44, 16 | SERVICE_SUPPORTED_AUDIT_LOG_QUERY = 45, 17 | /* File Access Services */ 18 | SERVICE_SUPPORTED_ATOMIC_READ_FILE = 6, 19 | SERVICE_SUPPORTED_ATOMIC_WRITE_FILE = 7, 20 | /* Object Access Services */ 21 | SERVICE_SUPPORTED_ADD_LIST_ELEMENT = 8, 22 | SERVICE_SUPPORTED_REMOVE_LIST_ELEMENT = 9, 23 | SERVICE_SUPPORTED_CREATE_OBJECT = 10, 24 | SERVICE_SUPPORTED_DELETE_OBJECT = 11, 25 | SERVICE_SUPPORTED_READ_PROPERTY = 12, 26 | SERVICE_SUPPORTED_READ_PROP_CONDITIONAL = 13, 27 | SERVICE_SUPPORTED_READ_PROP_MULTIPLE = 14, 28 | SERVICE_SUPPORTED_READ_RANGE = 35, 29 | SERVICE_SUPPORTED_WRITE_PROPERTY = 15, 30 | SERVICE_SUPPORTED_WRITE_PROP_MULTIPLE = 16, 31 | SERVICE_SUPPORTED_WRITE_GROUP = 40, 32 | /* Remote Device Management Services */ 33 | SERVICE_SUPPORTED_DEVICE_COMMUNICATION_CONTROL = 17, 34 | SERVICE_SUPPORTED_PRIVATE_TRANSFER = 18, 35 | SERVICE_SUPPORTED_TEXT_MESSAGE = 19, 36 | SERVICE_SUPPORTED_REINITIALIZE_DEVICE = 20, 37 | SERVICE_SUPPORTED_WHO_AM_I = 47, 38 | SERVICE_SUPPORTED_YOU_ARE = 48, 39 | /* Virtual Terminal Services */ 40 | SERVICE_SUPPORTED_VT_OPEN = 21, 41 | SERVICE_SUPPORTED_VT_CLOSE = 22, 42 | SERVICE_SUPPORTED_VT_DATA = 23, 43 | /* Security Services */ 44 | SERVICE_SUPPORTED_AUTHENTICATE = 24, 45 | SERVICE_SUPPORTED_REQUEST_KEY = 25, 46 | /* Unconfirmed Services */ 47 | SERVICE_SUPPORTED_I_AM = 26, 48 | SERVICE_SUPPORTED_I_HAVE = 27, 49 | SERVICE_SUPPORTED_UNCONFIRMED_COV_NOTIFICATION = 28, 50 | SERVICE_SUPPORTED_UNCONFIRMED_EVENT_NOTIFICATION = 29, 51 | SERVICE_SUPPORTED_UNCONFIRMED_PRIVATE_TRANSFER = 30, 52 | SERVICE_SUPPORTED_UNCONFIRMED_TEXT_MESSAGE = 31, 53 | SERVICE_SUPPORTED_TIME_SYNCHRONIZATION = 32, 54 | SERVICE_SUPPORTED_UTC_TIME_SYNCHRONIZATION = 36, 55 | SERVICE_SUPPORTED_WHO_HAS = 33, 56 | SERVICE_SUPPORTED_WHO_IS = 34, 57 | SERVICE_SUPPORTED_UNCONFIRMED_AUDIT_NOTIFICATION = 46, 58 | /* Other services to be added as they are defined. */ 59 | /* All values in this production are reserved */ 60 | /* for definition by ASHRAE. */ 61 | MAX_BACNET_SERVICES_SUPPORTED = 47 62 | } 63 | -------------------------------------------------------------------------------- /Base/BacnetStatusFlags.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | [Flags] 4 | public enum BacnetStatusFlags 5 | { 6 | STATUS_FLAG_IN_ALARM = 1, 7 | STATUS_FLAG_FAULT = 2, 8 | STATUS_FLAG_OVERRIDDEN = 4, 9 | STATUS_FLAG_OUT_OF_SERVICE = 8 10 | } 11 | -------------------------------------------------------------------------------- /Base/BacnetTimestampTags.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetTimestampTags 4 | { 5 | TIME_STAMP_NONE = -1, 6 | TIME_STAMP_TIME = 0, 7 | TIME_STAMP_SEQUENCE = 1, 8 | TIME_STAMP_DATETIME = 2 9 | } 10 | -------------------------------------------------------------------------------- /Base/BacnetTrendLogValueType.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetTrendLogValueType : byte 4 | { 5 | // Copyright (C) 2009 Peter Mc Shane in Steve Karg Stack, trendlog.h 6 | // Thank's to it's encoding sample, very usefull for this decoding work 7 | TL_TYPE_STATUS = 0, 8 | TL_TYPE_BOOL = 1, 9 | TL_TYPE_REAL = 2, 10 | TL_TYPE_ENUM = 3, 11 | TL_TYPE_UNSIGN = 4, 12 | TL_TYPE_SIGN = 5, 13 | TL_TYPE_BITS = 6, 14 | TL_TYPE_NULL = 7, 15 | TL_TYPE_ERROR = 8, 16 | TL_TYPE_DELTA = 9, 17 | TL_TYPE_ANY = 10 18 | } 19 | -------------------------------------------------------------------------------- /Base/BacnetUnconfirmedServices.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetUnconfirmedServices : byte 4 | { 5 | SERVICE_UNCONFIRMED_I_AM = 0, 6 | SERVICE_UNCONFIRMED_I_HAVE = 1, 7 | SERVICE_UNCONFIRMED_COV_NOTIFICATION = 2, 8 | SERVICE_UNCONFIRMED_EVENT_NOTIFICATION = 3, 9 | SERVICE_UNCONFIRMED_PRIVATE_TRANSFER = 4, 10 | SERVICE_UNCONFIRMED_TEXT_MESSAGE = 5, 11 | SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION = 6, 12 | SERVICE_UNCONFIRMED_WHO_HAS = 7, 13 | SERVICE_UNCONFIRMED_WHO_IS = 8, 14 | SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION = 9, 15 | /* addendum 2010-aa */ 16 | SERVICE_UNCONFIRMED_WRITE_GROUP = 10, 17 | /* addendum 2012-aq */ 18 | SERVICE_UNCONFIRMED_COV_NOTIFICATION_MULTIPLE = 11, 19 | /* addendum 2016-bi */ 20 | SERVICE_UNCONFIRMED_AUDIT_NOTIFICATION = 12, 21 | /* addendum 2016-bz */ 22 | SERVICE_UNCONFIRMED_WHO_AM_I = 13, 23 | SERVICE_UNCONFIRMED_YOU_ARE = 14, 24 | /* Other services to be added as they are defined. */ 25 | /* All choice values in this production are reserved */ 26 | /* for definition by ASHRAE. */ 27 | /* Proprietary extensions are made by using the */ 28 | /* UnconfirmedPrivateTransfer service. See Clause 23. */ 29 | MAX_BACNET_UNCONFIRMED_SERVICE = 15 30 | }; 31 | -------------------------------------------------------------------------------- /Base/BacnetUnitsId.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | // Add FC : from Karg's Stack 4 | public enum BacnetUnitsId 5 | { 6 | UNITS_METERS_PER_SECOND_PER_SECOND = 166, 7 | /* Area */ 8 | UNITS_SQUARE_METERS = 0, 9 | UNITS_SQUARE_CENTIMETERS = 116, 10 | UNITS_SQUARE_FEET = 1, 11 | UNITS_SQUARE_INCHES = 115, 12 | /* Currency */ 13 | UNITS_CURRENCY1 = 105, 14 | UNITS_CURRENCY2 = 106, 15 | UNITS_CURRENCY3 = 107, 16 | UNITS_CURRENCY4 = 108, 17 | UNITS_CURRENCY5 = 109, 18 | UNITS_CURRENCY6 = 110, 19 | UNITS_CURRENCY7 = 111, 20 | UNITS_CURRENCY8 = 112, 21 | UNITS_CURRENCY9 = 113, 22 | UNITS_CURRENCY10 = 114, 23 | /* Electrical */ 24 | UNITS_MILLIAMPERES = 2, 25 | UNITS_AMPERES = 3, 26 | UNITS_AMPERES_PER_METER = 167, 27 | UNITS_AMPERES_PER_SQUARE_METER = 168, 28 | UNITS_AMPERE_SQUARE_METERS = 169, 29 | UNITS_DECIBELS = 199, 30 | UNITS_DECIBELS_MILLIVOLT = 200, 31 | UNITS_DECIBELS_VOLT = 201, 32 | UNITS_FARADS = 170, 33 | UNITS_HENRYS = 171, 34 | UNITS_OHMS = 4, 35 | UNITS_OHM_METERS = 172, 36 | UNITS_MILLIOHMS = 145, 37 | UNITS_KILOHMS = 122, 38 | UNITS_MEGOHMS = 123, 39 | UNITS_MICROSIEMENS = 190, 40 | UNITS_MILLISIEMENS = 202, 41 | UNITS_SIEMENS = 173, /* 1 mho equals 1 siemens */ 42 | UNITS_SIEMENS_PER_METER = 174, 43 | UNITS_TESLAS = 175, 44 | UNITS_VOLTS = 5, 45 | UNITS_MILLIVOLTS = 124, 46 | UNITS_KILOVOLTS = 6, 47 | UNITS_MEGAVOLTS = 7, 48 | UNITS_VOLT_AMPERES = 8, 49 | UNITS_KILOVOLT_AMPERES = 9, 50 | UNITS_MEGAVOLT_AMPERES = 10, 51 | UNITS_VOLT_AMPERES_REACTIVE = 11, 52 | UNITS_KILOVOLT_AMPERES_REACTIVE = 12, 53 | UNITS_MEGAVOLT_AMPERES_REACTIVE = 13, 54 | UNITS_VOLTS_PER_DEGREE_KELVIN = 176, 55 | UNITS_VOLTS_PER_METER = 177, 56 | UNITS_DEGREES_PHASE = 14, 57 | UNITS_POWER_FACTOR = 15, 58 | UNITS_WEBERS = 178, 59 | /* Energy */ 60 | UNITS_JOULES = 16, 61 | UNITS_KILOJOULES = 17, 62 | UNITS_KILOJOULES_PER_KILOGRAM = 125, 63 | UNITS_MEGAJOULES = 126, 64 | UNITS_WATT_HOURS = 18, 65 | UNITS_KILOWATT_HOURS = 19, 66 | UNITS_MEGAWATT_HOURS = 146, 67 | UNITS_WATT_HOURS_REACTIVE = 203, 68 | UNITS_KILOWATT_HOURS_REACTIVE = 204, 69 | UNITS_MEGAWATT_HOURS_REACTIVE = 205, 70 | UNITS_BTUS = 20, 71 | UNITS_KILO_BTUS = 147, 72 | UNITS_MEGA_BTUS = 148, 73 | UNITS_THERMS = 21, 74 | UNITS_TON_HOURS = 22, 75 | /* Enthalpy */ 76 | UNITS_JOULES_PER_KILOGRAM_DRY_AIR = 23, 77 | UNITS_KILOJOULES_PER_KILOGRAM_DRY_AIR = 149, 78 | UNITS_MEGAJOULES_PER_KILOGRAM_DRY_AIR = 150, 79 | UNITS_BTUS_PER_POUND_DRY_AIR = 24, 80 | UNITS_BTUS_PER_POUND = 117, 81 | /* Entropy */ 82 | UNITS_JOULES_PER_DEGREE_KELVIN = 127, 83 | UNITS_KILOJOULES_PER_DEGREE_KELVIN = 151, 84 | UNITS_MEGAJOULES_PER_DEGREE_KELVIN = 152, 85 | UNITS_JOULES_PER_KILOGRAM_DEGREE_KELVIN = 128, 86 | /* Force */ 87 | UNITS_NEWTON = 153, 88 | /* Frequency */ 89 | UNITS_CYCLES_PER_HOUR = 25, 90 | UNITS_CYCLES_PER_MINUTE = 26, 91 | UNITS_HERTZ = 27, 92 | UNITS_KILOHERTZ = 129, 93 | UNITS_MEGAHERTZ = 130, 94 | UNITS_PER_HOUR = 131, 95 | /* Humidity */ 96 | UNITS_GRAMS_OF_WATER_PER_KILOGRAM_DRY_AIR = 28, 97 | UNITS_PERCENT_RELATIVE_HUMIDITY = 29, 98 | /* Length */ 99 | UNITS_MICROMETERS = 194, 100 | UNITS_MILLIMETERS = 30, 101 | UNITS_CENTIMETERS = 118, 102 | UNITS_KILOMETERS = 193, 103 | UNITS_METERS = 31, 104 | UNITS_INCHES = 32, 105 | UNITS_FEET = 33, 106 | /* Light */ 107 | UNITS_CANDELAS = 179, 108 | UNITS_CANDELAS_PER_SQUARE_METER = 180, 109 | UNITS_WATTS_PER_SQUARE_FOOT = 34, 110 | UNITS_WATTS_PER_SQUARE_METER = 35, 111 | UNITS_LUMENS = 36, 112 | UNITS_LUXES = 37, 113 | UNITS_FOOT_CANDLES = 38, 114 | /* Mass */ 115 | UNITS_MILLIGRAMS = 196, 116 | UNITS_GRAMS = 195, 117 | UNITS_KILOGRAMS = 39, 118 | UNITS_POUNDS_MASS = 40, 119 | UNITS_TONS = 41, 120 | /* Mass Flow */ 121 | UNITS_GRAMS_PER_SECOND = 154, 122 | UNITS_GRAMS_PER_MINUTE = 155, 123 | UNITS_KILOGRAMS_PER_SECOND = 42, 124 | UNITS_KILOGRAMS_PER_MINUTE = 43, 125 | UNITS_KILOGRAMS_PER_HOUR = 44, 126 | UNITS_POUNDS_MASS_PER_SECOND = 119, 127 | UNITS_POUNDS_MASS_PER_MINUTE = 45, 128 | UNITS_POUNDS_MASS_PER_HOUR = 46, 129 | UNITS_TONS_PER_HOUR = 156, 130 | /* Power */ 131 | UNITS_MILLIWATTS = 132, 132 | UNITS_WATTS = 47, 133 | UNITS_KILOWATTS = 48, 134 | UNITS_MEGAWATTS = 49, 135 | UNITS_BTUS_PER_HOUR = 50, 136 | UNITS_KILO_BTUS_PER_HOUR = 157, 137 | UNITS_HORSEPOWER = 51, 138 | UNITS_TONS_REFRIGERATION = 52, 139 | /* Pressure */ 140 | UNITS_PASCALS = 53, 141 | UNITS_HECTOPASCALS = 133, 142 | UNITS_KILOPASCALS = 54, 143 | UNITS_MILLIBARS = 134, 144 | UNITS_BARS = 55, 145 | UNITS_POUNDS_FORCE_PER_SQUARE_INCH = 56, 146 | UNITS_MILLIMETERS_OF_WATER = 206, 147 | UNITS_CENTIMETERS_OF_WATER = 57, 148 | UNITS_INCHES_OF_WATER = 58, 149 | UNITS_MILLIMETERS_OF_MERCURY = 59, 150 | UNITS_CENTIMETERS_OF_MERCURY = 60, 151 | UNITS_INCHES_OF_MERCURY = 61, 152 | /* Temperature */ 153 | UNITS_DEGREES_CELSIUS = 62, 154 | UNITS_DEGREES_KELVIN = 63, 155 | UNITS_DEGREES_KELVIN_PER_HOUR = 181, 156 | UNITS_DEGREES_KELVIN_PER_MINUTE = 182, 157 | UNITS_DEGREES_FAHRENHEIT = 64, 158 | UNITS_DEGREE_DAYS_CELSIUS = 65, 159 | UNITS_DEGREE_DAYS_FAHRENHEIT = 66, 160 | UNITS_DELTA_DEGREES_FAHRENHEIT = 120, 161 | UNITS_DELTA_DEGREES_KELVIN = 121, 162 | /* Time */ 163 | UNITS_YEARS = 67, 164 | UNITS_MONTHS = 68, 165 | UNITS_WEEKS = 69, 166 | UNITS_DAYS = 70, 167 | UNITS_HOURS = 71, 168 | UNITS_MINUTES = 72, 169 | UNITS_SECONDS = 73, 170 | UNITS_HUNDREDTHS_SECONDS = 158, 171 | UNITS_MILLISECONDS = 159, 172 | /* Torque */ 173 | UNITS_NEWTON_METERS = 160, 174 | /* Velocity */ 175 | UNITS_MILLIMETERS_PER_SECOND = 161, 176 | UNITS_MILLIMETERS_PER_MINUTE = 162, 177 | UNITS_METERS_PER_SECOND = 74, 178 | UNITS_METERS_PER_MINUTE = 163, 179 | UNITS_METERS_PER_HOUR = 164, 180 | UNITS_KILOMETERS_PER_HOUR = 75, 181 | UNITS_FEET_PER_SECOND = 76, 182 | UNITS_FEET_PER_MINUTE = 77, 183 | UNITS_MILES_PER_HOUR = 78, 184 | /* Volume */ 185 | UNITS_CUBIC_FEET = 79, 186 | UNITS_CUBIC_METERS = 80, 187 | UNITS_IMPERIAL_GALLONS = 81, 188 | UNITS_MILLILITERS = 197, 189 | UNITS_LITERS = 82, 190 | UNITS_US_GALLONS = 83, 191 | /* Volumetric Flow */ 192 | UNITS_CUBIC_FEET_PER_SECOND = 142, 193 | UNITS_CUBIC_FEET_PER_MINUTE = 84, 194 | // One unit in Addendum 135-2012bg 195 | UNITS_MILLION_CUBIC_FEET_PER_MINUTE = 254, 196 | UNITS_CUBIC_FEET_PER_HOUR = 191, 197 | // five units in Addendum 135-2012bg 198 | UNITS_STANDARD_CUBIC_FEET_PER_DAY = 47808, 199 | UNITS_MILLION_STANDARD_CUBIC_FEET_PER_DAY = 47809, 200 | UNITS_THOUSAND_CUBIC_FEET_PER_DAY = 47810, 201 | UNITS_THOUSAND_STANDARD_CUBIC_FEET_PER_DAY = 47811, 202 | UINITS_POUNDS_MASS_PER_DAY = 47812, 203 | UNITS_CUBIC_METERS_PER_SECOND = 85, 204 | UNITS_CUBIC_METERS_PER_MINUTE = 165, 205 | UNITS_CUBIC_METERS_PER_HOUR = 135, 206 | UNITS_IMPERIAL_GALLONS_PER_MINUTE = 86, 207 | UNITS_MILLILITERS_PER_SECOND = 198, 208 | UNITS_LITERS_PER_SECOND = 87, 209 | UNITS_LITERS_PER_MINUTE = 88, 210 | UNITS_LITERS_PER_HOUR = 136, 211 | UNITS_US_GALLONS_PER_MINUTE = 89, 212 | UNITS_US_GALLONS_PER_HOUR = 192, 213 | /* Other */ 214 | UNITS_DEGREES_ANGULAR = 90, 215 | UNITS_DEGREES_CELSIUS_PER_HOUR = 91, 216 | UNITS_DEGREES_CELSIUS_PER_MINUTE = 92, 217 | UNITS_DEGREES_FAHRENHEIT_PER_HOUR = 93, 218 | UNITS_DEGREES_FAHRENHEIT_PER_MINUTE = 94, 219 | UNITS_JOULE_SECONDS = 183, 220 | UNITS_KILOGRAMS_PER_CUBIC_METER = 186, 221 | UNITS_KW_HOURS_PER_SQUARE_METER = 137, 222 | UNITS_KW_HOURS_PER_SQUARE_FOOT = 138, 223 | UNITS_MEGAJOULES_PER_SQUARE_METER = 139, 224 | UNITS_MEGAJOULES_PER_SQUARE_FOOT = 140, 225 | UNITS_NO_UNITS = 95, 226 | UNITS_NEWTON_SECONDS = 187, 227 | UNITS_NEWTONS_PER_METER = 188, 228 | UNITS_PARTS_PER_MILLION = 96, 229 | UNITS_PARTS_PER_BILLION = 97, 230 | UNITS_PERCENT = 98, 231 | UNITS_PERCENT_OBSCURATION_PER_FOOT = 143, 232 | UNITS_PERCENT_OBSCURATION_PER_METER = 144, 233 | UNITS_PERCENT_PER_SECOND = 99, 234 | UNITS_PER_MINUTE = 100, 235 | UNITS_PER_SECOND = 101, 236 | UNITS_PSI_PER_DEGREE_FAHRENHEIT = 102, 237 | UNITS_RADIANS = 103, 238 | UNITS_RADIANS_PER_SECOND = 184, 239 | UNITS_REVOLUTIONS_PER_MINUTE = 104, 240 | UNITS_SQUARE_METERS_PER_NEWTON = 185, 241 | UNITS_WATTS_PER_METER_PER_DEGREE_KELVIN = 189, 242 | UNITS_WATTS_PER_SQUARE_METER_DEGREE_KELVIN = 141, 243 | UNITS_PER_MILLE = 207, 244 | UNITS_GRAMS_PER_GRAM = 208, 245 | UNITS_KILOGRAMS_PER_KILOGRAM = 209, 246 | UNITS_GRAMS_PER_KILOGRAM = 210, 247 | UNITS_MILLIGRAMS_PER_GRAM = 211, 248 | UNITS_MILLIGRAMS_PER_KILOGRAM = 212, 249 | UNITS_GRAMS_PER_MILLILITER = 213, 250 | UNITS_GRAMS_PER_LITER = 214, 251 | UNITS_MILLIGRAMS_PER_LITER = 215, 252 | UNITS_MICROGRAMS_PER_LITER = 216, 253 | UNITS_GRAMS_PER_CUBIC_METER = 217, 254 | UNITS_MILLIGRAMS_PER_CUBIC_METER = 218, 255 | UNITS_MICROGRAMS_PER_CUBIC_METER = 219, 256 | UNITS_NANOGRAMS_PER_CUBIC_METER = 220, 257 | UNITS_GRAMS_PER_CUBIC_CENTIMETER = 221, 258 | UNITS_BECQUERELS = 222, 259 | UNITS_KILOBECQUERELS = 223, 260 | UNITS_MEGABECQUERELS = 224, 261 | UNITS_GRAY = 225, 262 | UNITS_MILLIGRAY = 226, 263 | UNITS_MICROGRAY = 227, 264 | UNITS_SIEVERTS = 228, 265 | UNITS_MILLISIEVERTS = 229, 266 | UNITS_MICROSIEVERTS = 230, 267 | UNITS_MICROSIEVERTS_PER_HOUR = 231, 268 | UNITS_DECIBELS_A = 232, 269 | UNITS_NEPHELOMETRIC_TURBIDITY_UNIT = 233, 270 | UNITS_PH = 234, 271 | UNITS_GRAMS_PER_SQUARE_METER = 235, 272 | // Since Addendum 135-2012ar 273 | UNITS_MINUTES_PER_DEGREE_KELVIN = 236, 274 | UNITS_METER_SQUARED_PER_METER = 237, 275 | UNITS_AMPERE_SECONDS = 238, 276 | UNITS_VOLT_AMPERE_HOURS = 239, 277 | UNITS_KILOVOLT_AMPERE_HOURS = 240, 278 | UNITS_MEGAVOLT_AMPERE_HOURS = 241, 279 | UNITS_VOLT_AMPERE_HOURS_REACTIVE = 242, 280 | UNITS_KILOVOLT_AMPERE_HOURS_REACTIVE = 243, 281 | UNITS_MEGAVOLT_AMPERE_HOURS_REACTIVE = 244, 282 | UNITS_VOLT_SQUARE_HOURS = 245, 283 | UNITS_AMPERE_SQUARE_HOURS = 246, 284 | UNITS_JOULE_PER_HOURS = 247, 285 | UNITS_CUBIC_FEET_PER_DAY = 248, 286 | UNITS_CUBIC_METERS_PER_DAY = 249, 287 | UNITS_WATT_HOURS_PER_CUBIC_METER = 250, 288 | UNITS_JOULES_PER_CUBIC_METER = 251, 289 | UNITS_MOLE_PERCENT = 252, 290 | UNITS_PASCAL_SECONDS = 253, 291 | UNITS_MILLION_STANDARD_CUBIC_FEET_PER_MINUTE = 254, 292 | UNITS_RESERVED_RANGE_MAX = 255, 293 | /* Enumerated values 256-47807 may be used by others 294 | subject to the procedures and constraints described in Clause 23. */ 295 | UNITS_PROPRIETARY_RANGE_MIN = 256, 296 | UNITS_PROPRIETARY_RANGE_MAX = 47807, 297 | /* Enumerated values 47808-49999 are reserved for definition by ASHRAE. */ 298 | UNITS_RESERVED_RANGE_MIN2 = 47808, 299 | UNITS_POUNDS_MASS_PER_DAY = 47812, 300 | /* 47813 - NOT USED */ 301 | UNITS_MILLIREMS = 47814, 302 | UNITS_MILLIREMS_PER_HOUR = 47815, 303 | UNITS_RESERVED_RANGE_MAX2 = 49999, 304 | UNITS_PROPRIETARY_RANGE_MIN2 = 50000, 305 | /* Enumerated values 50000-65535 may be used by others 306 | subject to the procedures and constraints described in Clause 23. */ 307 | /* do the proprietary range inside of enum so that 308 | compilers will allocate adequate sized datatype for enum 309 | which is used to store decoding */ 310 | UNITS_PROPRIETARY_RANGE_MAX2 = 65535 311 | } 312 | -------------------------------------------------------------------------------- /Base/BacnetValue.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetValue 4 | { 5 | public BacnetApplicationTags Tag; 6 | public object Value; 7 | 8 | public BacnetValue(BacnetApplicationTags tag, object value) 9 | { 10 | Tag = tag; 11 | Value = value; 12 | } 13 | 14 | public BacnetValue(object value) 15 | { 16 | Value = value; 17 | Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_NULL; 18 | 19 | //guess at the tag 20 | if (value != null) 21 | Tag = TagFromType(value.GetType()); 22 | } 23 | 24 | public BacnetApplicationTags TagFromType(Type t) 25 | { 26 | if (t == typeof(string)) 27 | return BacnetApplicationTags.BACNET_APPLICATION_TAG_CHARACTER_STRING; 28 | if (t == typeof(int) || t == typeof(short) || t == typeof(sbyte)) 29 | return BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT; 30 | if (t == typeof(uint) || t == typeof(ushort) || t == typeof(byte)) 31 | return BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT; 32 | if (t == typeof(bool)) 33 | return BacnetApplicationTags.BACNET_APPLICATION_TAG_BOOLEAN; 34 | if (t == typeof(float)) 35 | return BacnetApplicationTags.BACNET_APPLICATION_TAG_REAL; 36 | if (t == typeof(double)) 37 | return BacnetApplicationTags.BACNET_APPLICATION_TAG_DOUBLE; 38 | if (t == typeof(BacnetBitString)) 39 | return BacnetApplicationTags.BACNET_APPLICATION_TAG_BIT_STRING; 40 | if (t == typeof(BacnetObjectId)) 41 | return BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID; 42 | if (t == typeof(BacnetError)) 43 | return BacnetApplicationTags.BACNET_APPLICATION_TAG_ERROR; 44 | if (t == typeof(BacnetDeviceObjectPropertyReference)) 45 | return BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE; 46 | if (t.IsEnum) 47 | return BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED; 48 | 49 | return BacnetApplicationTags.BACNET_APPLICATION_TAG_CONTEXT_SPECIFIC_ENCODED; 50 | } 51 | 52 | public T As() 53 | { 54 | if (typeof(T) == typeof(DateTime)) 55 | { 56 | switch (Tag) 57 | { 58 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_DATE: 59 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_DATETIME: 60 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_TIME: 61 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_TIMESTAMP: 62 | return (T)Value; 63 | } 64 | } 65 | 66 | if (typeof(T) == typeof(TimeSpan) && Tag == BacnetApplicationTags.BACNET_APPLICATION_TAG_TIME) 67 | return (T)(dynamic)((DateTime)Value).TimeOfDay; 68 | 69 | if (typeof(T) != typeof(object) && TagFromType(typeof(T)) != Tag) 70 | throw new ArgumentException($"Value with tag {Tag} can't be converted to {typeof(T).Name}"); 71 | 72 | // ReSharper disable once RedundantCast 73 | // This is needed for casting to enums 74 | return (T)(dynamic)Value; 75 | } 76 | 77 | public override string ToString() 78 | { 79 | if (Value == null) 80 | return string.Empty; 81 | 82 | if (Value.GetType() != typeof(byte[])) 83 | return Value.ToString(); 84 | 85 | var tmp = (byte[])Value; 86 | return tmp.Aggregate(string.Empty, (current, b) => 87 | current + b.ToString("X2")); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Base/BacnetWritePriority.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetWritePriority 4 | { 5 | NO_PRIORITY = 0, 6 | MANUAL_LIFE_SAFETY = 1, 7 | AUTOMATIC_LIFE_SAFETY = 2, 8 | UNSPECIFIED_LEVEL_3 = 3, 9 | UNSPECIFIED_LEVEL_4 = 4, 10 | CRITICAL_EQUIPMENT_CONTROL = 5, 11 | MINIMUM_ON_OFF = 6, 12 | UNSPECIFIED_LEVEL_7 = 7, 13 | MANUAL_OPERATOR = 8, 14 | UNSPECIFIED_LEVEL_9 = 9, 15 | UNSPECIFIED_LEVEL_10 = 10, 16 | UNSPECIFIED_LEVEL_11 = 11, 17 | UNSPECIFIED_LEVEL_12 = 12, 18 | UNSPECIFIED_LEVEL_13 = 13, 19 | UNSPECIFIED_LEVEL_14 = 14, 20 | UNSPECIFIED_LEVEL_15 = 15, 21 | LOWEST_AND_DEFAULT = 16 22 | } 23 | -------------------------------------------------------------------------------- /Base/BacnetweekNDay.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct BacnetweekNDay : ASN1.IEncode, ASN1.IDecode 4 | { 5 | public byte month; /* 1 January, 13 Odd, 14 Even, 255 Any */ 6 | public byte week; /* Don't realy understand ??? 1 for day 1 to 7, 2 for ... what's the objective ? boycott it*/ 7 | public byte wday; /* 1=Monday-7=Sunday, 255 any */ 8 | 9 | public BacnetweekNDay(byte day, byte month, byte week = 255) 10 | { 11 | wday = day; 12 | this.month = month; 13 | this.week = week; 14 | } 15 | 16 | public void Encode(EncodeBuffer buffer) 17 | { 18 | buffer.Add(month); 19 | buffer.Add(week); 20 | buffer.Add(wday); 21 | } 22 | 23 | public int Decode(byte[] buffer, int offset, uint count) 24 | { 25 | month = buffer[offset++]; 26 | week = buffer[offset++]; 27 | wday = buffer[offset]; 28 | return 3; 29 | } 30 | 31 | private static string GetDayName(int day) 32 | { 33 | if (day == 7) 34 | day = 0; 35 | 36 | return CultureInfo.CurrentCulture.DateTimeFormat.DayNames[day]; 37 | } 38 | 39 | public override string ToString() 40 | { 41 | string ret = wday != 255 ? GetDayName(wday) : "Every days"; 42 | 43 | if (month != 255) 44 | ret += " on " + CultureInfo.CurrentCulture.DateTimeFormat.MonthNames[month - 1]; 45 | else 46 | ret += " on every month"; 47 | 48 | return ret; 49 | } 50 | 51 | public bool IsAFittingDate(DateTime date) 52 | { 53 | if (date.Month != month && month != 255 && month != 13 && month != 14) 54 | return false; 55 | if (month == 13 && (date.Month & 1) != 1) 56 | return false; 57 | if (month == 14 && (date.Month & 1) == 1) 58 | return false; 59 | 60 | // What about week, too much stupid : boycott it ! 61 | 62 | if (wday == 255) 63 | return true; 64 | if (wday == 7 && date.DayOfWeek == 0) // Sunday 7 for Bacnet, 0 for .NET 65 | return true; 66 | if (wday == (int)date.DayOfWeek) 67 | return true; 68 | 69 | return false; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Base/DeviceReportingRecipient.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public struct DeviceReportingRecipient : ASN1.IEncode 4 | { 5 | public BacnetBitString WeekofDay; 6 | public DateTime toTime, fromTime; 7 | 8 | public BacnetObjectId Id; 9 | public BacnetAddress adr; 10 | 11 | public uint processIdentifier; 12 | public bool Ack_Required; 13 | public BacnetBitString evenType; 14 | 15 | public DeviceReportingRecipient(BacnetValue v0, BacnetValue v1, BacnetValue v2, BacnetValue v3, BacnetValue v4, BacnetValue v5, BacnetValue v6) 16 | { 17 | Id = new BacnetObjectId(); 18 | adr = null; 19 | 20 | WeekofDay = (BacnetBitString)v0.Value; 21 | fromTime = (DateTime)v1.Value; 22 | toTime = (DateTime)v2.Value; 23 | if (v3.Value is BacnetObjectId id) 24 | { 25 | Id = id; 26 | } 27 | else 28 | { 29 | var netdescr = (BacnetValue[])v3.Value; 30 | var s = (ushort)(uint)netdescr[0].Value; 31 | var b = (byte[])netdescr[1].Value; 32 | adr = new BacnetAddress(BacnetAddressTypes.IP, s, b); 33 | } 34 | processIdentifier = (uint)v4.Value; 35 | Ack_Required = (bool)v5.Value; 36 | evenType = (BacnetBitString)v6.Value; 37 | } 38 | 39 | public DeviceReportingRecipient(BacnetBitString weekofDay, DateTime fromTime, DateTime toTime, BacnetObjectId id, uint processIdentifier, bool ackRequired, BacnetBitString evenType) 40 | { 41 | adr = null; 42 | 43 | WeekofDay = weekofDay; 44 | this.toTime = toTime; 45 | this.fromTime = fromTime; 46 | Id = id; 47 | this.processIdentifier = processIdentifier; 48 | Ack_Required = ackRequired; 49 | this.evenType = evenType; 50 | } 51 | 52 | public DeviceReportingRecipient(BacnetBitString weekofDay, DateTime fromTime, DateTime toTime, BacnetAddress adr, uint processIdentifier, bool ackRequired, BacnetBitString evenType) 53 | { 54 | Id = new BacnetObjectId(); 55 | WeekofDay = weekofDay; 56 | this.toTime = toTime; 57 | this.fromTime = fromTime; 58 | this.adr = adr; 59 | this.processIdentifier = processIdentifier; 60 | Ack_Required = ackRequired; 61 | this.evenType = evenType; 62 | } 63 | 64 | public void Encode(EncodeBuffer buffer) 65 | { 66 | ASN1.bacapp_encode_application_data(buffer, new BacnetValue(WeekofDay)); 67 | ASN1.bacapp_encode_application_data(buffer, new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_TIME, fromTime)); 68 | ASN1.bacapp_encode_application_data(buffer, new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_TIME, toTime)); 69 | 70 | if (adr != null) 71 | { 72 | adr.Encode(buffer); 73 | } 74 | else 75 | { 76 | // BacnetObjectId is context specific encoded 77 | ASN1.encode_context_object_id(buffer, 0, Id.type, Id.instance); 78 | } 79 | 80 | ASN1.bacapp_encode_application_data(buffer, new BacnetValue(processIdentifier)); 81 | ASN1.bacapp_encode_application_data(buffer, new BacnetValue(Ack_Required)); 82 | ASN1.bacapp_encode_application_data(buffer, new BacnetValue(evenType)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Base/Enums/BacnetBackupState.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetBackupState 4 | { 5 | IDLE = 0, 6 | PREPARING_FOR_BACKUP = 1, 7 | PREPARING_FOR_RESTORE = 2, 8 | PERFORMING_A_BACKUP = 3, 9 | PERFORMING_A_RESTORE = 4 10 | } 11 | -------------------------------------------------------------------------------- /Base/Enums/BacnetCOVTypes.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetCOVTypes 4 | { 5 | CHANGE_OF_VALUE_BITS, 6 | CHANGE_OF_VALUE_REAL 7 | } 8 | -------------------------------------------------------------------------------- /Base/Enums/BacnetEventEnable.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetEventEnable 4 | { 5 | EVENT_ENABLE_TO_OFFNORMAL = 1, 6 | EVENT_ENABLE_TO_FAULT = 2, 7 | EVENT_ENABLE_TO_NORMAL = 4 8 | } 9 | -------------------------------------------------------------------------------- /Base/Enums/BacnetEventStates.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetEventStates 4 | { 5 | EVENT_STATE_NORMAL = 0, 6 | EVENT_STATE_FAULT = 1, 7 | EVENT_STATE_OFFNORMAL = 2, 8 | EVENT_STATE_HIGH_LIMIT = 3, 9 | EVENT_STATE_LOW_LIMIT = 4, 10 | EVENT_STATE_LIFE_SAFETY_ALARM = 5 11 | } 12 | -------------------------------------------------------------------------------- /Base/Enums/BacnetEventTypes.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetEventTypes 4 | { 5 | EVENT_CHANGE_OF_BITSTRING = 0, 6 | EVENT_CHANGE_OF_STATE = 1, 7 | EVENT_CHANGE_OF_VALUE = 2, 8 | EVENT_COMMAND_FAILURE = 3, 9 | EVENT_FLOATING_LIMIT = 4, 10 | EVENT_OUT_OF_RANGE = 5, 11 | EVENT_COMPLEX_EVENT_TYPE = 6, 12 | [Obsolete("Context tag 7 is deprecated")] 13 | EVENT_BUFFER_READY_OBSOLETE = 7, 14 | EVENT_CHANGE_OF_LIFE_SAFETY = 8, 15 | EVENT_EXTENDED = 9, 16 | EVENT_BUFFER_READY = 10, 17 | EVENT_UNSIGNED_RANGE = 11, 18 | /* -- enumeration value 12 is reserved for future addenda */ 19 | EVENT_ACCESS_EVENT = 13, 20 | EVENT_DOUBLE_OUT_OF_RANGE = 14, 21 | EVENT_SIGNED_OUT_OF_RANGE = 15, 22 | EVENT_UNSIGNED_OUT_OF_RANGE = 16, 23 | EVENT_CHANGE_OF_CHARACTERSTRING = 17, 24 | EVENT_CHANGE_OF_STATUS_FLAGS = 18, 25 | EVENT_CHANGE_OF_RELIABILITY = 19, 26 | EVENT_NONE = 20, 27 | EVENT_CHANGE_OF_DISCRETE_VALUE = 21, 28 | EVENT_CHANGE_OF_TIMER = 22, 29 | /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ 30 | /* Enumerated values 64-65535 may be used by others subject to */ 31 | /* the procedures and constraints described in Clause 23. */ 32 | /* It is expected that these enumerated values will correspond to */ 33 | /* the use of the complex-event-type CHOICE [6] of the */ 34 | /* BACnetNotificationParameters production. */ 35 | /* The last enumeration used in this version is 11. */ 36 | /* do the max range inside of enum so that 37 | compilers will allocate adequate sized datatype for enum 38 | which is used to store decoding */ 39 | EVENT_PROPRIETARY_MIN = 64, 40 | EVENT_PROPRIETARY_MAX = 65535 41 | } 42 | -------------------------------------------------------------------------------- /Base/Enums/BacnetFileAccessMethod.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetFileAccessMethod 4 | { 5 | RECORD_ACCESS = 0, 6 | STREAM_ACCESS = 1 7 | } 8 | -------------------------------------------------------------------------------- /Base/Enums/BacnetLifeSafetyModes.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetLifeSafetyModes 4 | { 5 | MIN_LIFE_SAFETY_MODE = 0, 6 | LIFE_SAFETY_MODE_OFF = 0, 7 | LIFE_SAFETY_MODE_ON = 1, 8 | LIFE_SAFETY_MODE_TEST = 2, 9 | LIFE_SAFETY_MODE_MANNED = 3, 10 | LIFE_SAFETY_MODE_UNMANNED = 4, 11 | LIFE_SAFETY_MODE_ARMED = 5, 12 | LIFE_SAFETY_MODE_DISARMED = 6, 13 | LIFE_SAFETY_MODE_PREARMED = 7, 14 | LIFE_SAFETY_MODE_SLOW = 8, 15 | LIFE_SAFETY_MODE_FAST = 9, 16 | LIFE_SAFETY_MODE_DISCONNECTED = 10, 17 | LIFE_SAFETY_MODE_ENABLED = 11, 18 | LIFE_SAFETY_MODE_DISABLED = 12, 19 | LIFE_SAFETY_MODE_AUTOMATIC_RELEASE_DISABLED = 13, 20 | LIFE_SAFETY_MODE_DEFAULT = 14, 21 | MAX_LIFE_SAFETY_MODE = 15, 22 | /* Enumerated values 0-255 are reserved for definition by ASHRAE. */ 23 | /* Enumerated values 256-65535 may be used by others subject to */ 24 | /* procedures and constraints described in Clause 23. */ 25 | /* do the max range inside of enum so that 26 | compilers will allocate adequate sized datatype for enum 27 | which is used to store decoding */ 28 | LIFE_SAFETY_MODE_PROPRIETARY_MIN = 256, 29 | LIFE_SAFETY_MODE_PROPRIETARY_MAX = 65535 30 | } 31 | -------------------------------------------------------------------------------- /Base/Enums/BacnetLifeSafetyOperations.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetLifeSafetyOperations 4 | { 5 | LIFE_SAFETY_OP_NONE = 0, 6 | LIFE_SAFETY_OP_SILENCE = 1, 7 | LIFE_SAFETY_OP_SILENCE_AUDIBLE = 2, 8 | LIFE_SAFETY_OP_SILENCE_VISUAL = 3, 9 | LIFE_SAFETY_OP_RESET = 4, 10 | LIFE_SAFETY_OP_RESET_ALARM = 5, 11 | LIFE_SAFETY_OP_RESET_FAULT = 6, 12 | LIFE_SAFETY_OP_UNSILENCE = 7, 13 | LIFE_SAFETY_OP_UNSILENCE_AUDIBLE = 8, 14 | LIFE_SAFETY_OP_UNSILENCE_VISUAL = 9, 15 | /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ 16 | /* Enumerated values 64-65535 may be used by others subject to */ 17 | /* procedures and constraints described in Clause 23. */ 18 | /* do the max range inside of enum so that 19 | compilers will allocate adequate sized datatype for enum 20 | which is used to store decoding */ 21 | LIFE_SAFETY_OP_PROPRIETARY_MIN = 64, 22 | LIFE_SAFETY_OP_PROPRIETARY_MAX = 65535 23 | } 24 | -------------------------------------------------------------------------------- /Base/Enums/BacnetLifeSafetyStates.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetLifeSafetyStates 4 | { 5 | MIN_LIFE_SAFETY_STATE = 0, 6 | LIFE_SAFETY_STATE_QUIET = 0, 7 | LIFE_SAFETY_STATE_PRE_ALARM = 1, 8 | LIFE_SAFETY_STATE_ALARM = 2, 9 | LIFE_SAFETY_STATE_FAULT = 3, 10 | LIFE_SAFETY_STATE_FAULT_PRE_ALARM = 4, 11 | LIFE_SAFETY_STATE_FAULT_ALARM = 5, 12 | LIFE_SAFETY_STATE_NOT_READY = 6, 13 | LIFE_SAFETY_STATE_ACTIVE = 7, 14 | LIFE_SAFETY_STATE_TAMPER = 8, 15 | LIFE_SAFETY_STATE_TEST_ALARM = 9, 16 | LIFE_SAFETY_STATE_TEST_ACTIVE = 10, 17 | LIFE_SAFETY_STATE_TEST_FAULT = 11, 18 | LIFE_SAFETY_STATE_TEST_FAULT_ALARM = 12, 19 | LIFE_SAFETY_STATE_HOLDUP = 13, 20 | LIFE_SAFETY_STATE_DURESS = 14, 21 | LIFE_SAFETY_STATE_TAMPER_ALARM = 15, 22 | LIFE_SAFETY_STATE_ABNORMAL = 16, 23 | LIFE_SAFETY_STATE_EMERGENCY_POWER = 17, 24 | LIFE_SAFETY_STATE_DELAYED = 18, 25 | LIFE_SAFETY_STATE_BLOCKED = 19, 26 | LIFE_SAFETY_STATE_LOCAL_ALARM = 20, 27 | LIFE_SAFETY_STATE_GENERAL_ALARM = 21, 28 | LIFE_SAFETY_STATE_SUPERVISORY = 22, 29 | LIFE_SAFETY_STATE_TEST_SUPERVISORY = 23, 30 | MAX_LIFE_SAFETY_STATE = 24, 31 | /* Enumerated values 0-255 are reserved for definition by ASHRAE. */ 32 | /* Enumerated values 256-65535 may be used by others subject to */ 33 | /* procedures and constraints described in Clause 23. */ 34 | /* do the max range inside of enum so that 35 | compilers will allocate adequate sized datatype for enum 36 | which is used to store decoding */ 37 | LIFE_SAFETY_STATE_PROPRIETARY_MIN = 256, 38 | LIFE_SAFETY_STATE_PROPRIETARY_MAX = 65535 39 | } 40 | -------------------------------------------------------------------------------- /Base/Enums/BacnetLimitEnable.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetLimitEnable 4 | { 5 | EVENT_LOW_LIMIT_ENABLE = 1, 6 | EVENT_HIGH_LIMIT_ENABLE = 2 7 | } 8 | -------------------------------------------------------------------------------- /Base/Enums/BacnetNotifyTypes.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public enum BacnetNotifyTypes 4 | { 5 | NOTIFY_ALARM = 0, 6 | NOTIFY_EVENT = 1, 7 | NOTIFY_ACK_NOTIFICATION = 2 8 | } 9 | -------------------------------------------------------------------------------- /GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Common.Logging; 2 | global using PacketDotNet; 3 | global using SharpPcap; 4 | global using SharpPcap.LibPcap; 5 | global using System.Collections.Generic; 6 | global using System.Globalization; 7 | global using System.IO.BACnet.Base; 8 | global using System.IO.BACnet.Serialize; 9 | global using System.IO.Pipes; 10 | global using System.IO.Ports; 11 | global using System.Linq; 12 | global using System.Net; 13 | global using System.Net.NetworkInformation; 14 | global using System.Net.Sockets; 15 | global using System.Reflection; 16 | global using System.Runtime.InteropServices; 17 | global using System.Text; 18 | global using System.Text.RegularExpressions; 19 | global using System.Threading; 20 | global using System.Threading.Tasks; 21 | global using System.Xml.Serialization; 22 | -------------------------------------------------------------------------------- /Helpers/BacnetValuesExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet.Helpers; 2 | 3 | public static class BacnetValuesExtensions 4 | { 5 | public static bool Has(this IList propertyValues, BacnetPropertyIds propertyId) 6 | { 7 | if (propertyValues.All(v => v.property.GetPropertyId() != propertyId)) 8 | return false; 9 | 10 | return propertyValues 11 | .Where(v => v.property.GetPropertyId() == propertyId) 12 | .Any(v => !v.value.HasError()); 13 | } 14 | 15 | public static bool HasError(this IList propertyValues, BacnetErrorCodes error) 16 | { 17 | return propertyValues 18 | .SelectMany(p => p.value) 19 | .Where(v => v.Tag == BacnetApplicationTags.BACNET_APPLICATION_TAG_ERROR) 20 | .Any(v => v.As().error_code == error); 21 | } 22 | 23 | public static bool HasError(this IList propertyValues) 24 | { 25 | return propertyValues.Any(p => p.value.HasError()); 26 | } 27 | 28 | public static bool HasError(this IList values) 29 | { 30 | return values.Any(v => v.Tag == BacnetApplicationTags.BACNET_APPLICATION_TAG_ERROR); 31 | } 32 | 33 | public static object Get(this IList propertyValues, BacnetPropertyIds propertyId) 34 | { 35 | return Get(propertyValues, propertyId); 36 | } 37 | 38 | public static T Get(this IList propertyValues, BacnetPropertyIds propertyId) 39 | { 40 | return GetMany(propertyValues, propertyId).FirstOrDefault(); 41 | } 42 | 43 | public static T[] GetMany(this IList propertyValues, BacnetPropertyIds propertyId) 44 | { 45 | if (!propertyValues.Has(propertyId)) 46 | return new T[0]; 47 | 48 | var property = propertyValues.First(v => v.property.GetPropertyId() == propertyId); 49 | 50 | return property.property.propertyArrayIndex == ASN1.BACNET_ARRAY_ALL 51 | ? property.value.GetMany() 52 | : new[] { property.value[(int)property.property.propertyArrayIndex].As() }; 53 | } 54 | 55 | public static T[] GetMany(this IList values) 56 | { 57 | return values.Select(v => v.As()).ToArray(); 58 | } 59 | 60 | public static T Get(this IList values) 61 | { 62 | return GetMany(values).FirstOrDefault(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /MIT_license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Yabe project 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # .NET library for BACnet 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://raw.githubusercontent.com/ela-compil/BACnet/master/MIT_license.txt) 4 | [![NuGet version](https://badge.fury.io/nu/bacnet.svg)](https://www.nuget.org/packages/BACnet) 5 | [![Donate](https://img.shields.io/badge/%24-donate-ff00ff.svg)](https://www.paypal.me/JakubBartkowiak) 6 | 7 | This library was originally developed by Morten Kvistgaard with a lot of contributions from F. Chaxel and Steve Karg and the [BACnet Stack (in C)](https://sourceforge.net/projects/bacnet/). It is used as a core library in [YABE (Yet Another BACnet Explorer)](https://sourceforge.net/projects/yetanotherbacnetexplorer/). It has been forked here from the SourceForge SVN source, splitted into separate repositories and made available to download via NuGet. 8 | 9 | ## How to use it 10 | 11 | You can get it by grabbing the latest [NuGet package](https://www.nuget.org/packages/BACnet). 12 | 13 | ## Getting Started 14 | 15 | [See examples](https://github.com/ela-compil/BACnet.Examples) to learn how to use this library. 16 | -------------------------------------------------------------------------------- /Serialize/APDU.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet.Serialize; 2 | 3 | public class APDU 4 | { 5 | public static BacnetPduTypes GetDecodedType(byte[] buffer, int offset) 6 | { 7 | return (BacnetPduTypes)buffer[offset]; 8 | } 9 | 10 | public static void SetDecodedType(byte[] buffer, int offset, BacnetPduTypes type) 11 | { 12 | buffer[offset] = (byte)type; 13 | } 14 | 15 | public static int GetDecodedInvokeId(byte[] buffer, int offset) 16 | { 17 | var type = GetDecodedType(buffer, offset); 18 | switch (type & BacnetPduTypes.PDU_TYPE_MASK) 19 | { 20 | case BacnetPduTypes.PDU_TYPE_SIMPLE_ACK: 21 | case BacnetPduTypes.PDU_TYPE_COMPLEX_ACK: 22 | case BacnetPduTypes.PDU_TYPE_ERROR: 23 | case BacnetPduTypes.PDU_TYPE_REJECT: 24 | case BacnetPduTypes.PDU_TYPE_ABORT: 25 | return buffer[offset + 1]; 26 | case BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST: 27 | return buffer[offset + 2]; 28 | default: 29 | return -1; 30 | } 31 | } 32 | 33 | public static void EncodeConfirmedServiceRequest(EncodeBuffer buffer, BacnetPduTypes type, BacnetConfirmedServices service, BacnetMaxSegments maxSegments, 34 | BacnetMaxAdpu maxAdpu, byte invokeId, byte sequenceNumber = 0, byte proposedWindowSize = 0) 35 | { 36 | buffer.buffer[buffer.offset++] = (byte)type; 37 | buffer.buffer[buffer.offset++] = (byte)((byte)maxSegments | (byte)maxAdpu); 38 | buffer.buffer[buffer.offset++] = invokeId; 39 | 40 | if ((type & BacnetPduTypes.SEGMENTED_MESSAGE) > 0) 41 | { 42 | buffer.buffer[buffer.offset++] = sequenceNumber; 43 | buffer.buffer[buffer.offset++] = proposedWindowSize; 44 | } 45 | buffer.buffer[buffer.offset++] = (byte)service; 46 | } 47 | 48 | public static int DecodeConfirmedServiceRequest(byte[] buffer, int offset, out BacnetPduTypes type, out BacnetConfirmedServices service, 49 | out BacnetMaxSegments maxSegments, out BacnetMaxAdpu maxAdpu, out byte invokeId, out byte sequenceNumber, out byte proposedWindowNumber) 50 | { 51 | var orgOffset = offset; 52 | 53 | type = (BacnetPduTypes)buffer[offset++]; 54 | maxSegments = (BacnetMaxSegments)(buffer[offset] & 0xF0); 55 | maxAdpu = (BacnetMaxAdpu)(buffer[offset++] & 0x0F); 56 | invokeId = buffer[offset++]; 57 | 58 | sequenceNumber = 0; 59 | proposedWindowNumber = 0; 60 | if ((type & BacnetPduTypes.SEGMENTED_MESSAGE) > 0) 61 | { 62 | sequenceNumber = buffer[offset++]; 63 | proposedWindowNumber = buffer[offset++]; 64 | } 65 | service = (BacnetConfirmedServices)buffer[offset++]; 66 | 67 | return offset - orgOffset; 68 | } 69 | 70 | public static void EncodeUnconfirmedServiceRequest(EncodeBuffer buffer, BacnetPduTypes type, BacnetUnconfirmedServices service) 71 | { 72 | buffer.buffer[buffer.offset++] = (byte)type; 73 | buffer.buffer[buffer.offset++] = (byte)service; 74 | } 75 | 76 | public static int DecodeUnconfirmedServiceRequest(byte[] buffer, int offset, out BacnetPduTypes type, out BacnetUnconfirmedServices service) 77 | { 78 | var orgOffset = offset; 79 | 80 | type = (BacnetPduTypes)buffer[offset++]; 81 | service = (BacnetUnconfirmedServices)buffer[offset++]; 82 | 83 | return offset - orgOffset; 84 | } 85 | 86 | public static void EncodeSimpleAck(EncodeBuffer buffer, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId) 87 | { 88 | buffer.buffer[buffer.offset++] = (byte)type; 89 | buffer.buffer[buffer.offset++] = invokeId; 90 | buffer.buffer[buffer.offset++] = (byte)service; 91 | } 92 | 93 | public static int DecodeSimpleAck(byte[] buffer, int offset, out BacnetPduTypes type, out BacnetConfirmedServices service, out byte invokeId) 94 | { 95 | var orgOffset = offset; 96 | 97 | type = (BacnetPduTypes)buffer[offset++]; 98 | invokeId = buffer[offset++]; 99 | service = (BacnetConfirmedServices)buffer[offset++]; 100 | 101 | return offset - orgOffset; 102 | } 103 | 104 | public static int EncodeComplexAck(EncodeBuffer buffer, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte sequenceNumber = 0, byte proposedWindowNumber = 0) 105 | { 106 | var len = 3; 107 | buffer.buffer[buffer.offset++] = (byte)type; 108 | buffer.buffer[buffer.offset++] = invokeId; 109 | if ((type & BacnetPduTypes.SEGMENTED_MESSAGE) > 0) 110 | { 111 | buffer.buffer[buffer.offset++] = sequenceNumber; 112 | buffer.buffer[buffer.offset++] = proposedWindowNumber; 113 | len += 2; 114 | } 115 | buffer.buffer[buffer.offset++] = (byte)service; 116 | return len; 117 | } 118 | 119 | public static int DecodeComplexAck(byte[] buffer, int offset, out BacnetPduTypes type, out BacnetConfirmedServices service, out byte invokeId, 120 | out byte sequenceNumber, out byte proposedWindowNumber) 121 | { 122 | var orgOffset = offset; 123 | 124 | type = (BacnetPduTypes)buffer[offset++]; 125 | invokeId = buffer[offset++]; 126 | 127 | sequenceNumber = 0; 128 | proposedWindowNumber = 0; 129 | if ((type & BacnetPduTypes.SEGMENTED_MESSAGE) > 0) 130 | { 131 | sequenceNumber = buffer[offset++]; 132 | proposedWindowNumber = buffer[offset++]; 133 | } 134 | service = (BacnetConfirmedServices)buffer[offset++]; 135 | 136 | return offset - orgOffset; 137 | } 138 | 139 | public static void EncodeSegmentAck(EncodeBuffer buffer, BacnetPduTypes type, byte originalInvokeId, byte sequenceNumber, byte actualWindowSize) 140 | { 141 | buffer.buffer[buffer.offset++] = (byte)type; 142 | buffer.buffer[buffer.offset++] = originalInvokeId; 143 | buffer.buffer[buffer.offset++] = sequenceNumber; 144 | buffer.buffer[buffer.offset++] = actualWindowSize; 145 | } 146 | 147 | public static int DecodeSegmentAck(byte[] buffer, int offset, out BacnetPduTypes type, out byte originalInvokeId, out byte sequenceNumber, out byte actualWindowSize) 148 | { 149 | var orgOffset = offset; 150 | 151 | type = (BacnetPduTypes)buffer[offset++]; 152 | originalInvokeId = buffer[offset++]; 153 | sequenceNumber = buffer[offset++]; 154 | actualWindowSize = buffer[offset++]; 155 | 156 | return offset - orgOffset; 157 | } 158 | 159 | public static void EncodeError(EncodeBuffer buffer, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId) 160 | { 161 | buffer.buffer[buffer.offset++] = (byte)type; 162 | buffer.buffer[buffer.offset++] = invokeId; 163 | buffer.buffer[buffer.offset++] = (byte)service; 164 | } 165 | 166 | public static int DecodeError(byte[] buffer, int offset, out BacnetPduTypes type, out BacnetConfirmedServices service, out byte invokeId) 167 | { 168 | var orgOffset = offset; 169 | 170 | type = (BacnetPduTypes)buffer[offset++]; 171 | invokeId = buffer[offset++]; 172 | service = (BacnetConfirmedServices)buffer[offset++]; 173 | 174 | return offset - orgOffset; 175 | } 176 | 177 | public static void EncodeAbort(EncodeBuffer buffer, BacnetPduTypes type, byte invokeId, BacnetAbortReason reason) 178 | { 179 | EncodeAbortOrReject(buffer, type, invokeId, reason); 180 | } 181 | 182 | public static void EncodeReject(EncodeBuffer buffer, BacnetPduTypes type, byte invokeId, BacnetRejectReason reason) 183 | { 184 | EncodeAbortOrReject(buffer, type, invokeId, reason); 185 | } 186 | 187 | private static void EncodeAbortOrReject(EncodeBuffer buffer, BacnetPduTypes type, byte invokeId, dynamic reason) 188 | { 189 | buffer.buffer[buffer.offset++] = (byte)type; 190 | buffer.buffer[buffer.offset++] = invokeId; 191 | buffer.buffer[buffer.offset++] = (byte)reason; 192 | } 193 | 194 | public static int DecodeAbort(byte[] buffer, int offset, out BacnetPduTypes type, 195 | out byte invokeId, out BacnetAbortReason reason) 196 | { 197 | return DecodeAbortOrReject(buffer, offset, out type, out invokeId, out reason); 198 | } 199 | 200 | public static int DecodeReject(byte[] buffer, int offset, out BacnetPduTypes type, 201 | out byte invokeId, out BacnetRejectReason reason) 202 | { 203 | return DecodeAbortOrReject(buffer, offset, out type, out invokeId, out reason); 204 | } 205 | 206 | private static int DecodeAbortOrReject(byte[] buffer, int offset, 207 | out BacnetPduTypes type, out byte invokeId, out TReason reason) 208 | { 209 | var orgOffset = offset; 210 | 211 | type = (BacnetPduTypes)buffer[offset++]; 212 | invokeId = buffer[offset++]; 213 | reason = (TReason)(dynamic)buffer[offset++]; 214 | 215 | return offset - orgOffset; 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /Serialize/EncodeBuffer.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet.Serialize; 2 | 3 | public class EncodeBuffer 4 | { 5 | public byte[] buffer; //buffer to serialize into 6 | public int offset; //offset in buffer ... will go beyond max_offset (so that you may count what's needed) 7 | public int max_offset; //don't write beyond this offset 8 | public int serialize_counter; //used with 'min_limit' 9 | public int min_limit; //don't write before this limit (used for segmentation) 10 | public EncodeResult result; 11 | public bool expandable; 12 | 13 | public EncodeBuffer() 14 | { 15 | expandable = true; 16 | buffer = new byte[128]; 17 | max_offset = buffer.Length - 1; 18 | } 19 | 20 | public EncodeBuffer(byte[] buffer, int offset) 21 | { 22 | if (buffer == null) buffer = new byte[0]; 23 | expandable = false; 24 | this.buffer = buffer; 25 | this.offset = offset; 26 | max_offset = buffer.Length; 27 | } 28 | 29 | public void Increment() 30 | { 31 | if (offset < max_offset) 32 | { 33 | if (serialize_counter >= min_limit) 34 | offset++; 35 | serialize_counter++; 36 | } 37 | else 38 | { 39 | if (serialize_counter >= min_limit) 40 | offset++; 41 | } 42 | } 43 | 44 | public void Add(byte b) 45 | { 46 | if (offset < max_offset) 47 | { 48 | if (serialize_counter >= min_limit) 49 | buffer[offset] = b; 50 | } 51 | else 52 | { 53 | if (expandable) 54 | { 55 | Array.Resize(ref buffer, buffer.Length * 2); 56 | max_offset = buffer.Length - 1; 57 | if (serialize_counter >= min_limit) 58 | buffer[offset] = b; 59 | } 60 | else 61 | result |= EncodeResult.NotEnoughBuffer; 62 | } 63 | 64 | Increment(); 65 | } 66 | 67 | public void Add(byte[] buffer, int count) 68 | { 69 | for (var i = 0; i < count; i++) 70 | Add(buffer[i]); 71 | } 72 | 73 | public int GetDiff(EncodeBuffer buffer) 74 | { 75 | var diff = Math.Abs(buffer.offset - offset); 76 | diff = Math.Max(Math.Abs(buffer.serialize_counter - serialize_counter), diff); 77 | return diff; 78 | } 79 | 80 | public EncodeBuffer Copy() 81 | { 82 | return new EncodeBuffer 83 | { 84 | buffer = buffer, 85 | max_offset = max_offset, 86 | min_limit = min_limit, 87 | offset = offset, 88 | result = result, 89 | serialize_counter = serialize_counter, 90 | expandable = expandable 91 | }; 92 | } 93 | 94 | public byte[] ToArray() 95 | { 96 | var ret = new byte[offset]; 97 | Array.Copy(buffer, 0, ret, 0, ret.Length); 98 | return ret; 99 | } 100 | 101 | public void Reset(int newOffset) 102 | { 103 | offset = newOffset; 104 | serialize_counter = 0; 105 | result = EncodeResult.Good; 106 | } 107 | 108 | public override string ToString() 109 | { 110 | return offset + " - " + serialize_counter; 111 | } 112 | 113 | public int GetLength() 114 | { 115 | return Math.Min(offset, max_offset); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Serialize/EncodeResult.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet.Serialize; 2 | 3 | [Flags] 4 | public enum EncodeResult 5 | { 6 | Good = 0, 7 | NotEnoughBuffer = 1 8 | } 9 | -------------------------------------------------------------------------------- /Serialize/MSTP.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet.Serialize; 2 | 3 | public class MSTP 4 | { 5 | public const byte MSTP_PREAMBLE1 = 0x55; 6 | public const byte MSTP_PREAMBLE2 = 0xFF; 7 | public const BacnetMaxAdpu MSTP_MAX_APDU = BacnetMaxAdpu.MAX_APDU480; 8 | public const byte MSTP_HEADER_LENGTH = 8; 9 | 10 | public static byte CRC_Calc_Header(byte dataValue, byte crcValue) 11 | { 12 | var crc = (ushort)(crcValue ^ dataValue); 13 | 14 | /* Exclusive OR the terms in the table (top down) */ 15 | crc = (ushort)(crc ^ (crc << 1) ^ (crc << 2) ^ (crc << 3) ^ (crc << 4) ^ (crc << 5) ^ (crc << 6) ^ (crc << 7)); 16 | 17 | /* Combine bits shifted out left hand end */ 18 | return (byte)((crc & 0xfe) ^ ((crc >> 8) & 1)); 19 | } 20 | 21 | public static byte CRC_Calc_Header(byte[] buffer, int offset, int length) 22 | { 23 | byte crc = 0xff; 24 | for (var i = offset; i < offset + length; i++) 25 | crc = CRC_Calc_Header(buffer[i], crc); 26 | return (byte)~crc; 27 | } 28 | 29 | public static ushort CRC_Calc_Data(byte dataValue, ushort crcValue) 30 | { 31 | var crcLow = (ushort)((crcValue & 0xff) ^ dataValue); 32 | 33 | /* Exclusive OR the terms in the table (top down) */ 34 | return (ushort)((crcValue >> 8) ^ (crcLow << 8) ^ (crcLow << 3) 35 | ^ (crcLow << 12) ^ (crcLow >> 4) 36 | ^ (crcLow & 0x0f) ^ ((crcLow & 0x0f) << 7)); 37 | } 38 | 39 | public static ushort CRC_Calc_Data(byte[] buffer, int offset, int length) 40 | { 41 | ushort crc = 0xffff; 42 | for (var i = offset; i < offset + length; i++) 43 | crc = CRC_Calc_Data(buffer[i], crc); 44 | return (ushort)~crc; 45 | } 46 | 47 | public static int Encode(byte[] buffer, int offset, BacnetMstpFrameTypes frameType, byte destinationAddress, byte sourceAddress, int msgLength) 48 | { 49 | buffer[offset + 0] = MSTP_PREAMBLE1; 50 | buffer[offset + 1] = MSTP_PREAMBLE2; 51 | buffer[offset + 2] = (byte)frameType; 52 | buffer[offset + 3] = destinationAddress; 53 | buffer[offset + 4] = sourceAddress; 54 | buffer[offset + 5] = (byte)((msgLength & 0xFF00) >> 8); 55 | buffer[offset + 6] = (byte)((msgLength & 0x00FF) >> 0); 56 | buffer[offset + 7] = CRC_Calc_Header(buffer, offset + 2, 5); 57 | if (msgLength > 0) 58 | { 59 | //calculate data crc 60 | var dataCrc = CRC_Calc_Data(buffer, offset + 8, msgLength); 61 | buffer[offset + 8 + msgLength + 0] = (byte)(dataCrc & 0xFF); //LSB first! 62 | buffer[offset + 8 + msgLength + 1] = (byte)(dataCrc >> 8); 63 | } 64 | //optional pad (0xFF) 65 | return MSTP_HEADER_LENGTH + msgLength + (msgLength > 0 ? 2 : 0); 66 | } 67 | 68 | public static int Decode(byte[] buffer, int offset, int maxLength, out BacnetMstpFrameTypes frameType, out byte destinationAddress, out byte sourceAddress, out int msgLength) 69 | { 70 | if (maxLength < MSTP_HEADER_LENGTH) //not enough data 71 | { 72 | frameType = BacnetMstpFrameTypes.FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY; // don't care 73 | destinationAddress = sourceAddress = 0; // don't care 74 | msgLength = 0; 75 | return -1; 76 | } 77 | 78 | frameType = (BacnetMstpFrameTypes)buffer[offset + 2]; 79 | destinationAddress = buffer[offset + 3]; 80 | sourceAddress = buffer[offset + 4]; 81 | msgLength = (buffer[offset + 5] << 8) | (buffer[offset + 6] << 0); 82 | var crcHeader = buffer[offset + 7]; 83 | ushort crcData = 0; 84 | 85 | if (msgLength > 0) 86 | { 87 | if (offset + 8 + msgLength + 1 >= buffer.Length) 88 | return -1; 89 | 90 | crcData = (ushort)((buffer[offset + 8 + msgLength + 1] << 8) | (buffer[offset + 8 + msgLength + 0] << 0)); 91 | } 92 | 93 | if (buffer[offset + 0] != MSTP_PREAMBLE1) 94 | return -1; 95 | 96 | if (buffer[offset + 1] != MSTP_PREAMBLE2) 97 | return -1; 98 | 99 | if (CRC_Calc_Header(buffer, offset + 2, 5) != crcHeader) 100 | return -1; 101 | 102 | if (msgLength > 0 && maxLength >= MSTP_HEADER_LENGTH + msgLength + 2 && CRC_Calc_Data(buffer, offset + 8, msgLength) != crcData) 103 | return -1; 104 | 105 | return 8 + msgLength + (msgLength > 0 ? 2 : 0); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Serialize/NPDU.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet.Serialize; 2 | 3 | public class NPDU 4 | { 5 | public const byte BACNET_PROTOCOL_VERSION = 1; 6 | 7 | public static BacnetNpduControls DecodeFunction(byte[] buffer, int offset) 8 | { 9 | if (buffer[offset + 0] != BACNET_PROTOCOL_VERSION) return 0; 10 | return (BacnetNpduControls)buffer[offset + 1]; 11 | } 12 | 13 | public static int Decode(byte[] buffer, int offset, out BacnetNpduControls function, out BacnetAddress destination, 14 | out BacnetAddress source, out byte hopCount, out BacnetNetworkMessageTypes networkMsgType, out ushort vendorId) 15 | { 16 | var orgOffset = offset; 17 | 18 | offset++; 19 | function = (BacnetNpduControls)buffer[offset++]; 20 | 21 | destination = null; 22 | if ((function & BacnetNpduControls.DestinationSpecified) == BacnetNpduControls.DestinationSpecified) 23 | { 24 | destination = new BacnetAddress(BacnetAddressTypes.None, (ushort)((buffer[offset++] << 8) | (buffer[offset++] << 0)), null); 25 | int adrLen = buffer[offset++]; 26 | if (adrLen > 0) 27 | { 28 | destination.adr = new byte[adrLen]; 29 | for (var i = 0; i < destination.adr.Length; i++) 30 | destination.adr[i] = buffer[offset++]; 31 | } 32 | } 33 | 34 | source = null; 35 | if ((function & BacnetNpduControls.SourceSpecified) == BacnetNpduControls.SourceSpecified) 36 | { 37 | source = new BacnetAddress(BacnetAddressTypes.None, (ushort)((buffer[offset++] << 8) | (buffer[offset++] << 0)), null); 38 | int adrLen = buffer[offset++]; 39 | if (adrLen > 0) 40 | { 41 | source.adr = new byte[adrLen]; 42 | for (var i = 0; i < source.adr.Length; i++) 43 | source.adr[i] = buffer[offset++]; 44 | } 45 | } 46 | 47 | hopCount = 0; 48 | if ((function & BacnetNpduControls.DestinationSpecified) == BacnetNpduControls.DestinationSpecified) 49 | { 50 | hopCount = buffer[offset++]; 51 | } 52 | 53 | networkMsgType = BacnetNetworkMessageTypes.NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK; 54 | vendorId = 0; 55 | if (function.HasFlag(BacnetNpduControls.NetworkLayerMessage)) 56 | { 57 | networkMsgType = (BacnetNetworkMessageTypes)buffer[offset++]; 58 | if ((byte)networkMsgType >= 0x80) 59 | { 60 | vendorId = (ushort)((buffer[offset++] << 8) | (buffer[offset++] << 0)); 61 | } 62 | //DAL - this originally made no sense as the higher level code would just ignore network messages 63 | // else if (networkMsgType == BacnetNetworkMessageTypes.NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK) 64 | // offset += 2; // Don't care about destination network adress 65 | } 66 | 67 | if (buffer[orgOffset + 0] != BACNET_PROTOCOL_VERSION) 68 | return -1; 69 | 70 | return offset - orgOffset; 71 | } 72 | 73 | public static void Encode(EncodeBuffer buffer, BacnetNpduControls function, BacnetAddress destination, 74 | BacnetAddress source, byte hopCount, BacnetNetworkMessageTypes networkMsgType, ushort vendorId) 75 | { 76 | Encode(buffer, function, destination, source, hopCount); 77 | 78 | if (function.HasFlag(BacnetNpduControls.NetworkLayerMessage)) // sure it is, otherwise the other Encode is used 79 | { 80 | buffer.buffer[buffer.offset++] = (byte)networkMsgType; 81 | if ((byte)networkMsgType >= 0x80) // who used this ??? sure nobody ! 82 | { 83 | buffer.buffer[buffer.offset++] = (byte)((vendorId & 0xFF00) >> 8); 84 | buffer.buffer[buffer.offset++] = (byte)((vendorId & 0x00FF) >> 0); 85 | } 86 | } 87 | } 88 | 89 | public static void Encode(EncodeBuffer buffer, BacnetNpduControls function, BacnetAddress destination, 90 | BacnetAddress source = null, byte hopCount = 0xFF) 91 | { 92 | // Modif FC 93 | var hasDestination = destination != null && destination.net > 0; // && destination.net != 0xFFFF; 94 | var hasSource = source != null && source.net > 0 && source.net != 0xFFFF; 95 | 96 | buffer.buffer[buffer.offset++] = BACNET_PROTOCOL_VERSION; 97 | buffer.buffer[buffer.offset++] = (byte)(function | (hasDestination ? BacnetNpduControls.DestinationSpecified : 0) | (hasSource ? BacnetNpduControls.SourceSpecified : 0)); 98 | 99 | if (hasDestination) 100 | { 101 | buffer.buffer[buffer.offset++] = (byte)((destination.net & 0xFF00) >> 8); 102 | buffer.buffer[buffer.offset++] = (byte)((destination.net & 0x00FF) >> 0); 103 | 104 | if (destination.net == 0xFFFF) //patch by F. Chaxel 105 | buffer.buffer[buffer.offset++] = 0; 106 | else 107 | { 108 | buffer.buffer[buffer.offset++] = (byte)destination.adr.Length; 109 | if (destination.adr.Length > 0) 110 | { 111 | foreach (var t in destination.adr) 112 | buffer.buffer[buffer.offset++] = t; 113 | } 114 | } 115 | } 116 | 117 | if (hasSource) 118 | { 119 | buffer.buffer[buffer.offset++] = (byte)((source.net & 0xFF00) >> 8); 120 | buffer.buffer[buffer.offset++] = (byte)((source.net & 0x00FF) >> 0); 121 | buffer.buffer[buffer.offset++] = (byte)source.adr.Length; 122 | if (source.adr.Length > 0) 123 | { 124 | foreach (var t in source.adr) 125 | buffer.buffer[buffer.offset++] = t; 126 | } 127 | } 128 | 129 | if (hasDestination) 130 | { 131 | buffer.buffer[buffer.offset++] = hopCount; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Serialize/PTP.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet.Serialize; 2 | 3 | public class PTP 4 | { 5 | public const byte PTP_PREAMBLE1 = 0x55; 6 | public const byte PTP_PREAMBLE2 = 0xFF; 7 | public const byte PTP_GREETING_PREAMBLE1 = 0x42; 8 | public const byte PTP_GREETING_PREAMBLE2 = 0x41; 9 | public const BacnetMaxAdpu PTP_MAX_APDU = BacnetMaxAdpu.MAX_APDU480; 10 | public const byte PTP_HEADER_LENGTH = 6; 11 | 12 | public static int Encode(byte[] buffer, int offset, BacnetPtpFrameTypes frameType, int msgLength) 13 | { 14 | buffer[offset + 0] = PTP_PREAMBLE1; 15 | buffer[offset + 1] = PTP_PREAMBLE2; 16 | buffer[offset + 2] = (byte)frameType; 17 | buffer[offset + 3] = (byte)((msgLength & 0xFF00) >> 8); 18 | buffer[offset + 4] = (byte)((msgLength & 0x00FF) >> 0); 19 | buffer[offset + 5] = MSTP.CRC_Calc_Header(buffer, offset + 2, 3); 20 | if (msgLength > 0) 21 | { 22 | //calculate data crc 23 | var dataCrc = MSTP.CRC_Calc_Data(buffer, offset + 6, msgLength); 24 | buffer[offset + 6 + msgLength + 0] = (byte)(dataCrc & 0xFF); //LSB first! 25 | buffer[offset + 6 + msgLength + 1] = (byte)(dataCrc >> 8); 26 | } 27 | return PTP_HEADER_LENGTH + msgLength + (msgLength > 0 ? 2 : 0); 28 | } 29 | 30 | public static int Decode(byte[] buffer, int offset, int maxLength, out BacnetPtpFrameTypes frameType, out int msgLength) 31 | { 32 | if (maxLength < PTP_HEADER_LENGTH) // not enough data 33 | { 34 | frameType = BacnetPtpFrameTypes.FRAME_TYPE_CONNECT_REQUEST; // don't care 35 | msgLength = 0; 36 | return -1; //not enough data 37 | } 38 | 39 | frameType = (BacnetPtpFrameTypes)buffer[offset + 2]; 40 | msgLength = (buffer[offset + 3] << 8) | (buffer[offset + 4] << 0); 41 | var crcHeader = buffer[offset + 5]; 42 | ushort crcData = 0; 43 | 44 | if (msgLength > 0) 45 | { 46 | if (offset + 6 + msgLength + 1 >= buffer.Length) 47 | return -1; 48 | 49 | crcData = (ushort)((buffer[offset + 6 + msgLength + 1] << 8) | (buffer[offset + 6 + msgLength + 0] << 0)); 50 | } 51 | 52 | if (buffer[offset + 0] != PTP_PREAMBLE1) 53 | return -1; 54 | 55 | if (buffer[offset + 1] != PTP_PREAMBLE2) 56 | return -1; 57 | 58 | if (MSTP.CRC_Calc_Header(buffer, offset + 2, 3) != crcHeader) 59 | return -1; 60 | 61 | if (msgLength > 0 && maxLength >= PTP_HEADER_LENGTH + msgLength + 2 && MSTP.CRC_Calc_Data(buffer, offset + 6, msgLength) != crcData) 62 | return -1; 63 | 64 | return 8 + msgLength + (msgLength > 0 ? 2 : 0); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Storage/Object.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet.Storage; 2 | 3 | [Serializable] 4 | public class Object 5 | { 6 | [XmlAttribute] 7 | public BacnetObjectTypes Type { get; set; } 8 | 9 | [XmlAttribute] 10 | public uint Instance { get; set; } 11 | 12 | public Property[] Properties { get; set; } 13 | 14 | public Object() 15 | { 16 | Properties = new Property[0]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Storage/Property.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet.Storage; 2 | 3 | [Serializable] 4 | public class Property 5 | { 6 | [XmlIgnore] 7 | public BacnetPropertyIds Id { get; set; } 8 | 9 | [XmlAttribute("Id")] 10 | public string IdText 11 | { 12 | get 13 | { 14 | return Id.ToString(); 15 | } 16 | set 17 | { 18 | Id = (BacnetPropertyIds)Enum.Parse(typeof(BacnetPropertyIds), value); 19 | } 20 | } 21 | 22 | [XmlAttribute] 23 | public BacnetApplicationTags Tag { get; set; } 24 | 25 | [XmlElement] 26 | public string[] Value { get; set; } 27 | 28 | public static BacnetValue DeserializeValue(string value, BacnetApplicationTags type) 29 | { 30 | switch (type) 31 | { 32 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_NULL: 33 | return value == "" 34 | ? new BacnetValue(type, null) 35 | : new BacnetValue(value); 36 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_BOOLEAN: 37 | return new BacnetValue(type, bool.Parse(value)); 38 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT: 39 | return new BacnetValue(type, uint.Parse(value)); 40 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT: 41 | return new BacnetValue(type, int.Parse(value)); 42 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_REAL: 43 | return new BacnetValue(type, float.Parse(value, CultureInfo.InvariantCulture)); 44 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_DOUBLE: 45 | return new BacnetValue(type, double.Parse(value, CultureInfo.InvariantCulture)); 46 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_OCTET_STRING: 47 | try 48 | { 49 | return new BacnetValue(type, Convert.FromBase64String(value)); 50 | } 51 | catch 52 | { 53 | return new BacnetValue(type, value); 54 | } 55 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_CONTEXT_SPECIFIC_DECODED: 56 | try 57 | { 58 | return new BacnetValue(type, Convert.FromBase64String(value)); 59 | } 60 | catch 61 | { 62 | return new BacnetValue(type, value); 63 | } 64 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_CHARACTER_STRING: 65 | return new BacnetValue(type, value); 66 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_BIT_STRING: 67 | return new BacnetValue(type, BacnetBitString.Parse(value)); 68 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED: 69 | return new BacnetValue(type, uint.Parse(value)); 70 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_DATE: 71 | return new BacnetValue(type, DateTime.Parse(value)); 72 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_TIME: 73 | return new BacnetValue(type, DateTime.Parse(value)); 74 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID: 75 | return new BacnetValue(type, BacnetObjectId.Parse(value)); 76 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_READ_ACCESS_SPECIFICATION: 77 | return new BacnetValue(type, BacnetReadAccessSpecification.Parse(value)); 78 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE: 79 | return new BacnetValue(type, BacnetDeviceObjectPropertyReference.Parse(value)); 80 | default: 81 | return new BacnetValue(type, null); 82 | } 83 | } 84 | 85 | public static string SerializeValue(BacnetValue value, BacnetApplicationTags type) 86 | { 87 | switch (type) 88 | { 89 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_NULL: 90 | return value.ToString(); // Modif FC 91 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_REAL: 92 | return ((float)value.Value).ToString(CultureInfo.InvariantCulture); 93 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_DOUBLE: 94 | return ((double)value.Value).ToString(CultureInfo.InvariantCulture); 95 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_OCTET_STRING: 96 | return Convert.ToBase64String((byte[])value.Value); 97 | case BacnetApplicationTags.BACNET_APPLICATION_TAG_CONTEXT_SPECIFIC_DECODED: 98 | { 99 | return value.Value is byte[]? Convert.ToBase64String((byte[])value.Value) 100 | : string.Join(";", ((BacnetValue[])value.Value) 101 | .Select(v => SerializeValue(v, v.Tag))); 102 | } 103 | default: 104 | return value.Value.ToString(); 105 | } 106 | } 107 | 108 | [XmlIgnore] 109 | public IList BacnetValue 110 | { 111 | get 112 | { 113 | if (Value == null) 114 | return new BacnetValue[0]; 115 | 116 | var ret = new BacnetValue[Value.Length]; 117 | for (var i = 0; i < ret.Length; i++) 118 | ret[i] = DeserializeValue(Value[i], Tag); 119 | 120 | return ret; 121 | } 122 | set 123 | { 124 | Value = value.Select(v => SerializeValue(v, Tag)).ToArray(); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Transport/BacnetIpV6UdpProtocolTransport.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * MIT License 3 | * 4 | * Copyright (C) 2015 Frederic Chaxel 5 | * Morten Kvistgaard 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject to 12 | * the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included 15 | * in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | * 25 | *********************************************************************/ 26 | 27 | // based on Addendum 135-2012aj-4 28 | 29 | namespace System.IO.BACnet; 30 | 31 | public class BacnetIpV6UdpProtocolTransport : BacnetTransportBase 32 | { 33 | private readonly bool _exclusivePort; 34 | private readonly string _localEndpoint; 35 | private readonly int _vMac; 36 | private bool _dontFragment; 37 | private UdpClient _exclusiveConn; 38 | private UdpClient _sharedConn; 39 | 40 | public BVLCV6 Bvlc { get; private set; } 41 | public int SharedPort { get; } 42 | 43 | // Give [::]:xxxx if the socket is open with System.Net.IPAddress.IPv6Any 44 | // Used the bvlc layer class in BBMD mode 45 | // Some more complex solutions could avoid this, that's why this property is virtual 46 | public virtual IPEndPoint LocalEndPoint => (IPEndPoint)_exclusiveConn.Client.LocalEndPoint; 47 | 48 | public BacnetIpV6UdpProtocolTransport(int port, int vMac = -1, bool useExclusivePort = false, 49 | bool dontFragment = false, int maxPayload = 1472, string localEndpointIp = "") 50 | { 51 | SharedPort = port; 52 | MaxBufferLength = maxPayload; 53 | Type = BacnetAddressTypes.IPV6; 54 | MaxAdpuLength = BVLCV6.BVLC_MAX_APDU; 55 | 56 | // Two frames type, unicast with 10 bytes or broadcast with 7 bytes 57 | // Here it's the biggest header, resize will be done after, if needed 58 | HeaderLength = BVLCV6.BVLC_HEADER_LENGTH; 59 | 60 | _exclusivePort = useExclusivePort; 61 | _dontFragment = dontFragment; 62 | _localEndpoint = localEndpointIp; 63 | _vMac = vMac; 64 | } 65 | 66 | public override void Start() 67 | { 68 | Open(); 69 | 70 | _sharedConn?.BeginReceive(OnReceiveData, _sharedConn); 71 | _exclusiveConn?.BeginReceive(OnReceiveData, _exclusiveConn); 72 | } 73 | 74 | public override int Send(byte[] buffer, int offset, int dataLength, BacnetAddress address, 75 | bool waitForTransmission, int timeout) 76 | { 77 | if (_exclusiveConn == null) return 0; 78 | 79 | //add header 80 | var fullLength = dataLength + HeaderLength; 81 | 82 | if (address.net == 0xFFFF) 83 | { 84 | var newBuffer = new byte[fullLength - 3]; 85 | Array.Copy(buffer, 3, newBuffer, 0, fullLength - 3); 86 | fullLength -= 3; 87 | buffer = newBuffer; 88 | Bvlc.Encode(buffer, offset - BVLCV6.BVLC_HEADER_LENGTH, 89 | BacnetBvlcV6Functions.BVLC_ORIGINAL_BROADCAST_NPDU, fullLength, address); 90 | } 91 | else 92 | { 93 | Bvlc.Encode(buffer, offset - BVLCV6.BVLC_HEADER_LENGTH, BacnetBvlcV6Functions.BVLC_ORIGINAL_UNICAST_NPDU, 94 | fullLength, address); 95 | } 96 | 97 | // create end point 98 | Convert(address, out var ep); 99 | 100 | try 101 | { 102 | // send 103 | // multicast are transported from our local unicast socket also 104 | return _exclusiveConn.Send(buffer, fullLength, ep); 105 | } 106 | catch 107 | { 108 | return 0; 109 | } 110 | } 111 | 112 | public override BacnetAddress GetBroadcastAddress() 113 | { 114 | // could be FF08, FF05, FF04, FF02 115 | var ep = new IPEndPoint(IPAddress.Parse("[FF0E::BAC0]"), SharedPort); 116 | Convert(ep, out var ret); 117 | ret.net = 0xFFFF; 118 | return ret; 119 | } 120 | 121 | public override void Dispose() 122 | { 123 | _exclusiveConn?.Close(); 124 | _exclusiveConn = null; 125 | _sharedConn?.Close(); 126 | _sharedConn = null; 127 | } 128 | 129 | public override bool Equals(object obj) 130 | { 131 | var a = obj as BacnetIpV6UdpProtocolTransport; 132 | return a?.SharedPort == SharedPort; 133 | } 134 | 135 | public override int GetHashCode() 136 | { 137 | return SharedPort.GetHashCode(); 138 | } 139 | 140 | public override string ToString() 141 | { 142 | return "Udp IPv6:" + SharedPort; 143 | } 144 | 145 | private void Open() 146 | { 147 | UdpClient multicastListener = null; 148 | 149 | if (!_exclusivePort) 150 | { 151 | /* We need a shared multicast "listen" port. This is the 0xBAC0 port */ 152 | /* This will enable us to have more than 1 client, on the same machine. Perhaps it's not that important though. */ 153 | /* We (might) only receive the multicast on this. Any unicasts to this might be eaten by another local client */ 154 | if (_sharedConn == null) 155 | { 156 | _sharedConn = new UdpClient(AddressFamily.InterNetworkV6) { ExclusiveAddressUse = false }; 157 | _sharedConn.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); 158 | EndPoint ep = new IPEndPoint(IPAddress.IPv6Any, SharedPort); 159 | if (!string.IsNullOrEmpty(_localEndpoint)) 160 | ep = new IPEndPoint(IPAddress.Parse(_localEndpoint), SharedPort); 161 | _sharedConn.Client.Bind(ep); 162 | multicastListener = _sharedConn; 163 | } 164 | /* This is our own exclusive port. We'll recieve everything sent to this. */ 165 | /* So this is how we'll present our selves to the world */ 166 | if (_exclusiveConn == null) 167 | { 168 | EndPoint ep = new IPEndPoint(IPAddress.IPv6Any, 0); 169 | if (!string.IsNullOrEmpty(_localEndpoint)) ep = new IPEndPoint(IPAddress.Parse(_localEndpoint), 0); 170 | _exclusiveConn = new UdpClient((IPEndPoint)ep); 171 | } 172 | } 173 | else 174 | { 175 | EndPoint ep = new IPEndPoint(IPAddress.IPv6Any, SharedPort); 176 | if (!string.IsNullOrEmpty(_localEndpoint)) 177 | ep = new IPEndPoint(IPAddress.Parse(_localEndpoint), SharedPort); 178 | _exclusiveConn = new UdpClient(AddressFamily.InterNetworkV6) 179 | { 180 | ExclusiveAddressUse = true 181 | }; 182 | _exclusiveConn.Client.Bind((IPEndPoint)ep); 183 | multicastListener = _exclusiveConn; 184 | } 185 | 186 | multicastListener.JoinMulticastGroup(IPAddress.Parse("[FF02::BAC0]")); 187 | multicastListener.JoinMulticastGroup(IPAddress.Parse("[FF04::BAC0]")); 188 | multicastListener.JoinMulticastGroup(IPAddress.Parse("[FF05::BAC0]")); 189 | multicastListener.JoinMulticastGroup(IPAddress.Parse("[FF08::BAC0]")); 190 | multicastListener.JoinMulticastGroup(IPAddress.Parse("[FF0E::BAC0]")); 191 | 192 | // If this option is enabled Yabe cannot see itself ! 193 | // multicastListener.MulticastLoopback = false; 194 | 195 | Bvlc = new BVLCV6(this, _vMac); 196 | } 197 | 198 | protected void Close() 199 | { 200 | _sharedConn?.BeginReceive(OnReceiveData, _sharedConn); 201 | _exclusiveConn?.BeginReceive(OnReceiveData, _exclusiveConn); 202 | } 203 | 204 | private void OnReceiveData(IAsyncResult asyncResult) 205 | { 206 | var conn = (UdpClient)asyncResult.AsyncState; 207 | try 208 | { 209 | var ep = new IPEndPoint(IPAddress.Any, 0); 210 | byte[] localBuffer; 211 | int rx; 212 | 213 | try 214 | { 215 | localBuffer = conn.EndReceive(asyncResult, ref ep); 216 | rx = localBuffer.Length; 217 | } 218 | catch (Exception) // ICMP port unreachable 219 | { 220 | //restart data receive 221 | conn.BeginReceive(OnReceiveData, conn); 222 | return; 223 | } 224 | 225 | if (rx == 0) // Empty frame : port scanner maybe 226 | { 227 | //restart data receive 228 | conn.BeginReceive(OnReceiveData, conn); 229 | return; 230 | } 231 | 232 | try 233 | { 234 | //verify message 235 | Convert(ep, out var remoteAddress); 236 | if (rx < BVLCV6.BVLC_HEADER_LENGTH - 3) 237 | { 238 | Log.Warn("Some garbage data got in"); 239 | } 240 | else 241 | { 242 | // Basic Header lenght 243 | var headerLength = Bvlc.Decode(localBuffer, 0, out var function, out _, ep, remoteAddress); 244 | 245 | switch (headerLength) 246 | { 247 | case 0: 248 | return; 249 | case -1: 250 | Log.Debug("Unknow BVLC Header"); 251 | return; 252 | } 253 | 254 | // response to BVLC_REGISTER_FOREIGN_DEVICE (could be BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK ... but we are not a BBMD, don't care) 255 | if (function == BacnetBvlcV6Functions.BVLC_RESULT) 256 | { 257 | Log.Debug("Receive Register as Foreign Device Response"); 258 | } 259 | 260 | // a BVLC_FORWARDED_NPDU frame by a BBMD, change the remote_address to the original one (stored in the BVLC header) 261 | // we don't care about the BBMD address 262 | if (function == BacnetBvlcV6Functions.BVLC_FORWARDED_NPDU) 263 | { 264 | Array.Copy(localBuffer, 7, remoteAddress.adr, 0, 18); 265 | } 266 | 267 | if (function != BacnetBvlcV6Functions.BVLC_ORIGINAL_UNICAST_NPDU && 268 | function != BacnetBvlcV6Functions.BVLC_ORIGINAL_BROADCAST_NPDU && 269 | function != BacnetBvlcV6Functions.BVLC_FORWARDED_NPDU) 270 | return; 271 | 272 | if (rx > headerLength) 273 | InvokeMessageRecieved(localBuffer, headerLength, rx - headerLength, remoteAddress); 274 | } 275 | } 276 | catch (Exception ex) 277 | { 278 | Log.Error("Exception in udp recieve", ex); 279 | } 280 | finally 281 | { 282 | //restart data receive 283 | conn.BeginReceive(OnReceiveData, conn); 284 | } 285 | } 286 | catch (Exception ex) 287 | { 288 | //restart data receive 289 | if (conn.Client != null) 290 | { 291 | Log.Error("Exception in Ip OnRecieveData", ex); 292 | conn.BeginReceive(OnReceiveData, conn); 293 | } 294 | } 295 | } 296 | 297 | public static string ConvertToHex(byte[] buffer, int length) 298 | { 299 | var ret = ""; 300 | 301 | for (var i = 0; i < length; i++) 302 | ret += buffer[i].ToString("X2"); 303 | 304 | return ret; 305 | } 306 | 307 | // Modif FC : used for BBMD communication 308 | public int Send(byte[] buffer, int dataLength, IPEndPoint ep) 309 | { 310 | try 311 | { 312 | // return _exclusiveConn.Send(buffer, data_length, ep); 313 | ThreadPool.QueueUserWorkItem(o => _exclusiveConn.Send(buffer, dataLength, ep), null); 314 | return dataLength; 315 | } 316 | catch 317 | { 318 | return 0; 319 | } 320 | } 321 | 322 | public bool SendRegisterAsForeignDevice(IPEndPoint bbmd, short ttl) 323 | { 324 | if (bbmd.AddressFamily != AddressFamily.InterNetworkV6) 325 | return false; 326 | 327 | Bvlc.SendRegisterAsForeignDevice(bbmd, ttl); 328 | return true; 329 | } 330 | 331 | public bool SendRemoteWhois(byte[] buffer, IPEndPoint bbmd, int msgLength) 332 | { 333 | if (bbmd.AddressFamily != AddressFamily.InterNetworkV6) 334 | return false; 335 | 336 | // This message was build using the default (10) header lenght, but it's smaller (7) 337 | var newBuffer = new byte[msgLength - 3]; 338 | Array.Copy(buffer, 3, newBuffer, 0, msgLength - 3); 339 | msgLength -= 3; 340 | 341 | Bvlc.SendRemoteWhois(newBuffer, bbmd, msgLength); 342 | return true; 343 | } 344 | 345 | public static void Convert(IPEndPoint ep, out BacnetAddress address) 346 | { 347 | var tmp1 = ep.Address.GetAddressBytes(); 348 | var tmp2 = BitConverter.GetBytes((ushort)ep.Port); 349 | Array.Reverse(tmp2); 350 | Array.Resize(ref tmp1, tmp1.Length + tmp2.Length); 351 | Array.Copy(tmp2, 0, tmp1, tmp1.Length - tmp2.Length, tmp2.Length); 352 | address = new BacnetAddress(BacnetAddressTypes.IPV6, 0, tmp1); 353 | } 354 | 355 | public static void Convert(BacnetAddress address, out IPEndPoint ep) 356 | { 357 | var port = (ushort)((address.adr[16] << 8) | (address.adr[17] << 0)); 358 | var ipv6 = new byte[16]; 359 | Array.Copy(address.adr, ipv6, 16); 360 | ep = new IPEndPoint(new IPAddress(ipv6), port); 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /Transport/BacnetPipeTransport.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public class BacnetPipeTransport : IBacnetSerialTransport 4 | { 5 | private PipeStream _conn; 6 | private IAsyncResult _currentRead; 7 | private IAsyncResult _currentConnect; 8 | private readonly bool _isServer; 9 | 10 | public string Name { get; } 11 | public int BytesToRead => PeekPipe(); 12 | 13 | public static string[] AvailablePorts 14 | { 15 | get 16 | { 17 | try 18 | { 19 | var listOfPipes = Directory.GetFiles(@"\\.\pipe\"); 20 | for (var i = 0; i < listOfPipes.Length; i++) 21 | listOfPipes[i] = listOfPipes[i].Replace(@"\\.\pipe\", ""); 22 | return listOfPipes; 23 | } 24 | catch (Exception ex) 25 | { 26 | var log = LogManager.GetLogger(); 27 | log.Warn("Exception in AvailablePorts", ex); 28 | return InteropAvailablePorts; 29 | } 30 | } 31 | } 32 | 33 | public BacnetPipeTransport(string name, bool isServer = false) 34 | { 35 | Name = name; 36 | _isServer = isServer; 37 | } 38 | 39 | /// 40 | /// Get the available byte count. (The .NET pipe interface has a few lackings. See also the "InteropAvailablePorts" function) 41 | /// 42 | [DllImport("kernel32.dll", EntryPoint = "PeekNamedPipe", SetLastError = true)] 43 | private static extern bool PeekNamedPipe(IntPtr handle, IntPtr buffer, uint nBufferSize, IntPtr bytesRead, ref uint bytesAvail, IntPtr bytesLeftThisMessage); 44 | 45 | public int PeekPipe() 46 | { 47 | uint bytesAvail = 0; 48 | return PeekNamedPipe(_conn.SafePipeHandle.DangerousGetHandle(), IntPtr.Zero, 0, IntPtr.Zero, ref bytesAvail, IntPtr.Zero) 49 | ? (int)bytesAvail 50 | : 0; 51 | } 52 | 53 | public override string ToString() 54 | { 55 | return Name; 56 | } 57 | 58 | public override int GetHashCode() 59 | { 60 | return Name.GetHashCode(); 61 | } 62 | 63 | public override bool Equals(object obj) 64 | { 65 | if (obj is not BacnetPipeTransport) return false; 66 | var a = (BacnetPipeTransport)obj; 67 | return Name.Equals(a.Name); 68 | } 69 | 70 | public void Open() 71 | { 72 | if (_conn != null) Close(); 73 | 74 | if (!_isServer) 75 | { 76 | _conn = new NamedPipeClientStream(".", Name, PipeDirection.InOut, PipeOptions.Asynchronous); 77 | ((NamedPipeClientStream)_conn).Connect(3000); 78 | } 79 | else 80 | { 81 | _conn = new NamedPipeServerStream(Name, PipeDirection.InOut, 20, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); 82 | } 83 | } 84 | 85 | public void Write(byte[] buffer, int offset, int length) 86 | { 87 | if (!_conn.IsConnected) return; 88 | try 89 | { 90 | //doing syncronous writes (to an Asynchronous pipe) seems to be a bad thing 91 | _conn.BeginWrite(buffer, offset, length, (r) => { _conn.EndWrite(r); }, null); 92 | } 93 | catch (IOException) 94 | { 95 | Disconnect(); 96 | } 97 | } 98 | 99 | private void Disconnect() 100 | { 101 | if (_conn is NamedPipeServerStream stream) 102 | { 103 | try 104 | { 105 | stream.Disconnect(); 106 | } 107 | catch 108 | { 109 | } 110 | _currentConnect = null; 111 | } 112 | _currentRead = null; 113 | } 114 | 115 | private bool WaitForConnection(int timeoutMs) 116 | { 117 | if (_conn.IsConnected) return true; 118 | if (_conn is not NamedPipeServerStream) return true; 119 | 120 | var server = (NamedPipeServerStream)_conn; 121 | 122 | if (_currentConnect == null) 123 | { 124 | try 125 | { 126 | _currentConnect = server.BeginWaitForConnection(null, null); 127 | } 128 | catch (IOException) 129 | { 130 | Disconnect(); 131 | _currentConnect = server.BeginWaitForConnection(null, null); 132 | } 133 | } 134 | 135 | if (_currentConnect.IsCompleted || _currentConnect.AsyncWaitHandle.WaitOne(timeoutMs)) 136 | { 137 | try 138 | { 139 | server.EndWaitForConnection(_currentConnect); 140 | } 141 | catch (IOException) 142 | { 143 | Disconnect(); 144 | } 145 | _currentConnect = null; 146 | } 147 | 148 | return _conn.IsConnected; 149 | } 150 | 151 | public int Read(byte[] buffer, int offset, int length, int timeoutMs) 152 | { 153 | if (!WaitForConnection(timeoutMs)) return -BacnetMstpProtocolTransport.ETIMEDOUT; 154 | 155 | if (_currentRead == null) 156 | { 157 | try 158 | { 159 | _currentRead = _conn.BeginRead(buffer, offset, length, null, null); 160 | } 161 | catch (Exception) 162 | { 163 | Disconnect(); 164 | return -1; 165 | } 166 | } 167 | 168 | if (!_currentRead.IsCompleted && !_currentRead.AsyncWaitHandle.WaitOne(timeoutMs)) 169 | return -BacnetMstpProtocolTransport.ETIMEDOUT; 170 | 171 | try 172 | { 173 | var rx = _conn.EndRead(_currentRead); 174 | _currentRead = null; 175 | return rx; 176 | } 177 | catch (Exception) 178 | { 179 | Disconnect(); 180 | return -1; 181 | } 182 | } 183 | 184 | public void Close() 185 | { 186 | if (_conn == null) return; 187 | _conn.Close(); 188 | _conn = null; 189 | } 190 | 191 | public void Dispose() 192 | { 193 | Close(); 194 | } 195 | 196 | #region " Interop Get Pipe Names " 197 | // ReSharper disable All 198 | 199 | [StructLayout(LayoutKind.Sequential)] 200 | private struct FILETIME 201 | { 202 | public uint dwLowDateTime; 203 | public uint dwHighDateTime; 204 | } 205 | 206 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 207 | private struct WIN32_FIND_DATA 208 | { 209 | public uint dwFileAttributes; 210 | public FILETIME ftCreationTime; 211 | public FILETIME ftLastAccessTime; 212 | public FILETIME ftLastWriteTime; 213 | public uint nFileSizeHigh; 214 | public uint nFileSizeLow; 215 | public uint dwReserved0; 216 | public uint dwReserved1; 217 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] 218 | public string cFileName; 219 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] 220 | public string cAlternateFileName; 221 | } 222 | 223 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] 224 | private static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData); 225 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] 226 | private static extern int FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData); 227 | [DllImport("kernel32.dll")] 228 | private static extern bool FindClose(IntPtr hFindFile); 229 | 230 | /// 231 | /// The built-in functions for pipe enumeration isn't perfect, I'm afraid. Hence this messy interop. 232 | /// 233 | private static string[] InteropAvailablePorts 234 | { 235 | get 236 | { 237 | var ret = new List(); 238 | var handle = FindFirstFile(@"\\.\pipe\*", out var data); 239 | if (handle == new IntPtr(-1)) 240 | return ret.ToArray(); 241 | 242 | do 243 | { 244 | ret.Add(data.cFileName); 245 | } 246 | while (FindNextFile(handle, out data) != 0); 247 | 248 | FindClose(handle); 249 | return ret.ToArray(); 250 | } 251 | } 252 | 253 | // ReSharper restore 254 | #endregion 255 | } 256 | -------------------------------------------------------------------------------- /Transport/BacnetSerialPortTransport.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public class BacnetSerialPortTransport : IBacnetSerialTransport 4 | { 5 | private readonly string _portName; 6 | private readonly SerialPort _port; 7 | 8 | public int BytesToRead => _port?.BytesToRead ?? 0; 9 | 10 | public BacnetSerialPortTransport(string portName, int baudRate) 11 | { 12 | _portName = portName; 13 | _port = new SerialPort(_portName, baudRate, Parity.None, 8, StopBits.One); 14 | } 15 | 16 | public override bool Equals(object obj) 17 | { 18 | if (obj is not BacnetSerialPortTransport) return false; 19 | var a = (BacnetSerialPortTransport)obj; 20 | return _portName.Equals(a._portName); 21 | } 22 | 23 | public override int GetHashCode() 24 | { 25 | return _portName.GetHashCode(); 26 | } 27 | 28 | public override string ToString() 29 | { 30 | return _portName; 31 | } 32 | 33 | public void Open() 34 | { 35 | _port.Open(); 36 | } 37 | 38 | public void Write(byte[] buffer, int offset, int length) 39 | { 40 | _port?.Write(buffer, offset, length); 41 | } 42 | 43 | public int Read(byte[] buffer, int offset, int length, int timeoutMs) 44 | { 45 | if (_port == null) return 0; 46 | _port.ReadTimeout = timeoutMs; 47 | 48 | try 49 | { 50 | var rx = _port.Read(buffer, offset, length); 51 | return rx; 52 | } 53 | catch (TimeoutException) 54 | { 55 | return -BacnetMstpProtocolTransport.ETIMEDOUT; 56 | } 57 | catch (Exception) 58 | { 59 | return -1; 60 | } 61 | } 62 | 63 | public void Close() 64 | { 65 | _port?.Close(); 66 | } 67 | 68 | public void Dispose() 69 | { 70 | Close(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Transport/BacnetTransportBase.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public abstract class BacnetTransportBase : IBacnetTransport 4 | { 5 | public ILog Log { get; set; } 6 | public int HeaderLength { get; protected set; } 7 | public int MaxBufferLength { get; protected set; } 8 | public BacnetAddressTypes Type { get; protected set; } 9 | public BacnetMaxAdpu MaxAdpuLength { get; protected set; } 10 | public byte MaxInfoFrames { get; set; } = 0xFF; 11 | 12 | protected BacnetTransportBase() 13 | { 14 | Log = LogManager.GetLogger(GetType()); 15 | } 16 | 17 | public abstract void Start(); 18 | 19 | public abstract BacnetAddress GetBroadcastAddress(); 20 | 21 | public virtual bool WaitForAllTransmits(int timeout) 22 | { 23 | return true; // not used 24 | } 25 | 26 | public abstract int Send(byte[] buffer, int offset, int dataLength, BacnetAddress address, bool waitForTransmission, int timeout); 27 | 28 | public event MessageRecievedHandler MessageRecieved; 29 | 30 | protected void InvokeMessageRecieved(byte[] buffer, int offset, int msgLength, BacnetAddress remoteAddress) 31 | { 32 | MessageRecieved?.Invoke(this, buffer, offset, msgLength, remoteAddress); 33 | } 34 | 35 | public abstract void Dispose(); 36 | } 37 | -------------------------------------------------------------------------------- /Transport/BacnetTransportEthernet.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * MIT License 3 | * 4 | * Copyright (C) 2015 Frederic Chaxel 5 | * 6 | * lot of code ported from https://github.com/LorenVS/bacstack 7 | * Copyright (C) 2014 Loren Van Spronsen, thank to him. 8 | * Thank to Christopher Gunther for the idea, and the starting code 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining 11 | * a copy of this software and associated documentation files (the 12 | * "Software"), to deal in the Software without restriction, including 13 | * without limitation the rights to use, copy, modify, merge, publish, 14 | * distribute, sublicense, and/or sell copies of the Software, and to 15 | * permit persons to whom the Software is furnished to do so, subject to 16 | * the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included 19 | * in all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | *********************************************************************/ 30 | 31 | namespace System.IO.BACnet; 32 | 33 | // A reference to PacketDotNet.dll & SharpPcap.dll should be made 34 | // in order to use this code 35 | // This class is not in the file BacnetTransport.cs to avoid integration 36 | // of two dll when Bacnet/Ethernet is not used 37 | 38 | internal class BacnetEthernetProtocolTransport : BacnetTransportBase 39 | { 40 | private LibPcapLiveDevice _device; 41 | private byte[] _deviceMac; // Mac of the device 42 | private readonly string _deviceName; 43 | 44 | /// 45 | /// 46 | /// Something like "Local Lan 1", "Wireless network", ... 47 | public BacnetEthernetProtocolTransport(string friendlydeviceName) 48 | { 49 | _deviceName = friendlydeviceName; 50 | Type = BacnetAddressTypes.Ethernet; 51 | MaxAdpuLength = BacnetMaxAdpu.MAX_APDU1476; 52 | HeaderLength = 6 + 6 + 2 + 3; 53 | MaxBufferLength = 1500; 54 | } 55 | 56 | public override BacnetAddress GetBroadcastAddress() 57 | { 58 | return new BacnetAddress(BacnetAddressTypes.Ethernet, "FF-FF-FF-FF-FF-FF", 65535); 59 | } 60 | 61 | public override void Start() 62 | { 63 | _device = Open(); 64 | if (_device == null) throw new Exception("Cannot open Ethernet interface"); 65 | 66 | _deviceMac = _device.Interface.MacAddress.GetAddressBytes(); 67 | 68 | // filter to only bacnet packets 69 | _device.Filter = "ether proto 0x82"; 70 | 71 | var th = new Thread(CaptureThread) { IsBackground = true }; 72 | th.Start(); 73 | } 74 | 75 | public override int Send(byte[] buffer, int offset, int dataLength, BacnetAddress address, 76 | bool waitForTransmission, int timeout) 77 | { 78 | var hdrOffset = 0; 79 | 80 | for (var i = 0; i < 6; i++) 81 | buffer[hdrOffset++] = address.adr[i]; 82 | 83 | // write the source mac address bytes 84 | for (var i = 0; i < 6; i++) 85 | buffer[hdrOffset++] = _deviceMac[i]; 86 | 87 | // the next 2 bytes are used for the packet length 88 | buffer[hdrOffset++] = (byte)(((dataLength + 3) & 0xFF00) >> 8); 89 | buffer[hdrOffset++] = (byte)((dataLength + 3) & 0xFF); 90 | 91 | // DSAP and SSAP 92 | buffer[hdrOffset++] = 0x82; 93 | buffer[hdrOffset++] = 0x82; 94 | 95 | // LLC control field 96 | buffer[hdrOffset] = 0x03; 97 | 98 | lock (_device) 99 | { 100 | _device.SendPacket(buffer, dataLength + HeaderLength); 101 | } 102 | 103 | return dataLength + HeaderLength; 104 | } 105 | 106 | public override void Dispose() 107 | { 108 | lock (_device) 109 | _device.Close(); 110 | } 111 | 112 | public override string ToString() 113 | { 114 | return "Ethernet"; 115 | } 116 | 117 | private LibPcapLiveDevice Open() 118 | { 119 | var devices = LibPcapLiveDeviceList.Instance.Where(dev => dev.Interface != null); 120 | 121 | if (!string.IsNullOrEmpty(_deviceName)) // specified interface 122 | { 123 | try 124 | { 125 | var device = devices.FirstOrDefault(dev => dev.Interface.FriendlyName == _deviceName); 126 | device?.Open(DeviceMode.Normal, 1000); // 1000 ms read timeout 127 | return device; 128 | } 129 | catch 130 | { 131 | return null; 132 | } 133 | } 134 | foreach (var device in devices) 135 | { 136 | device.Open(DeviceMode.Normal, 1000); // 1000 ms read timeout 137 | if (device.LinkType == LinkLayers.Ethernet 138 | && device.Interface.MacAddress != null) 139 | return device; 140 | device.Close(); 141 | } 142 | 143 | return null; 144 | } 145 | 146 | private void CaptureThread() 147 | { 148 | _device.NonBlockingMode = true; // Without that it's very, very slow 149 | while (true) 150 | { 151 | try 152 | { 153 | var packet = _device.GetNextPacket(); 154 | if (packet != null) 155 | OnPacketArrival(packet); 156 | else 157 | Thread.Sleep(10); // NonBlockingMode, we need to slow the overhead 158 | } 159 | catch 160 | { 161 | return; 162 | } // closed interface sure ! 163 | } 164 | } 165 | 166 | private bool _isOutboundPacket(IList buffer, int offset) 167 | { 168 | // check to see if the source mac 100% 169 | // matches the device mac address of the local device 170 | 171 | for (var i = 0; i < 6; i++) 172 | { 173 | if (buffer[offset + i] != _deviceMac[i]) 174 | return false; 175 | } 176 | 177 | return true; 178 | } 179 | 180 | private static byte[] Mac(byte[] buffer, int offset) 181 | { 182 | var b = new byte[6]; 183 | Buffer.BlockCopy(buffer, offset, b, 0, 6); 184 | return b; 185 | } 186 | 187 | private void OnPacketArrival(RawCapture packet) 188 | { 189 | // don't process any packet too short to not be valid 190 | if (packet.Data.Length <= 17) 191 | return; 192 | 193 | var buffer = packet.Data; 194 | var offset = 0; 195 | 196 | // Got frames send by me, not for me, not broadcast 197 | var dest = Mac(buffer, offset); 198 | if (!_isOutboundPacket(dest, 0) && dest[0] != 255) 199 | return; 200 | 201 | offset += 6; 202 | 203 | // source address 204 | var bacSource = new BacnetAddress(BacnetAddressTypes.Ethernet, 0, Mac(buffer, offset)); 205 | offset += 6; 206 | 207 | // len 208 | var length = buffer[offset] * 256 + buffer[offset + 1]; 209 | offset += 2; 210 | 211 | // 3 bytes LLC hearder 212 | var dsap = buffer[offset++]; 213 | var ssap = buffer[offset++]; 214 | var control = buffer[offset++]; 215 | 216 | length -= 3; // Bacnet content length eq. ethernet lenght minus LLC header length 217 | 218 | // don't process non-BACnet packets 219 | if (dsap != 0x82 || ssap != 0x82 || control != 0x03) 220 | return; 221 | 222 | InvokeMessageRecieved(buffer, HeaderLength, length, bacSource); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /Transport/IBacnetSerialTransport.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public interface IBacnetSerialTransport : IDisposable 4 | { 5 | int BytesToRead { get; } 6 | 7 | void Open(); 8 | void Close(); 9 | int Read(byte[] buffer, int offset, int length, int timeoutMs); 10 | void Write(byte[] buffer, int offset, int length); 11 | } 12 | -------------------------------------------------------------------------------- /Transport/IBacnetTransport.cs: -------------------------------------------------------------------------------- 1 | namespace System.IO.BACnet; 2 | 3 | public interface IBacnetTransport : IDisposable 4 | { 5 | byte MaxInfoFrames { get; set; } 6 | int HeaderLength { get; } 7 | int MaxBufferLength { get; } 8 | BacnetAddressTypes Type { get; } 9 | BacnetMaxAdpu MaxAdpuLength { get; } 10 | 11 | void Start(); 12 | BacnetAddress GetBroadcastAddress(); 13 | bool WaitForAllTransmits(int timeout); 14 | int Send(byte[] buffer, int offset, int dataLength, BacnetAddress address, bool waitForTransmission, int timeout); 15 | 16 | event MessageRecievedHandler MessageRecieved; 17 | } 18 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ela-compil/BACnet/782fc101c6167cb0720e3d421095151490eed326/logo.png --------------------------------------------------------------------------------