├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SerialPortLib.Tests ├── SerialPortLib.Tests.csproj └── Tests.cs ├── SerialPortLib.sln ├── SerialPortLib ├── Events.cs ├── Logging.cs ├── SerialPortInput.cs ├── SerialPortLib.csproj └── nuget_pack.ps1 ├── TestApp.Net ├── Program.cs ├── TestApp.Net.csproj └── appsettings.json └── appveyor.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Build results 2 | [Dd]ebug/ 3 | [Dd]ebugPublic/ 4 | [Rr]elease/ 5 | [Rr]eleases/ 6 | [Bb]in/ 7 | [Oo]bj/ 8 | 9 | # NuGet 10 | packages 11 | !packages/repositories.config 12 | 13 | # Misc 14 | Thumbs.db 15 | *.suo 16 | *.userprefs 17 | .idea 18 | .riderModule.iml 19 | /.vs/SerialPortLib/DesignTimeBuild/.dtbcache.v2 20 | /.vs/ProjectSettings.json 21 | /.vs/VSWorkspaceState.json 22 | /.vs/slnx.sqlite 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## How to contribute to this repository 4 | 5 | #### **Did you find a bug?** 6 | 7 | * **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/genielabs/serialport-lib-dotnet/issues). 8 | 9 | * If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/genielabs/serialport-lib-dotnet/issues/new). 10 | Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. 11 | 12 | #### **Did you write a patch that fixes a bug?** 13 | 14 | * Open a new GitHub pull request with the patch. 15 | 16 | * Ensure the PR description clearly describes the problem and solution. 17 | Include the relevant issue number if applicable. 18 | 19 | #### **Did you fix whitespace, format code, or make a purely cosmetic patch?** 20 | 21 | Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, 22 | or testability of this project, will generally not be accepted unless discussed via the [issue tracker](https://github.com/genielabs/serialport-lib-dotnet/issues). 23 | 24 | #### **Do you intend to add a new feature or change an existing one?** 25 | 26 | File a new *[enhancement issue](https://github.com/genielabs/serialport-lib-dotnet/issues/new?labels=enhancement)*. 27 | 28 | #### **Do you have questions about the source code?** 29 | 30 | File a new *[question issue](https://github.com/genielabs/serialport-lib-dotnet/issues/new?labels=question)*. 31 | 32 | #### **Coding styles and conventions** 33 | 34 | This project follows *Microsoft .Net* [coding conventions](https://docs.microsoft.com/dotnet/csharp/programming-guide/inside-a-program/coding-conventions) and [naming guidelines](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/capitalization-conventions). 35 | 36 | ##### Releasing a new version 37 | 38 | To release a new version push a new tag using the format: 39 | 40 | `..` 41 | 42 | examples: `1.0.16`, `1.0.20-pre1` 43 | 44 | When a new tag is submitted the CI system will build the project, run tests and package assets (.dll and .nupkg distribution files). The NuGet package will be automatically published and assets will be also uploaded to the new release tag on GitHub repository. 45 | 46 | #### Join SerialPortLib team! 47 | 48 | SerialPortLib is a volunteer effort. We encourage you to pitch in and join the team! 49 | 50 | Thanks! :heart: 51 | 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/r9fcyt98fjmygwu6?svg=true)](https://ci.appveyor.com/project/genemars/serialport-lib-dotnet) 2 | [![NuGet](https://img.shields.io/nuget/v/SerialPortLib.svg)](https://www.nuget.org/packages/SerialPortLib/) 3 | ![License](https://img.shields.io/github/license/genielabs/serialport-lib-dotnet.svg) 4 | 5 | # Serial Port library for .Net 6 | 7 | ## Features 8 | 9 | - Easy to use 10 | - Event driven 11 | - Hot plug 12 | - Automatically restabilish connection on error/disconnect 13 | - Compatible with Mono 14 | - It overcomes the lack of *DataReceived* event in Mono 15 | 16 | ## NuGet Package 17 | 18 | SerialPortLib is available as a [NuGet package](https://www.nuget.org/packages/SerialPortLib). 19 | 20 | Run `Install-Package SerialPortLib` in the [Package Manager Console](http://docs.nuget.org/docs/start-here/using-the-package-manager-console) or search for “SerialPortLib” in your IDE’s package management plug-in. 21 | 22 | ## Example usage 23 | 24 | ```csharp 25 | using SerialPortLib; 26 | ... 27 | var serialPort = new SerialPortInput(); 28 | 29 | // Listen to Serial Port events 30 | 31 | serialPort.ConnectionStatusChanged += delegate(object sender, ConnectionStatusChangedEventArgs args) 32 | { 33 | Console.WriteLine("Connected = {0}", args.Connected); 34 | }; 35 | 36 | serialPort.MessageReceived += delegate(object sender, MessageReceivedEventArgs args) 37 | { 38 | Console.WriteLine("Received message: {0}", BitConverter.ToString(args.Data)); 39 | }; 40 | 41 | // Set port options 42 | serialPort.SetPort("/dev/ttyUSB0", 115200); 43 | 44 | // Connect the serial port 45 | serialPort.Connect(); 46 | 47 | // Send a message 48 | var message = System.Text.Encoding.UTF8.GetBytes("Hello World!"); 49 | serialPort.SendMessage(message); 50 | ``` 51 | 52 | ## License 53 | 54 | SerialPortLib is open source software, licensed under the terms of Apache License 2.0. See the [LICENSE](LICENSE) file for details. 55 | 56 | 57 | ## Who's using this library? 58 | 59 | - [HomeGenie Server](http://github.com/genielabs/HomeGenie): smart home automation server 60 | - [ZWaveLib](https://github.com/genielabs/zwave-lib-dotnet): z-wave home automation library for .net/mono 61 | -------------------------------------------------------------------------------- /SerialPortLib.Tests/SerialPortLib.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /SerialPortLib.Tests/Tests.cs: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of SerialPortLib (https://github.com/genielabs/serialport-lib-dotnet) 3 | 4 | Copyright (2012-2023) G-Labs (https://github.com/genielabs) 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | using NUnit.Framework; 20 | 21 | namespace SerialPortLib.Tests; 22 | 23 | [TestFixture] 24 | public class Tests 25 | { 26 | [Test] 27 | public void Test1() 28 | { 29 | Assert.Pass(); 30 | } 31 | } -------------------------------------------------------------------------------- /SerialPortLib.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SerialPortLib", "SerialPortLib\SerialPortLib.csproj", "{8118C0B9-3B16-47AA-8B24-E5B15F429F78}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp.Net", "TestApp.Net\TestApp.Net.csproj", "{D5D595D2-58B5-4F26-ACCA-3DC11EEAA7C9}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SerialPortLib.Tests", "SerialPortLib.Tests\SerialPortLib.Tests.csproj", "{60588868-01D1-4E40-A2BB-AA65A48ABF74}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {8118C0B9-3B16-47AA-8B24-E5B15F429F78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {8118C0B9-3B16-47AA-8B24-E5B15F429F78}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {8118C0B9-3B16-47AA-8B24-E5B15F429F78}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {8118C0B9-3B16-47AA-8B24-E5B15F429F78}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {D5D595D2-58B5-4F26-ACCA-3DC11EEAA7C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {D5D595D2-58B5-4F26-ACCA-3DC11EEAA7C9}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {D5D595D2-58B5-4F26-ACCA-3DC11EEAA7C9}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {D5D595D2-58B5-4F26-ACCA-3DC11EEAA7C9}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {60588868-01D1-4E40-A2BB-AA65A48ABF74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {60588868-01D1-4E40-A2BB-AA65A48ABF74}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {60588868-01D1-4E40-A2BB-AA65A48ABF74}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {60588868-01D1-4E40-A2BB-AA65A48ABF74}.Release|Any CPU.Build.0 = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(MonoDevelopProperties) = preSolution 30 | Policies = $0 31 | $0.TextStylePolicy = $1 32 | $1.FileWidth = 240 33 | $1.EolMarker = Unix 34 | $1.inheritsSet = VisualStudio 35 | $1.inheritsScope = text/plain 36 | $1.scope = text/x-csharp 37 | $0.CSharpFormattingPolicy = $2 38 | $2.AnonymousMethodBraceStyle = NextLine 39 | $2.PropertyBraceStyle = NextLine 40 | $2.PropertyGetBraceStyle = NextLine 41 | $2.PropertySetBraceStyle = NextLine 42 | $2.EventBraceStyle = NextLine 43 | $2.EventAddBraceStyle = NextLine 44 | $2.EventRemoveBraceStyle = NextLine 45 | $2.StatementBraceStyle = NextLine 46 | $2.ElseNewLinePlacement = NewLine 47 | $2.CatchNewLinePlacement = NewLine 48 | $2.FinallyNewLinePlacement = NewLine 49 | $2.BeforeMethodDeclarationParentheses = False 50 | $2.BeforeMethodCallParentheses = False 51 | $2.BeforeConstructorDeclarationParentheses = False 52 | $2.BeforeIndexerDeclarationBracket = False 53 | $2.BeforeDelegateDeclarationParentheses = False 54 | $2.AfterDelegateDeclarationParameterComma = True 55 | $2.NewParentheses = False 56 | $2.SpacesBeforeBrackets = False 57 | $2.MethodCallArgumentWrapping = DoNotWrap 58 | $2.NewLineAferMethodCallOpenParentheses = SameLine 59 | $2.MethodCallClosingParenthesesOnNewLine = SameLine 60 | $2.MethodDeclarationParameterWrapping = DoNotWrap 61 | $2.NewLineAferMethodDeclarationOpenParentheses = SameLine 62 | $2.MethodDeclarationClosingParenthesesOnNewLine = SameLine 63 | $2.AlignToFirstMethodDeclarationParameter = False 64 | $2.IndexerDeclarationParameterWrapping = DoNotWrap 65 | $2.NewLineAferIndexerDeclarationOpenBracket = SameLine 66 | $2.IndexerDeclarationClosingBracketOnNewLine = SameLine 67 | $2.AlignToFirstIndexerDeclarationParameter = False 68 | $2.IndexerArgumentWrapping = DoNotWrap 69 | $2.NewLineAferIndexerOpenBracket = SameLine 70 | $2.IndexerClosingBracketOnNewLine = SameLine 71 | $2.inheritsSet = Mono 72 | $2.inheritsScope = text/x-csharp 73 | $2.scope = text/x-csharp 74 | $0.DotNetNamingPolicy = $3 75 | $3.DirectoryNamespaceAssociation = None 76 | $3.ResourceNamePolicy = FileFormatDefault 77 | $0.TextStylePolicy = $4 78 | $4.FileWidth = 240 79 | $4.EolMarker = Unix 80 | $4.inheritsSet = VisualStudio 81 | $4.inheritsScope = text/plain 82 | $4.scope = text/plain 83 | $0.TextStylePolicy = $5 84 | $5.inheritsSet = null 85 | $5.scope = application/xml 86 | $0.XmlFormattingPolicy = $6 87 | $6.inheritsSet = Mono 88 | $6.inheritsScope = application/xml 89 | $6.scope = application/xml 90 | $0.TextStylePolicy = $7 91 | $7.inheritsSet = null 92 | $7.scope = application/json 93 | $0.TextStylePolicy = $8 94 | $8.inheritsSet = null 95 | $8.scope = application/config+xml 96 | $0.XmlFormattingPolicy = $9 97 | $9.inheritsSet = null 98 | $9.scope = application/config+xml 99 | $0.TextStylePolicy = $10 100 | $10.inheritsSet = null 101 | $10.scope = text/cache-manifest 102 | $0.TextStylePolicy = $11 103 | $11.inheritsSet = null 104 | $11.scope = application/x-sh 105 | $0.TextStylePolicy = $12 106 | $12.inheritsSet = null 107 | $12.scope = text/html 108 | $0.TextStylePolicy = $13 109 | $13.inheritsSet = null 110 | $13.scope = text/css 111 | $0.TextStylePolicy = $14 112 | $14.inheritsSet = null 113 | $14.scope = application/javascript 114 | $0.TextStylePolicy = $15 115 | $15.inheritsSet = null 116 | $15.scope = text/x-authors 117 | $0.TextStylePolicy = $16 118 | $16.inheritsSet = null 119 | $16.scope = text/x-markdown 120 | $0.TextStylePolicy = $17 121 | $17.inheritsSet = null 122 | $17.scope = image/svg+xml 123 | $0.XmlFormattingPolicy = $18 124 | $18.inheritsSet = null 125 | $18.scope = image/svg+xml 126 | $0.TextStylePolicy = $19 127 | $19.inheritsSet = null 128 | $19.scope = application/x-sharedlib 129 | $0.StandardHeader = $20 130 | $20.IncludeInNewFiles = False 131 | $0.NameConventionPolicy = $21 132 | $21.Rules = $22 133 | $22.NamingRule = $23 134 | $23.Name = Namespaces 135 | $23.AffectedEntity = Namespace 136 | $23.VisibilityMask = VisibilityMask 137 | $23.NamingStyle = PascalCase 138 | $23.IncludeInstanceMembers = True 139 | $23.IncludeStaticEntities = True 140 | $22.NamingRule = $24 141 | $24.Name = Types 142 | $24.AffectedEntity = Class, Struct, Enum, Delegate 143 | $24.VisibilityMask = Public 144 | $24.NamingStyle = PascalCase 145 | $24.IncludeInstanceMembers = True 146 | $24.IncludeStaticEntities = True 147 | $22.NamingRule = $25 148 | $25.Name = Interfaces 149 | $25.RequiredPrefixes = $26 150 | $26.String = I 151 | $25.AffectedEntity = Interface 152 | $25.VisibilityMask = Public 153 | $25.NamingStyle = PascalCase 154 | $25.IncludeInstanceMembers = True 155 | $25.IncludeStaticEntities = True 156 | $22.NamingRule = $27 157 | $27.Name = Attributes 158 | $27.RequiredSuffixes = $28 159 | $28.String = Attribute 160 | $27.AffectedEntity = CustomAttributes 161 | $27.VisibilityMask = Public 162 | $27.NamingStyle = PascalCase 163 | $27.IncludeInstanceMembers = True 164 | $27.IncludeStaticEntities = True 165 | $22.NamingRule = $29 166 | $29.Name = Event Arguments 167 | $29.RequiredSuffixes = $30 168 | $30.String = EventArgs 169 | $29.AffectedEntity = CustomEventArgs 170 | $29.VisibilityMask = Public 171 | $29.NamingStyle = PascalCase 172 | $29.IncludeInstanceMembers = True 173 | $29.IncludeStaticEntities = True 174 | $22.NamingRule = $31 175 | $31.Name = Exceptions 176 | $31.RequiredSuffixes = $32 177 | $32.String = Exception 178 | $31.AffectedEntity = CustomExceptions 179 | $31.VisibilityMask = VisibilityMask 180 | $31.NamingStyle = PascalCase 181 | $31.IncludeInstanceMembers = True 182 | $31.IncludeStaticEntities = True 183 | $22.NamingRule = $33 184 | $33.Name = Methods 185 | $33.AffectedEntity = Methods 186 | $33.VisibilityMask = Protected, Public 187 | $33.NamingStyle = PascalCase 188 | $33.IncludeInstanceMembers = True 189 | $33.IncludeStaticEntities = True 190 | $22.NamingRule = $34 191 | $34.Name = Static Readonly Fields 192 | $34.AffectedEntity = ReadonlyField 193 | $34.VisibilityMask = Protected, Public 194 | $34.NamingStyle = PascalCase 195 | $34.IncludeInstanceMembers = False 196 | $34.IncludeStaticEntities = True 197 | $22.NamingRule = $35 198 | $35.Name = Fields 199 | $35.AffectedEntity = Field 200 | $35.VisibilityMask = Protected, Public 201 | $35.NamingStyle = PascalCase 202 | $35.IncludeInstanceMembers = True 203 | $35.IncludeStaticEntities = True 204 | $22.NamingRule = $36 205 | $36.Name = ReadOnly Fields 206 | $36.AffectedEntity = ReadonlyField 207 | $36.VisibilityMask = Protected, Public 208 | $36.NamingStyle = PascalCase 209 | $36.IncludeInstanceMembers = True 210 | $36.IncludeStaticEntities = False 211 | $22.NamingRule = $37 212 | $37.Name = Constant Fields 213 | $37.AffectedEntity = ConstantField 214 | $37.VisibilityMask = Protected, Public 215 | $37.NamingStyle = PascalCase 216 | $37.IncludeInstanceMembers = True 217 | $37.IncludeStaticEntities = True 218 | $22.NamingRule = $38 219 | $38.Name = Properties 220 | $38.AffectedEntity = Property 221 | $38.VisibilityMask = Protected, Public 222 | $38.NamingStyle = PascalCase 223 | $38.IncludeInstanceMembers = True 224 | $38.IncludeStaticEntities = True 225 | $22.NamingRule = $39 226 | $39.Name = Events 227 | $39.AffectedEntity = Event 228 | $39.VisibilityMask = Protected, Public 229 | $39.NamingStyle = PascalCase 230 | $39.IncludeInstanceMembers = True 231 | $39.IncludeStaticEntities = True 232 | $22.NamingRule = $40 233 | $40.Name = Enum Members 234 | $40.AffectedEntity = EnumMember 235 | $40.VisibilityMask = VisibilityMask 236 | $40.NamingStyle = PascalCase 237 | $40.IncludeInstanceMembers = True 238 | $40.IncludeStaticEntities = True 239 | $22.NamingRule = $41 240 | $41.Name = Parameters 241 | $41.AffectedEntity = Parameter 242 | $41.VisibilityMask = VisibilityMask 243 | $41.NamingStyle = CamelCase 244 | $41.IncludeInstanceMembers = True 245 | $41.IncludeStaticEntities = True 246 | $22.NamingRule = $42 247 | $42.Name = Type Parameters 248 | $42.RequiredPrefixes = $43 249 | $43.String = T 250 | $42.AffectedEntity = TypeParameter 251 | $42.VisibilityMask = VisibilityMask 252 | $42.NamingStyle = PascalCase 253 | $42.IncludeInstanceMembers = True 254 | $42.IncludeStaticEntities = True 255 | version = 1.0 256 | EndGlobalSection 257 | EndGlobal 258 | -------------------------------------------------------------------------------- /SerialPortLib/Events.cs: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of SerialPortLib (https://github.com/genielabs/serialport-lib-dotnet) 3 | 4 | Copyright (2012-2025) G-Labs (https://github.com/genielabs) 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | /* 20 | * Author: Generoso Martello 21 | * Project Homepage: https://github.com/genielabs/serialport-lib-dotnet 22 | */ 23 | 24 | using System; 25 | 26 | namespace SerialPortLib 27 | { 28 | 29 | /// 30 | /// Connected state changed event arguments. 31 | /// 32 | public class ConnectionStatusChangedEventArgs 33 | { 34 | /// 35 | /// The connected state. 36 | /// 37 | public readonly bool Connected; 38 | 39 | /// 40 | /// Initializes a new instance of the class. 41 | /// 42 | /// State of the connection (true = connected, false = not connected). 43 | public ConnectionStatusChangedEventArgs(bool state) 44 | { 45 | Connected = state; 46 | } 47 | } 48 | 49 | /// 50 | /// Message received event arguments. 51 | /// 52 | public class MessageReceivedEventArgs 53 | { 54 | /// 55 | /// The data. 56 | /// 57 | public readonly byte[] Data; 58 | 59 | /// 60 | /// Initializes a new instance of the class. 61 | /// 62 | /// Data. 63 | public MessageReceivedEventArgs(byte[] data) 64 | { 65 | Data = data; 66 | } 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /SerialPortLib/Logging.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Logging; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.IO.Ports; 8 | using System.Runtime.CompilerServices; 9 | 10 | namespace SerialPortLib 11 | { 12 | public static class Logging 13 | { 14 | private static readonly ILogger Logger; 15 | 16 | static Logging() 17 | { 18 | var configuration = new ConfigurationBuilder() 19 | .SetBasePath(Directory.GetCurrentDirectory()) 20 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 21 | .Build(); 22 | 23 | var serviceCollection = new ServiceCollection(); 24 | serviceCollection.AddLogging(builder => 25 | { 26 | builder.AddConfiguration(configuration.GetSection("Logging")); 27 | builder.AddSimpleConsole(options => configuration.GetSection("Logging:Console:FormatterOptions")); 28 | builder.AddConsole(); 29 | }); 30 | 31 | var serviceProvider = serviceCollection.BuildServiceProvider(); 32 | var loggerFactory = serviceProvider.GetService(); 33 | Logger = loggerFactory.CreateLogger("SerialPortInput"); 34 | } 35 | 36 | public static void Log(LogLevel level, string message, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) 37 | { 38 | Logger.LogWithCallInfo(level, message, null, memberName, filePath, lineNumber); 39 | } 40 | 41 | public static void LogInfo(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) 42 | { 43 | Log(LogLevel.Information, message, memberName, filePath, lineNumber); 44 | } 45 | 46 | public static void LogDebug(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) 47 | { 48 | Log(LogLevel.Debug, message, memberName, filePath, lineNumber); 49 | } 50 | 51 | public static void LogCritical(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) 52 | { 53 | Log(LogLevel.Critical, message, memberName, filePath, lineNumber); 54 | } 55 | 56 | public static void LogTrace(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) 57 | { 58 | Log(LogLevel.Trace, message, memberName, filePath, lineNumber); 59 | } 60 | 61 | public static void LogWarning(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) 62 | { 63 | Log(LogLevel.Warning, message, memberName, filePath, lineNumber); 64 | } 65 | 66 | public static void LogError(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) 67 | { 68 | Log(LogLevel.Error, message, memberName, filePath, lineNumber); 69 | } 70 | 71 | public static void LogError(Exception ex, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) 72 | { 73 | Logger.LogWithCallInfo(LogLevel.Error, ex.Message, ex, memberName, filePath, lineNumber); 74 | } 75 | 76 | public static void LogError(SerialError error, [CallerMemberName] string methodName = "") 77 | { 78 | Logger.LogError($"SerialPort error occurred in {methodName}: {error}"); 79 | } 80 | 81 | // Extension to add caller info 82 | private static void LogWithCallInfo(this ILogger logger, LogLevel logLevel, string message, Exception exception = null, 83 | [CallerMemberName] string memberName = "", 84 | [CallerFilePath] string filePath = "", 85 | [CallerLineNumber] int lineNumber = 0) 86 | { 87 | using (logger.BeginScope(new Dictionary 88 | { 89 | {"CallerMemberName", memberName}, 90 | {"CallerFilePath", filePath}, 91 | {"CallerLineNumber", lineNumber} 92 | })) 93 | { 94 | logger.Log(logLevel, message, exception); 95 | } 96 | } 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /SerialPortLib/SerialPortInput.cs: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of SerialPortLib (https://github.com/genielabs/serialport-lib-dotnet) 3 | 4 | Copyright (2012-2025) G-Labs (https://github.com/genielabs) 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | /* 20 | * Author: Generoso Martello 21 | * Project Homepage: https://github.com/genielabs/serialport-lib-dotnet 22 | */ 23 | 24 | using System; 25 | using System.Threading; 26 | using System.IO.Ports; 27 | using System.Runtime.CompilerServices; // Required for CallerMemberName 28 | using System.Runtime.InteropServices; 29 | 30 | namespace SerialPortLib 31 | { 32 | /// 33 | /// DataBits enum. 34 | /// 35 | public enum DataBits 36 | { 37 | /// 38 | /// DataBits 5. 39 | /// 40 | Five = 5, 41 | /// 42 | /// DataBits 6. 43 | /// 44 | Six, 45 | /// 46 | /// DataBits 7. 47 | /// 48 | Seven, 49 | /// 50 | /// DataBits 8. 51 | /// 52 | Eight, 53 | } 54 | 55 | /// 56 | /// Serial port I/O 57 | /// 58 | public class SerialPortInput 59 | { 60 | 61 | #region Private Fields 62 | 63 | private SerialPort _serialPort; 64 | 65 | private string _portName = ""; 66 | private int _baudRate = 115200; 67 | private StopBits _stopBits = StopBits.One; 68 | private Parity _parity = Parity.None; 69 | private DataBits _dataBits = DataBits.Eight; 70 | 71 | // Read/Write error state variable 72 | private bool _gotReadWriteError = true; 73 | 74 | // Serial port reader task 75 | private Thread _reader; 76 | private CancellationTokenSource _readerCts; 77 | private readonly byte[] _buffer = new byte[2048]; 78 | 79 | // Serial port connection watcher 80 | private static Timer _connectionWatcherTimer; 81 | 82 | 83 | private readonly object _serialPortLock = new object(); // Renamed for clarity 84 | private bool _disconnectRequested; 85 | 86 | private const int ReaderJoinTimeoutMs = 5000; 87 | private const int ConnectionWatcherSleepMs = 1000; 88 | private const int DefaultReconnectDelayMs = 1000; 89 | private const int DefaultReadWriteTimeoutMs = 5000; 90 | 91 | #endregion 92 | 93 | #region Public Events 94 | 95 | /// 96 | /// Connected state changed event. 97 | /// 98 | public delegate void ConnectionStatusChangedEventHandler(object sender, ConnectionStatusChangedEventArgs args); 99 | 100 | /// 101 | /// Occurs when connected state changed. 102 | /// 103 | public event ConnectionStatusChangedEventHandler ConnectionStatusChanged; 104 | 105 | /// 106 | /// Message received event. 107 | /// 108 | public delegate void MessageReceivedEventHandler(object sender, MessageReceivedEventArgs args); 109 | 110 | /// 111 | /// Occurs when message received. 112 | /// 113 | public event MessageReceivedEventHandler MessageReceived; 114 | 115 | #endregion 116 | 117 | #region Public Members 118 | 119 | public SerialPortInput() 120 | { 121 | _readerCts = new CancellationTokenSource(); 122 | } 123 | 124 | /// 125 | /// Connect to the serial port. 126 | /// 127 | public bool Connect() 128 | { 129 | if (_disconnectRequested) 130 | { 131 | return false; 132 | } 133 | lock (_serialPortLock) 134 | { 135 | Disconnect(); 136 | Open(); 137 | _connectionWatcherTimer = new Timer(ConnectionWatcherTask, null, ConnectionWatcherSleepMs, ConnectionWatcherSleepMs); 138 | } 139 | return IsConnected; 140 | } 141 | 142 | /// 143 | /// Disconnect the serial port. 144 | /// 145 | public void Disconnect() 146 | { 147 | if (_disconnectRequested) 148 | { 149 | return; 150 | } 151 | _disconnectRequested = true; 152 | Close(); 153 | lock (_serialPortLock) 154 | { 155 | if (_connectionWatcherTimer != null) 156 | { 157 | _connectionWatcherTimer.Dispose(); 158 | _connectionWatcherTimer = null; 159 | } 160 | _disconnectRequested = false; 161 | } 162 | } 163 | 164 | /// 165 | /// Gets a value indicating whether the serial port is connected. 166 | /// 167 | /// true if connected; otherwise, false. 168 | public bool IsConnected 169 | { 170 | get { return _serialPort != null && _serialPort.IsOpen && !_gotReadWriteError && !_disconnectRequested; } 171 | } 172 | 173 | /// 174 | /// Sets the serial port options. 175 | /// 176 | /// Portname. 177 | /// Baudrate. 178 | /// Stopbits. 179 | /// Parity. 180 | /// Databits. 181 | public void SetPort(string portName, int baudRate = 115200, StopBits stopBits = StopBits.One, Parity parity = Parity.None, DataBits dataBits = DataBits.Eight) 182 | { 183 | if (_portName != portName || _baudRate != baudRate || stopBits != _stopBits || parity != _parity || dataBits != _dataBits) 184 | { 185 | // Change Parameters request 186 | // Take into account immediately the new connection parameters 187 | // (do not use the ConnectionWatcher, otherwise strange things will occurs !) 188 | _portName = portName; 189 | _baudRate = baudRate; 190 | _stopBits = stopBits; 191 | _parity = parity; 192 | _dataBits = dataBits; 193 | if (IsConnected) 194 | { 195 | Connect(); // Take into account immediately the new connection parameters 196 | } 197 | LogDebug($"Port parameters changed (port name {_portName} / baudrate {_baudRate} / stopbits {_stopBits} / parity {_parity} / databits {_dataBits})"); 198 | } 199 | } 200 | 201 | /// 202 | /// Returns the underlying System.IO.Ports.SerialPort instance 203 | /// 204 | /// 205 | public SerialPort SerialPort 206 | { 207 | get 208 | { 209 | if (_serialPort == null) 210 | { 211 | _serialPort = new SerialPort(); 212 | } 213 | return _serialPort; 214 | } 215 | } 216 | 217 | /// 218 | /// Sends the message. 219 | /// 220 | /// true, if message was sent, false otherwise. 221 | /// Message. 222 | public bool SendMessage(byte[] message) 223 | { 224 | bool success = false; 225 | if (IsConnected) 226 | { 227 | try 228 | { 229 | _serialPort.Write(message, 0, message.Length); 230 | success = true; 231 | LogDebug(BitConverter.ToString(message)); 232 | } 233 | catch (ObjectDisposedException e) 234 | { 235 | _gotReadWriteError = true; 236 | LogError(e); 237 | } 238 | catch (TimeoutException e) 239 | { 240 | _gotReadWriteError = true; 241 | LogError(e); 242 | } 243 | catch (Exception e) 244 | { 245 | LogError(e); 246 | } 247 | } 248 | return success; 249 | } 250 | 251 | /// 252 | /// Gets/Sets serial port reconnection delay in milliseconds. 253 | /// 254 | public int ReconnectDelay { get; set; } = DefaultReconnectDelayMs; 255 | 256 | /// 257 | /// Gets or sets the serial port read/write timeout. 258 | /// 259 | public int Timeout { get; set; } = DefaultReadWriteTimeoutMs; 260 | 261 | #endregion 262 | 263 | #region Private members 264 | 265 | #region Serial Port handling 266 | 267 | private bool Open() 268 | { 269 | bool success = false; 270 | lock (_serialPortLock) 271 | { 272 | Close(); 273 | try 274 | { 275 | bool tryOpen = true; 276 | 277 | var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 278 | if (!isWindows) 279 | { 280 | tryOpen = (tryOpen && System.IO.File.Exists(_portName)); 281 | } 282 | if (tryOpen) 283 | { 284 | SerialPort.ErrorReceived += HandleErrorReceived; 285 | SerialPort.PortName = _portName; 286 | SerialPort.BaudRate = _baudRate; 287 | SerialPort.StopBits = _stopBits; 288 | SerialPort.Parity = _parity; 289 | SerialPort.DataBits = (int)_dataBits; 290 | SerialPort.ReadTimeout = Timeout; 291 | SerialPort.WriteTimeout = Timeout; 292 | 293 | // We are not using serialPort.DataReceived event for receiving data since this is not working under Linux/Mono. 294 | // We use the readerTask instead (see below). 295 | 296 | SerialPort.Open(); 297 | success = true; 298 | } 299 | } 300 | catch (Exception e) 301 | { 302 | LogError(e); 303 | Close(); 304 | } 305 | if (_serialPort != null && _serialPort.IsOpen) 306 | { 307 | _gotReadWriteError = false; 308 | // Start the Reader task 309 | _readerCts = new CancellationTokenSource(); 310 | _reader = new Thread(ReaderTask) { IsBackground = true }; 311 | _reader.Start(_readerCts.Token); 312 | OnConnectionStatusChanged(new ConnectionStatusChangedEventArgs(true)); 313 | } 314 | } 315 | return success; 316 | } 317 | 318 | private void Close() 319 | { 320 | lock (_serialPortLock) 321 | { 322 | // Stop the Reader task 323 | if (_reader != null) 324 | { 325 | if (!_reader.Join(ReaderJoinTimeoutMs)) 326 | _readerCts.Cancel(); 327 | _reader = null; 328 | } 329 | if (_serialPort != null) 330 | { 331 | _serialPort.ErrorReceived -= HandleErrorReceived; 332 | if (_serialPort.IsOpen) 333 | { 334 | try 335 | { 336 | _serialPort.Close(); 337 | } 338 | catch (Exception ex) 339 | { 340 | LogError(ex); // Log the exception during close 341 | } 342 | if (!_serialPort.IsOpen) 343 | { 344 | OnConnectionStatusChanged(new ConnectionStatusChangedEventArgs(false)); 345 | } 346 | } 347 | _serialPort.Dispose(); 348 | _serialPort = null; 349 | } 350 | _gotReadWriteError = true; 351 | } 352 | } 353 | 354 | private void HandleErrorReceived(object sender, SerialErrorReceivedEventArgs e) 355 | { 356 | LogError(e.EventType); 357 | } 358 | 359 | #endregion 360 | 361 | #region Background Tasks 362 | 363 | private void ReaderTask(object data) 364 | { 365 | var ct = (CancellationToken) data; 366 | while (IsConnected && !ct.IsCancellationRequested) 367 | { 368 | try 369 | { 370 | var readerTask = _serialPort?.BaseStream.ReadAsync(_buffer, 0, _buffer.Length, ct); 371 | readerTask?.Wait(); 372 | if (readerTask?.Result > 0) 373 | { 374 | byte[] received = new byte[readerTask.Result]; 375 | Buffer.BlockCopy(_buffer, 0, received, 0, readerTask.Result); 376 | OnMessageReceived(new MessageReceivedEventArgs(received)); 377 | } 378 | } 379 | catch (OperationCanceledException) 380 | { 381 | // Task was cancelled, exit gracefully 382 | break; 383 | } 384 | catch (Exception e) 385 | { 386 | LogError(e); 387 | _gotReadWriteError = true; 388 | Thread.Sleep(DefaultReconnectDelayMs); 389 | } 390 | } 391 | } 392 | 393 | private void ConnectionWatcherTask(object data) 394 | { 395 | // This task takes care of automatically reconnecting the interface 396 | // when the connection is drop or if an I/O error occurs 397 | if (!_disconnectRequested && _gotReadWriteError) 398 | { 399 | try 400 | { 401 | if (_serialPort != null && _serialPort.IsOpen) 402 | { 403 | Close(); 404 | } 405 | else 406 | { 407 | Open(); 408 | } 409 | } 410 | catch (Exception e) 411 | { 412 | LogError(e); 413 | } 414 | } 415 | } 416 | 417 | private void LogDebug(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) 418 | { 419 | Logging.LogDebug(message, memberName); 420 | } 421 | 422 | private void LogError(Exception ex, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) 423 | { 424 | Logging.LogError(ex, memberName); 425 | } 426 | 427 | private void LogError(SerialError error, [CallerMemberName] string methodName = "") 428 | { 429 | Logging.LogError(error, methodName); 430 | } 431 | 432 | #endregion 433 | 434 | #region Events Raising 435 | 436 | /// 437 | /// Raises the connected state changed event. 438 | /// 439 | /// Arguments. 440 | protected virtual void OnConnectionStatusChanged(ConnectionStatusChangedEventArgs args) 441 | { 442 | LogDebug(args.Connected.ToString()); 443 | ConnectionStatusChanged?.Invoke(this, args); 444 | } 445 | 446 | /// 447 | /// Raises the message received event. 448 | /// 449 | /// Arguments. 450 | protected virtual void OnMessageReceived(MessageReceivedEventArgs args) 451 | { 452 | LogDebug(BitConverter.ToString(args.Data)); 453 | MessageReceived?.Invoke(this, args); 454 | } 455 | 456 | #endregion 457 | 458 | #endregion 459 | 460 | } 461 | } 462 | -------------------------------------------------------------------------------- /SerialPortLib/SerialPortLib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | SerialPortLib 4 | 1.2.0 5 | Generoso Martello 6 | G-Labs 7 | Serial Port libray for .Net 8 | Features 9 | - Easy to use 10 | - Event driven 11 | - Hot plug 12 | - Automatically restabilish connection on error/disconnect 13 | ./LICENSE 14 | https://github.com/genielabs/serialport-lib-dotnet/ 15 | serial port 16 | net6.0;netstandard2.0;net472;net9.0 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /SerialPortLib/nuget_pack.ps1: -------------------------------------------------------------------------------- 1 | $project = "SerialPortLib" 2 | $root = (split-path -parent $MyInvocation.MyCommand.Definition) + '\..' 3 | 4 | $versionStr = "{0}" -f ($env:APPVEYOR_REPO_TAG_NAME) 5 | 6 | if (-not ([string]::IsNullOrEmpty($versionStr))) { 7 | Write-Host "Setting $project .csproj version tag to $versionStr" 8 | 9 | $content = (Get-Content $root\$project\$project.csproj) 10 | $content = $content -replace '(?<=\).*?(?=\)',$versionStr 11 | 12 | $content | Out-File $root\$project\$project.csproj 13 | 14 | & dotnet pack -c release $root\$project -o . 15 | } 16 | else { 17 | Write-Host "Version string is empty, possibly dry run or APPVEYOR_REPO_TAG_NAME environment variable is not set" 18 | } 19 | -------------------------------------------------------------------------------- /TestApp.Net/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | using SerialPortLib; 6 | 7 | namespace TestApp.NetCore 8 | { 9 | class Program 10 | { 11 | private static string _defaultPort = "/dev/ttyUSB0"; 12 | private static SerialPortInput _serialPort; 13 | 14 | public static void Main(string[] args) 15 | { 16 | var servicesProvider = BuildDi(); 17 | using (servicesProvider as IDisposable) 18 | { 19 | _serialPort = servicesProvider.GetRequiredService(); 20 | _serialPort.ConnectionStatusChanged += SerialPort_ConnectionStatusChanged; 21 | _serialPort.MessageReceived += SerialPort_MessageReceived; 22 | 23 | while (true) 24 | { 25 | Console.WriteLine("\nPlease enter serial to open (eg. \"COM7\" or \"/dev/ttyUSB0\" without double quotes),"); 26 | Console.WriteLine("or enter \"QUIT\" to exit.\n"); 27 | Console.Write($"Port [{_defaultPort}]: "); 28 | string port = Console.ReadLine(); 29 | if (String.IsNullOrWhiteSpace(port)) 30 | port = _defaultPort; 31 | else 32 | _defaultPort = port; 33 | 34 | // exit if the user enters "quit" 35 | if (port.Trim().ToLower().Equals("quit")) 36 | break; 37 | 38 | _serialPort.SetPort(port, 115200); 39 | _serialPort.Connect(); 40 | 41 | Console.WriteLine($"Waiting for serial port connection on {port}."); 42 | while (!_serialPort.IsConnected) 43 | { 44 | Console.Write("."); 45 | Thread.Sleep(1000); 46 | } 47 | // This is a test message (Z-Wave protocol message for getting the nodes stored in the Controller) 48 | var testMessage = new byte[] { 0x01, 0x03, 0x00, 0x02, 0xFE }; 49 | // Try sending some data if connected 50 | if (_serialPort.IsConnected) 51 | { 52 | Console.WriteLine("\nConnected! Sending test message 5 times."); 53 | for (int s = 0; s < 5; s++) 54 | { 55 | Thread.Sleep(2000); 56 | Console.WriteLine($"\nSEND [{(s + 1)}]"); 57 | _serialPort.SendMessage(testMessage); 58 | } 59 | } 60 | Console.WriteLine("\nTest sequence completed, now disconnecting."); 61 | 62 | _serialPort.Disconnect(); 63 | } 64 | } 65 | } 66 | 67 | static void SerialPort_MessageReceived(object sender, MessageReceivedEventArgs args) 68 | { 69 | // New message buffer received 70 | //Console.Write(System.Text.Encoding.UTF8.GetString(args.Data)); 71 | Console.WriteLine(BitConverter.ToString(args.Data)); 72 | // On every message received we send an ACK message back to the device 73 | _serialPort.SendMessage(new byte[] { 0x06 }); 74 | } 75 | 76 | static void SerialPort_ConnectionStatusChanged(object sender, ConnectionStatusChangedEventArgs args) 77 | { 78 | Console.WriteLine("Serial port connection status = {0}", args.Connected); 79 | } 80 | 81 | private static IServiceProvider BuildDi() 82 | { 83 | return new ServiceCollection() 84 | .AddTransient() 85 | .BuildServiceProvider(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /TestApp.Net/TestApp.Net.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | TestApp.NetCore 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | PreserveNewest 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /TestApp.Net/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "SerialPortLib": "Debug" 6 | }, 7 | "Console": { 8 | "FormatterName": "simple", 9 | "FormatterOptions": { 10 | "IncludeScopes": true, 11 | "SingleLine": true, 12 | "TimestampFormat": "yyyy-MM-ddTHH:mm:ss.fffK " 13 | } 14 | } 15 | }, 16 | "AllowedHosts": "*" 17 | } 18 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.2.{build} 2 | image: Visual Studio 2022 3 | configuration: Release 4 | before_build: 5 | - nuget restore 6 | build: 7 | project: .\SerialPortLib.sln 8 | verbosity: minimal 9 | test: 10 | assemblies: 11 | only: 12 | - SerialPortLib.Tests/bin/Debug/SerialPortLib.Tests.dll 13 | after_test: 14 | - ps: .\SerialPortLib\nuget_pack.ps1 15 | artifacts: 16 | - path: '*.nupkg' 17 | name: SerialPortLib nupkg 18 | deploy: 19 | - provider: GitHub 20 | auth_token: 21 | secure: EV/QbdjryLysyefCkxLXLBr4icYTdmOi+6wtgrB2mLfrP0qTQPiwj7w4L2i5fi7q 22 | draft: false 23 | prerelease: false 24 | on: 25 | appveyor_repo_tag: true 26 | - provider: NuGet 27 | api_key: 28 | secure: QHC8Twb1rsEXWGXXNzQhuQowwvBUVecFB0iPkPG5Y8I0zCKeBcRk3oWgEhB5zHR8 29 | skip_symbols: false 30 | artifact: /.*\.nupkg/ 31 | on: 32 | appveyor_repo_tag: true 33 | --------------------------------------------------------------------------------