commit e7cf46cef843f8aaa4d9de74cbbac68bb67f0e4e Author: Lyubomir Penev Date: Sun Jan 25 16:01:40 2026 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..65181b3 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/App.config b/App.config new file mode 100644 index 0000000..56efbc7 --- /dev/null +++ b/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Form1.Designer.cs b/Form1.Designer.cs new file mode 100644 index 0000000..70729c1 --- /dev/null +++ b/Form1.Designer.cs @@ -0,0 +1,110 @@ +namespace Serial_Comms_CS +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + } +} + diff --git a/Form1.cs b/Form1.cs new file mode 100644 index 0000000..be8b4e2 --- /dev/null +++ b/Form1.cs @@ -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 + ); + } + } + } +} diff --git a/Form1.resx b/Form1.resx new file mode 100644 index 0000000..25aadac --- /dev/null +++ b/Form1.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..13918df --- /dev/null +++ b/Program.cs @@ -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 + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..229f729 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -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")] diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs new file mode 100644 index 0000000..659d9db --- /dev/null +++ b/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace Serial_Comms_CS.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // 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() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [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; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Properties/Resources.resx b/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs new file mode 100644 index 0000000..335429f --- /dev/null +++ b/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +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; + } + } + } +} diff --git a/Properties/Settings.settings b/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Serial Comms CS.csproj b/Serial Comms CS.csproj new file mode 100644 index 0000000..fbfc8d4 --- /dev/null +++ b/Serial Comms CS.csproj @@ -0,0 +1,84 @@ + + + + + Debug + AnyCPU + {B982E75A-10A9-4955-8114-9A1A3B5EF5A6} + WinExe + Serial_Comms_CS + Serial Comms CS + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + Form + + + Form1.cs + + + + + + Form1.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + \ No newline at end of file diff --git a/Serial Comms CS.sln b/Serial Comms CS.sln new file mode 100644 index 0000000..e858dc4 --- /dev/null +++ b/Serial Comms CS.sln @@ -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 diff --git a/SerialConnector.cs b/SerialConnector.cs new file mode 100644 index 0000000..0a5f5f9 --- /dev/null +++ b/SerialConnector.cs @@ -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> commands = new Dictionary>(); // Stores all the names of the incomming commands and their callback functions + public bool EnableDebugLogs { get; set; } // Enables or disables debugging console logs + + /// + /// Initializes a new instance of the class with the specified debug logging + /// option and baud rate. + /// + /// Use this constructor to configure the serial connection's baud rate and optionally + /// enable debug logging for troubleshooting or development purposes. + /// to enable detailed debug logging for serial communication; otherwise, . The default is . + /// The baud rate to use for the serial connection. Must be a positive integer. The default is 9600. + public SerialConnector(bool enableDebugLogs = false, int baudRate = 9600) + { + serial.BaudRate = baudRate; + this.EnableDebugLogs = enableDebugLogs; + } + + /// + /// Sets the serial port to the specified port name and opens the connection for communication. + /// + /// If a serial port connection is already open, it is closed before setting the new port + /// name and opening the connection. + /// The name of the serial port to use (for example, "COM1" or "/dev/ttyUSB0"). + 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; + } + } + + /// + /// Processes incoming serial data, verifies its integrity, and executes the corresponding command if valid. + /// + /// 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. + /// Thrown if the check bit verification fails due to parsing errors or if command execution fails. + 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]); + } + } + } + + /// + /// Receives all three parts of an icomming packet and check for the commands integrity using it's check bit. + /// + /// The command + /// The arguments to the command + /// The check bit of the packet + /// true if the packet is intact, false if it's malformed + 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; + } + + /// + /// Generates a checksum for a given string + /// + /// The string for which a checksum is needed + /// + 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; + } + + /// + /// Sends a command to the serial device to request repetition of the last operation. + /// + /// 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. + /// Thrown if the repeat command cannot be sent to the serial device. + 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"); + } + + /// + /// Sends an acknowledgment command with the specified check bit to the connected device. + /// + /// The check bit value to include in the acknowledgment command. This value is used to confirm receipt of a + /// previous message or command. + /// Thrown if the acknowledgment command cannot be sent to the device. + 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); + + } + + /// + /// Sends a command with the specified arguments to the connected device over the serial interface. + /// + /// 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. + /// The command to send. Cannot be or empty. + /// The arguments to include with the command. Cannot be ; may be empty if the command + /// does not require arguments. + /// An error occurred while sending the command or during post-send validation. + 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); + } + } + + /// + /// Performs post-send validation and handling based on the response received from the serial device after + /// sending a command. + /// + /// 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. + /// The command string that was sent to the serial device. + /// Thrown if the response from the serial device cannot be parsed, or if the check values in the acknowledgment + /// cannot be converted to integers. + 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"); + } + } + } + + /// + /// Registers a callback to be invoked when the specified command is triggered. + /// + /// 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. + /// The name of the command to register. Cannot be null or empty. Must be unique among registered commands. + /// The callback action to execute when the command is triggered. Cannot be null. + /// Thrown if the command already exists + public void OnCommand(string command, Action 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"); + } + + /// + /// Closes the serial port connection if it is currently open. + /// + /// Calling this method when the serial port is already closed has no effect. + 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(); + } + + } +}