first commit

This commit is contained in:
2026-01-25 16:01:40 +01:00
commit e7cf46cef8
14 changed files with 1578 additions and 0 deletions

414
.gitignore vendored Normal file
View File

@@ -0,0 +1,414 @@
# Created by https://www.toptal.com/developers/gitignore/api/visualstudio,dotnetcore
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio,dotnetcore
### DotnetCore ###
# .NET Core build folders
bin/
obj/
# Common node modules locations
/node_modules
/wwwroot/node_modules
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
### VisualStudio Patch ###
# Additional files built by Visual Studio
# End of https://www.toptal.com/developers/gitignore/api/visualstudio,dotnetcore

6
App.config Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

110
Form1.Designer.cs generated Normal file
View File

@@ -0,0 +1,110 @@
namespace Serial_Comms_CS
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.serialPortLbl = new System.Windows.Forms.Label();
this.serialPortBox = new System.Windows.Forms.ComboBox();
this.sendBtn = new System.Windows.Forms.Button();
this.reconnectBtn = new System.Windows.Forms.Button();
this.serialTimer = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout();
//
// serialPortLbl
//
this.serialPortLbl.AutoSize = true;
this.serialPortLbl.Location = new System.Drawing.Point(28, 33);
this.serialPortLbl.Name = "serialPortLbl";
this.serialPortLbl.Size = new System.Drawing.Size(124, 16);
this.serialPortLbl.TabIndex = 0;
this.serialPortLbl.Text = "Arduino Serial Port: ";
//
// serialPortBox
//
this.serialPortBox.FormattingEnabled = true;
this.serialPortBox.Location = new System.Drawing.Point(159, 33);
this.serialPortBox.Name = "serialPortBox";
this.serialPortBox.Size = new System.Drawing.Size(121, 24);
this.serialPortBox.TabIndex = 1;
this.serialPortBox.SelectedIndexChanged += new System.EventHandler(this.serialPortBox_SelectedIndexChanged);
//
// sendBtn
//
this.sendBtn.Location = new System.Drawing.Point(31, 74);
this.sendBtn.Name = "sendBtn";
this.sendBtn.Size = new System.Drawing.Size(121, 23);
this.sendBtn.TabIndex = 2;
this.sendBtn.Text = "Send";
this.sendBtn.UseVisualStyleBackColor = true;
this.sendBtn.Click += new System.EventHandler(this.sendBtn_Click);
//
// reconnectBtn
//
this.reconnectBtn.Location = new System.Drawing.Point(159, 74);
this.reconnectBtn.Name = "reconnectBtn";
this.reconnectBtn.Size = new System.Drawing.Size(121, 23);
this.reconnectBtn.TabIndex = 3;
this.reconnectBtn.Text = "Reconnect";
this.reconnectBtn.UseVisualStyleBackColor = true;
this.reconnectBtn.Click += new System.EventHandler(this.reconnectBtn_Click);
//
// serialTimer
//
this.serialTimer.Interval = 50;
this.serialTimer.Tick += new System.EventHandler(this.serialTimer_Tick);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(311, 121);
this.Controls.Add(this.reconnectBtn);
this.Controls.Add(this.sendBtn);
this.Controls.Add(this.serialPortBox);
this.Controls.Add(this.serialPortLbl);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.Name = "MainForm";
this.Text = "Serial Comms";
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.MainForm_FormClosed);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label serialPortLbl;
private System.Windows.Forms.ComboBox serialPortBox;
private System.Windows.Forms.Button sendBtn;
private System.Windows.Forms.Button reconnectBtn;
private System.Windows.Forms.Timer serialTimer;
}
}

131
Form1.cs Normal file
View File

@@ -0,0 +1,131 @@
using SerialComms;
using System;
using System.IO.Ports;
using System.Windows.Forms;
namespace Serial_Comms_CS
{
public partial class MainForm : Form
{
SerialConnector serialConnector = new SerialConnector();
void handleCalInt(string args)
{
Console.WriteLine("Callback CAL-INT called");
Console.WriteLine("Arguments: " + args);
}
void handleRead(string args)
{
Console.WriteLine("Callback READ called");
Console.WriteLine("Arguments: " + args);
}
public MainForm()
{
InitializeComponent();
// Determine which serial ports are available
string[] ports = SerialPort.GetPortNames();
if (ports.Length <= 0)
{
MessageBox.Show(
"Unable to find any COM ports",
"No COM ports",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
// Add the serial ports to the combobox
foreach (string port in ports)
{
serialPortBox.Items.Add(port);
}
try
{
serialConnector.OnCommand("CAL-NXT", (string args) => Console.WriteLine(args));
serialConnector.OnCommand("CAL-INT", handleCalInt);
serialConnector.OnCommand("READ", handleRead);
}
catch (Exception ex)
{
MessageBox.Show(
ex.Message,
"Failed to register command",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
}
private void serialPortBox_SelectedIndexChanged(object sender, EventArgs e)
{
string selectedPort = serialPortBox.Text;
try
{
serialConnector.SetSerialPort(selectedPort);
}
catch (Exception ex)
{
MessageBox.Show(
"Unable to set selected COM port\n" + ex.Message,
"Unable to open COM port",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
serialTimer.Enabled = true;
}
private void serialTimer_Tick(object sender, EventArgs e)
{
try
{
serialConnector.Cycle();
}
catch (Exception ex)
{
MessageBox.Show(
"Encountered an error while parsing commands\n" + ex.Message,
"Command parse error",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
}
private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
{
serialConnector.CloseSerial();
}
private void sendBtn_Click(object sender, EventArgs e)
{
serialConnector.SendCommand("CAL-INT", "3");
}
private void reconnectBtn_Click(object sender, EventArgs e)
{
string selectedPort = serialPortBox.Text;
try
{
serialConnector.SetSerialPort(selectedPort);
}
catch (Exception ex)
{
MessageBox.Show(
"Unable to set selected COM port\n" + ex.Message,
"Unable to open COM port",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
}
}
}
}

123
Form1.resx Normal file
View File

@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="serialTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

22
Program.cs Normal file
View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Serial_Comms_CS
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}

View File

@@ -0,0 +1,33 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Serial Comms CS")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Serial Comms CS")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("b982e75a-10a9-4955-8114-9a1a3b5ef5a6")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

71
Properties/Resources.Designer.cs generated Normal file
View File

@@ -0,0 +1,71 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Serial_Comms_CS.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Serial_Comms_CS.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}

117
Properties/Resources.resx Normal file
View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

30
Properties/Settings.Designer.cs generated Normal file
View File

@@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Serial_Comms_CS.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}

View File

@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

84
Serial Comms CS.csproj Normal file
View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B982E75A-10A9-4955-8114-9A1A3B5EF5A6}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>Serial_Comms_CS</RootNamespace>
<AssemblyName>Serial Comms CS</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SerialConnector.cs" />
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

25
Serial Comms CS.sln Normal file
View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36511.14 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serial Comms CS", "Serial Comms CS.csproj", "{B982E75A-10A9-4955-8114-9A1A3B5EF5A6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B982E75A-10A9-4955-8114-9A1A3B5EF5A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B982E75A-10A9-4955-8114-9A1A3B5EF5A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B982E75A-10A9-4955-8114-9A1A3B5EF5A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B982E75A-10A9-4955-8114-9A1A3B5EF5A6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B4EE6890-4607-4E40-BF12-BCD9A5E313A0}
EndGlobalSection
EndGlobal

405
SerialConnector.cs Normal file
View File

@@ -0,0 +1,405 @@
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Text;
namespace SerialComms
{
internal class SerialConnector
{
private static SerialPort serial = new SerialPort(); // The serial port used for all communications
private static Dictionary<string, Action<string>> commands = new Dictionary<string, Action<string>>(); // Stores all the names of the incomming commands and their callback functions
public bool EnableDebugLogs { get; set; } // Enables or disables debugging console logs
/// <summary>
/// Initializes a new instance of the <see cref="SerialConnector"/> class with the specified debug logging
/// option and baud rate.
/// </summary>
/// <remarks>Use this constructor to configure the serial connection's baud rate and optionally
/// enable debug logging for troubleshooting or development purposes.</remarks>
/// <param name="enableDebugLogs"><see langword="true"/> to enable detailed debug logging for serial communication; otherwise, <see
/// langword="false"/>. The default is <see langword="false"/>.</param>
/// <param name="baudRate">The baud rate to use for the serial connection. Must be a positive integer. The default is 9600.</param>
public SerialConnector(bool enableDebugLogs = false, int baudRate = 9600)
{
serial.BaudRate = baudRate;
this.EnableDebugLogs = enableDebugLogs;
}
/// <summary>
/// Sets the serial port to the specified port name and opens the connection for communication.
/// </summary>
/// <remarks>If a serial port connection is already open, it is closed before setting the new port
/// name and opening the connection.</remarks>
/// <param name="port">The name of the serial port to use (for example, "COM1" or "/dev/ttyUSB0").</param>
public void SetSerialPort(string port)
{
// If a port is already open, close it first to ensure a clean state before reconfiguring.
if (serial.IsOpen)
serial.Close();
// Optional debug log to indicate which port will be used.
if (EnableDebugLogs)
Console.WriteLine("Setting serial port to: " + port);
// Assign the requested port name to the SerialPort instance.
serial.PortName = port;
try
{
// Attempt to open the configured serial port. Opening can fail if the port doesn't exist
// or is already in use by another process.
if (EnableDebugLogs)
Console.WriteLine("Attempting to open serial port");
serial.Open();
// Clear any leftover data in the input buffer so subsequent reads start fresh.
serial.DiscardInBuffer();
if (EnableDebugLogs)
Console.WriteLine("Opened serial port");
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// Processes incoming serial data, verifies its integrity, and executes the corresponding command if valid.
/// </summary>
/// <remarks>This method reads available data from the serial buffer, validates the data using a
/// check bit, acknowledges receipt, and dispatches the command for execution. If the data fails integrity
/// checks or cannot be parsed, the method asks the sender to repeat the command or throws an exception. Debug
/// logging is performed if enabled.</remarks>
/// <exception cref="Exception">Thrown if the check bit verification fails due to parsing errors or if command execution fails.</exception>
public void Cycle()
{
// Only proceed if there is data waiting in the serial input buffer.
if (serial.BytesToRead > 0)
{
if (EnableDebugLogs)
Console.WriteLine("Starting to read from serial buffer");
// Read until the protocol terminator '@' is encountered.
string raw = serial.ReadTo("@");
if (EnableDebugLogs)
Console.WriteLine("Read: '" + raw + "' from serial buffer");
// Split the incoming packet into its three components: command, args, checkBit.
string[] data = raw.Split('#');
if (EnableDebugLogs)
Console.WriteLine("Split serial buffer in to: " + data[0] + ", " + data[1] + ", " + data[2]);
try
{
if (EnableDebugLogs)
Console.WriteLine("Attempting to verify check bit");
// Verify integrity using the provided check bit; parse the check bit to an integer.
if (!VerifyCheckBit(data[0], data[1], Int32.Parse(data[2])))
{
if (EnableDebugLogs)
Console.WriteLine("Check failed");
// Ask the sender to repeat the command if the integrity check failed and return early.
Repeat();
return;
}
}
catch (Exception ex)
{
// Wrap parsing/verification errors with context and rethrow.
throw new Exception("Failed to verify check bit. Was unable to parse command: " + raw + "\n" + ex.Message);
}
if (EnableDebugLogs)
Console.WriteLine("Command integrity verified successfuly");
// Send an acknowledgement back to the sender using the parsed check bit.
Acknowledge(Int32.Parse(data[2]));
try
{
// If a handler for this command is registered, invoke it with the argument string.
if (commands.ContainsKey(data[0]))
commands[data[0]](data[1]);
}
catch
{
// Provide context that execution of the command handler failed.
throw new Exception("Failed to get and execute command: " + data[0]);
}
}
}
/// <summary>
/// Receives all three parts of an icomming packet and check for the commands integrity using it's check bit.
/// </summary>
/// <param name="cmd">The command</param>
/// <param name="args">The arguments to the command</param>
/// <param name="checkBit">The check bit of the packet</param>
/// <returns>true if the packet is intact, false if it's malformed</returns>
private bool VerifyCheckBit(string cmd, string args, int checkBit)
{
// Recreate the portion of the packet which was used to generate the original checksum.
string toCheck = cmd + "#" + args;
// Compute the numeric checksum and compare to the provided check bit.
if (StringToCheckNum(toCheck) == checkBit)
return true;
else
return false;
}
/// <summary>
/// Generates a checksum for a given string
/// </summary>
/// <param name="str">The string for which a checksum is needed</param>
/// <returns></returns>
private int StringToCheckNum(string str)
{
// Initialize accumulator for checksum.
int x = 0;
// Convert the input string to bytes using the system default encoding.
byte[] ba = Encoding.Default.GetBytes(str);
// Convert the byte array to a hex string representation (e.g. "DE-AD-BE-EF").
var hexString = BitConverter.ToString(ba);
// Remove the hyphens so the string is a contiguous sequence of hex digits.
hexString = hexString.Replace("-", "");
// Sum the numeric value of each element in the hexString.
foreach (byte bit in hexString)
{
x += bit;
}
// Return the computed checksum.
return x;
}
/// <summary>
/// Sends a command to the serial device to request repetition of the last operation.
/// </summary>
/// <remarks>This method transmits a predefined repeat command to the connected serial device. If
/// debug logging is enabled, a message is written to the console indicating that a repeat request has been
/// sent.</remarks>
/// <exception cref="Exception">Thrown if the repeat command cannot be sent to the serial device.</exception>
private void Repeat()
{
// Pre-built repeat packet according to the protocol.
string cmd = "RPT##410@";
try
{
// Send the repeat request downstream.
serial.Write(cmd);
}
catch (Exception ex)
{
// Wrap and rethrow serial write exceptions for callers to handle.
throw new Exception("Failed to send RPT.\n" + ex.Message);
}
if (EnableDebugLogs)
Console.WriteLine("Asking for repeat of last command");
}
/// <summary>
/// Sends an acknowledgment command with the specified check bit to the connected device.
/// </summary>
/// <param name="checkBit">The check bit value to include in the acknowledgment command. This value is used to confirm receipt of a
/// previous message or command.</param>
/// <exception cref="Exception">Thrown if the acknowledgment command cannot be sent to the device.</exception>
private void Acknowledge(int checkBit)
{
// Build the acknowledge payload and compute its checksum to form a complete packet.
string cmd = "ACKG";
string toSend = cmd + "#" + checkBit;
int checkNum = StringToCheckNum(toSend);
string final = toSend + "#" + checkNum + "@";
try
{
// Transmit the ACKG packet.
serial.Write(final);
}
catch (Exception ex)
{
// Wrap and rethrow serial write exceptions for callers to handle.
throw new Exception("Failed to send ACKG.\n" + ex.Message);
}
if (EnableDebugLogs)
Console.WriteLine("Acknowledged last command: " + final);
}
/// <summary>
/// Sends a command with the specified arguments to the connected device over the serial interface.
/// </summary>
/// <remarks>The method formats the command and arguments according to the device protocol and
/// transmits them over the serial connection. If debug logging is enabled, the formatted command is written to
/// the console output.</remarks>
/// <param name="cmd">The command to send. Cannot be <see langword="null"/> or empty.</param>
/// <param name="args">The arguments to include with the command. Cannot be <see langword="null"/>; may be empty if the command
/// does not require arguments.</param>
/// <exception cref="Exception">An error occurred while sending the command or during post-send validation.</exception>
public void SendCommand(string cmd, string args)
{
// Compose the command payload and compute its checksum to create the final packet string.
string toSend = cmd + "#" + args;
int checkNum = StringToCheckNum(toSend);
string final = toSend + "#" + checkNum + "@";
try
{
// Send the final packet over serial.
serial.Write(final);
if (EnableDebugLogs)
Console.WriteLine("Sending command: " + final);
}
catch (Exception ex)
{
// Provide context about the failed send and propagate the error.
throw new Exception("Failed to send command: " + final + "\n" + ex.Message);
}
try
{
// Perform additional response handling after sending the command (e.g. check for ACKG or RPT).
AfterSendCheck(final);
}
catch (Exception ex)
{
// Rethrow any errors produced by the after-send logic.
throw new Exception(ex.Message);
}
}
/// <summary>
/// Performs post-send validation and handling based on the response received from the serial device after
/// sending a command.
/// </summary>
/// <remarks>This method reads the response from the serial device and processes it to determine
/// whether the command should be repeated or acknowledged. If the response indicates a repeat request, the
/// original command is resent. If the response is an acknowledgment, the method verifies the integrity of the
/// acknowledgment using check values.</remarks>
/// <param name="cmd">The command string that was sent to the serial device.</param>
/// <exception cref="Exception">Thrown if the response from the serial device cannot be parsed, or if the check values in the acknowledgment
/// cannot be converted to integers.</exception>
private void AfterSendCheck(string cmd)
{
// Read the next response packet.
string raw = serial.ReadTo("@");
// If an empty string was returned, attempt to read again recursively.
if (raw == "")
AfterSendCheck(cmd);
if (EnableDebugLogs)
Console.WriteLine("Performing after send checks");
// Split the incoming response into components.
string[] data = raw.Split('#');
if (data.Length < 1)
throw new Exception("Failed to parse Data after send packet");
if (EnableDebugLogs)
Console.WriteLine("Data array: " + data[0] + ", " + data[1]);
// Parse the original command that was sent (remove terminator, then split).
string[] dataCmd = cmd.Replace("@", "").Split('#');
if (dataCmd.Length < 2)
throw new Exception("Failed to parse DataCmd after send packet");
if (EnableDebugLogs)
Console.WriteLine("DataCmd array: " + data[0] + ", " + data[1] + ", " + data[2]);
// If the remote requested a repeat, resend the original command.
if (data[0] == "RPT")
{
if (EnableDebugLogs)
Console.WriteLine("Repeat command received");
SendCommand(dataCmd[0], dataCmd[1]);
}
else if (data[0] == "ACKG")
{
if (EnableDebugLogs)
Console.WriteLine("Command acknowledged");
int check, cmdCheck;
try
{
// Convert the received check value to an integer.
check = Int32.Parse(data[1]);
}
catch
{
throw new Exception("Failed to convert check bit to int");
}
try
{
// Extract the checksum portion of the original sent command for comparison.
cmdCheck = Int32.Parse(dataCmd[2]);
}
catch
{
throw new Exception("Failed to convert checkCmd bit in to int");
}
// Compare the two check values and optionally log the result.
if (check != cmdCheck)
{
if (EnableDebugLogs)
Console.WriteLine("Acknowledge check bad");
}
else
{
if (EnableDebugLogs)
Console.WriteLine("Acknowledge check good");
}
}
}
/// <summary>
/// Registers a callback to be invoked when the specified command is triggered.
/// </summary>
/// <remarks>Use this method to associate a handler with a command name. Attempting to register
/// the same command more than once will result in an exception.</remarks>
/// <param name="command">The name of the command to register. Cannot be null or empty. Must be unique among registered commands.</param>
/// <param name="callback">The callback action to execute when the command is triggered. Cannot be null.</param>
/// <exception cref="Exception">Thrown if the command already exists</exception>
public void OnCommand(string command, Action<string> callback)
{
// Add the callback to the commands dictionary if it does not already exist.
if (!commands.ContainsKey(command))
commands.Add(command, callback);
else
// Provide a clear exception if the command name is already registered.
throw new Exception("Unable to register command: " + command + " as command already exists");
}
/// <summary>
/// Closes the serial port connection if it is currently open.
/// </summary>
/// <remarks>Calling this method when the serial port is already closed has no effect.</remarks>
public void CloseSerial()
{
// Optionally log that the serial port is being closed.
if (EnableDebugLogs)
Console.WriteLine("Closing serial port");
// Only attempt to close if the port is currently open.
if (serial.IsOpen) serial.Close();
}
}
}