diff --git a/README.md b/README.md
index 59cb0af..37e3049 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,59 @@
-# spidriver-net
-An unofficial .NET SDK for the SPIDriver debugger
+
+
+[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/alandoherty/spidriver-net/master/LICENSE)
+[![GitHub issues](https://img.shields.io/github/issues/alandoherty/spidriver-net.svg?style=flat-square)](https://github.com/alandoherty/spidriver-net/issues)
+[![GitHub stars](https://img.shields.io/github/stars/alandoherty/spidriver-net.svg?style=flat-square)](https://github.com/alandoherty/spidriver-net/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/alandoherty/spidriver-net.svg?style=flat-square)](https://github.com/alandoherty/spidriver-net/network)
+[![GitHub forks](https://img.shields.io/nuget/dt/SPIDriver.svg?style=flat-square)](https://www.nuget.org/packages/SPIDriver/)
+
+
+
+# spidriver
+
+An unofficial .NET library for [SPIDriver](https://spidriver.com/), allows for synchronous/asynchronous control of the device via the serial port. Open permissive MIT license with a dual-compliation for .NET Standard 2.0 and .NET Framework 4.6.1.
+
+![SPIDriver PCB](img/spidriver.jpg)
+
+## Getting Started
+
+[![NuGet Status](https://img.shields.io/nuget/v/SPIDriver.svg?style=flat-square)](https://www.nuget.org/packages/SPIDriver/)
+
+You can install the package using either the CLI:
+
+```
+dotnet add package SPIDriver
+```
+
+or from the NuGet package manager:
+
+```
+Install-Package SPIDriver
+```
+
+For assistance with the SPIDriver product itself you can view the [user guide](https://spidriver.com/spidriver.pdf) hosted on the official website.
+
+### Example
+
+You can find the example project `Example.CommandLine`, which demonstrates a CLI which implements all the features of the library.
+
+```
+> connect
+Connected successfully
+> status
+Model: spidriver1
+Serial Number: DO01JHMO
+Uptime: 01:56:22
+Voltage: 5.203V
+Current: 0A
+Temperature: 32.6°C
+A: True
+B: True
+CS: True
+CRC: 0x0001
+> writef airline.mp4
+Wrote 1510004 bytes in 33.294s
+```
+
+## Contributing
+
+Any pull requests or bug reports are welcome, please try and keep to the existing style conventions and comment any additions.
\ No newline at end of file
diff --git a/SpiDriver.sln b/SpiDriver.sln
new file mode 100644
index 0000000..a05c6ad
--- /dev/null
+++ b/SpiDriver.sln
@@ -0,0 +1,41 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29009.5
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpiDriver", "src\SpiDriver\SpiDriver.csproj", "{DB31321A-1176-45BA-B346-3E935C461B62}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7C86F419-D0FB-4251-AF6C-2C9194EA1C05}"
+ ProjectSection(SolutionItems) = preProject
+ README.md = README.md
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{EFCCC756-6E2D-4608-865D-5CE6B204C6F8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.CommandLine", "samples\Example.CommandLine\Example.CommandLine.csproj", "{69010272-1995-4708-A354-CC8485F9109F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {DB31321A-1176-45BA-B346-3E935C461B62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DB31321A-1176-45BA-B346-3E935C461B62}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DB31321A-1176-45BA-B346-3E935C461B62}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DB31321A-1176-45BA-B346-3E935C461B62}.Release|Any CPU.Build.0 = Release|Any CPU
+ {69010272-1995-4708-A354-CC8485F9109F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {69010272-1995-4708-A354-CC8485F9109F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {69010272-1995-4708-A354-CC8485F9109F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {69010272-1995-4708-A354-CC8485F9109F}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {69010272-1995-4708-A354-CC8485F9109F} = {EFCCC756-6E2D-4608-865D-5CE6B204C6F8}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {1AD50AD4-1933-4F12-BC76-14C291BA7587}
+ EndGlobalSection
+EndGlobal
diff --git a/img/spidriver.jpg b/img/spidriver.jpg
new file mode 100644
index 0000000..379ca78
Binary files /dev/null and b/img/spidriver.jpg differ
diff --git a/samples/Example.CommandLine/Example.CommandLine.csproj b/samples/Example.CommandLine/Example.CommandLine.csproj
new file mode 100644
index 0000000..6c0c373
--- /dev/null
+++ b/samples/Example.CommandLine/Example.CommandLine.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ netcoreapp2.2
+
+
+
+
+
+
+
diff --git a/samples/Example.CommandLine/Program.cs b/samples/Example.CommandLine/Program.cs
new file mode 100644
index 0000000..9f5a2f2
--- /dev/null
+++ b/samples/Example.CommandLine/Program.cs
@@ -0,0 +1,164 @@
+using SpiDriver;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+
+namespace Example.CommandLine
+{
+ class Program
+ {
+ static void Main(string[] argv) {
+ // the device
+ Device device = null;
+
+ // loop through accepting commands
+ string line;
+
+ while (true) {
+ // read next line
+ Console.Write("> ");
+ line = Console.ReadLine().Trim();
+
+ // parse the line
+ string[] components = line.Split(' ');
+ string[] args = components.Length == 0 ? new string[0] : new string[components.Length - 1];
+ string cmd = components.Length == 0 ? null : components[0];
+
+ if (args.Length > 0)
+ Array.Copy(components, 1, args, 0, args.Length);
+
+ // ignore empty lines
+ if (cmd == null)
+ continue;
+
+ // process command
+ if (cmd.Equals("connect", StringComparison.CurrentCultureIgnoreCase)) {
+ if (args.Length == 0) {
+ Console.WriteLine("connect ");
+ continue;
+ }
+
+ try {
+ device = new Device(args[0]);
+ device.Connect();
+ Console.WriteLine("Connected successfully");
+ } catch (Exception ex) {
+ Console.Error.WriteLine(ex.Message);
+ }
+ } else if (cmd.Equals("a", StringComparison.CurrentCultureIgnoreCase) || cmd.Equals("b", StringComparison.CurrentCultureIgnoreCase)
+ || cmd.Equals("cs", StringComparison.CurrentCultureIgnoreCase)) {
+ // check device is connected
+ if (device == null) {
+ Console.WriteLine("Not connected to device");
+ continue;
+ }
+
+ // determine output
+ Output output = Output.A;
+
+ if (cmd.Equals("b", StringComparison.CurrentCultureIgnoreCase))
+ output = Output.B;
+ else if (cmd.Equals("cs", StringComparison.CurrentCultureIgnoreCase))
+ output = Output.CS;
+
+ try {
+ if (args.Length == 0) {
+ bool value = device.GetOutput(output);
+ device.SetOutput(output, !value);
+ Console.WriteLine($"{output}: {!value}");
+ } else {
+ if (args[0].Equals("yes", StringComparison.CurrentCultureIgnoreCase) || args[0].Equals("on", StringComparison.CurrentCultureIgnoreCase)) {
+ device.SetOutput(output, true);
+ } else {
+ device.SetOutput(output, false);
+ }
+ }
+ } catch (Exception ex) {
+ Console.Error.WriteLine(ex.Message);
+ }
+ } else if (cmd.Equals("status", StringComparison.CurrentCultureIgnoreCase)) {
+ if (device == null) {
+ Console.WriteLine("Not connected to device");
+ continue;
+ }
+
+ try {
+ DeviceStatus status = device.GetStatus();
+
+ Console.WriteLine($"Model: {status.Model}");
+ Console.WriteLine($"Serial Number: {status.Serial}");
+ Console.WriteLine($"Uptime: {status.Uptime}");
+ Console.WriteLine($"Voltage: {status.Voltage}V");
+ Console.WriteLine($"Current: {status.Current}A");
+ Console.WriteLine($"Temperature: {status.Temperature}°C");
+ Console.WriteLine($"A: {status.A}");
+ Console.WriteLine($"B: {status.B}");
+ Console.WriteLine($"CS: {status.ChipSelect}");
+ Console.WriteLine($"CRC: 0x{status.Crc.ToString("x4")}");
+ } catch (Exception ex) {
+ Console.Error.WriteLine(ex.Message);
+ }
+ } else if (cmd.Equals("writef", StringComparison.CurrentCultureIgnoreCase)) {
+ string path = string.Join(' ', args);
+
+ if (device == null) {
+ Console.WriteLine("Not connected to device");
+ continue;
+ } else if (args.Length == 0) {
+ Console.WriteLine("No data in arguments");
+ continue;
+ } else if (!File.Exists(path)) {
+ Console.WriteLine("File does not exist");
+ continue;
+ }
+
+ // create stopwatch to time transfer
+ Stopwatch stopwatch = new Stopwatch();
+
+ try {
+ byte[] fb = File.ReadAllBytes(path);
+
+ stopwatch.Start();
+ device.Write(fb, 0, fb.Length);
+
+ Console.WriteLine($"Wrote {fb.Length} bytes in {Math.Round(stopwatch.Elapsed.TotalSeconds, 3)}s");
+ } catch (Exception ex) {
+ Console.Error.WriteLine(ex.Message);
+ }
+ } else if (cmd.Equals("read", StringComparison.CurrentCultureIgnoreCase)) {
+ int count = 0;
+
+ if (device == null) {
+ Console.WriteLine("Not connected to device");
+ continue;
+ } else if (args.Length == 0) {
+ Console.WriteLine("No data in arguments");
+ continue;
+ } else if (!int.TryParse(args[0], out count)) {
+ Console.WriteLine("Invalid count to read");
+ continue;
+ }
+
+ try {
+ byte[] data = new byte[count];
+ device.Read(data, 0, data.Length);
+
+ Console.WriteLine(BitConverter.ToString(data));
+ } catch (Exception ex) {
+ Console.Error.WriteLine(ex.Message);
+ }
+ } else if (cmd.Equals("help", StringComparison.CurrentCultureIgnoreCase)) {
+ Console.WriteLine("connect - connect to serial port");
+ Console.WriteLine("a [on/off] - toggle or set A output");
+ Console.WriteLine("b [on/off] - toggle or set B output");
+ Console.WriteLine("cs [on/off] - toggle or set CS output");
+ Console.WriteLine("status - get device status");
+ Console.WriteLine("writef - write file to SPI");
+ } else if (line != "quit" && line != "q" && line != "exit") {
+ return;
+ }
+ }
+ }
+ }
+}
diff --git a/src/SpiDriver/DataStream.cs b/src/SpiDriver/DataStream.cs
new file mode 100644
index 0000000..2d0a77e
--- /dev/null
+++ b/src/SpiDriver/DataStream.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace SpiDriver
+{
+ ///
+ /// Provides functionality to write/read data to the SPI component as a stream.
+ ///
+ public class DataStream : Stream
+ {
+ private Device _device;
+
+ ///
+ /// Gets if this stream can read data.
+ ///
+ public override bool CanRead {
+ get {
+ return true;
+ }
+ }
+
+ ///
+ /// Gets if this stream can seek.
+ ///
+ public override bool CanSeek {
+ get {
+ return false;
+ }
+ }
+
+ ///
+ /// Gets if this stream can write data.
+ ///
+ public override bool CanWrite {
+ get {
+ return true;
+ }
+ }
+
+ ///
+ /// Not supported.
+ ///
+ public override long Length {
+ get {
+ return 0;
+ }
+ }
+
+ ///
+ /// Not supported.
+ ///
+ public override long Position {
+ get {
+ throw new NotImplementedException();
+ } set {
+ throw new NotSupportedException();
+ }
+ }
+
+ ///
+ /// Flushes the stream.
+ ///
+ public override void Flush() {
+ }
+
+ ///
+ /// Reads data from the SPI device.
+ ///
+ /// The byte array.
+ /// The offset in the byte array to begin reading.
+ /// The number of bytes to read.
+ /// The number of bytes read.
+ public override int Read(byte[] buffer, int offset, int count) {
+ return _device.Read(buffer, offset, count);
+ }
+
+ ///
+ /// Not supported.
+ ///
+ ///
+ ///
+ ///
+ public override long Seek(long offset, SeekOrigin origin) {
+ throw new NotSupportedException();
+ }
+
+ ///
+ /// Not supported.
+ ///
+ ///
+ public override void SetLength(long value) {
+ throw new NotSupportedException();
+ }
+
+ ///
+ /// Writes the byte array to the SPI device.
+ ///
+ /// The buffer.
+ /// The offset.
+ /// The count.
+ public override void Write(byte[] buffer, int offset, int count) {
+ _device.Write(buffer, offset, count);
+ }
+
+ ///
+ /// Reads and writes data to and from the SPI device.
+ ///
+ /// The input byte array.
+ /// The offset in the byte array to begin reading.
+ /// The output byte array.
+ /// The offset in the byte array to begin writing.
+ /// The number of bytes to read.
+ /// The number of bytes written and read.
+ public int ReadWrite(byte[] inBytes, int inOffset, byte[] outBytes, int outOffset, int count) {
+ return _device.ReadWrite(inBytes, inOffset, outBytes, outOffset, count);
+ }
+
+ ///
+ /// Creates a new data stream.
+ ///
+ /// The device.
+ internal DataStream(Device device) {
+ _device = device;
+ }
+ }
+}
diff --git a/src/SpiDriver/Device.cs b/src/SpiDriver/Device.cs
new file mode 100644
index 0000000..8237bf0
--- /dev/null
+++ b/src/SpiDriver/Device.cs
@@ -0,0 +1,543 @@
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.IO.Ports;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SpiDriver
+{
+ ///
+ /// Provides functionality to communicate with SPIDriver devices.
+ ///
+ public class Device
+ {
+ internal SerialPort _port;
+ private SemaphoreSlim _portSemaphore = new SemaphoreSlim(1, 1);
+
+ ///
+ /// Gets or sets the read timeout, the default is infinite.
+ ///
+ public TimeSpan ReadTimeout {
+ get {
+ return TimeSpan.FromMilliseconds(_port.ReadTimeout);
+ } set {
+ _port.ReadTimeout = (int)value.TotalMilliseconds;
+ }
+ }
+
+ ///
+ /// Gets or sets the write timeout, the default is infinite.
+ ///
+ public TimeSpan WriteTimeout {
+ get {
+ return TimeSpan.FromMilliseconds(_port.WriteTimeout);
+ }
+ set {
+ _port.WriteTimeout = (int)value.TotalMilliseconds;
+ }
+ }
+
+ ///
+ /// Gets the serial port, direct access is not thread safe.
+ ///
+ public SerialPort Port {
+ get {
+ return _port;
+ }
+ }
+
+ ///
+ /// Gets if the connection to the device is open.
+ ///
+ public bool IsOpen {
+ get {
+ return _port.IsOpen;
+ }
+ }
+
+ private void ReadWire(byte[] buffer, int offset, int count) {
+ int dataRemaining = count;
+
+ while (dataRemaining > 0) {
+ int dataRead = _port.Read(buffer, count - dataRemaining, dataRemaining);
+ dataRemaining -= dataRead;
+ }
+ }
+
+ ///
+ /// Closes the device.
+ ///
+ public void Close() {
+ _port.Close();
+ }
+
+ ///
+ /// Gets a which provides read/write access to the underlying SPI data stream.
+ ///
+ ///
+ public Stream GetStream() {
+ return new DataStream(this);
+ }
+
+ ///
+ /// Gets the current output state by querying the device status.
+ ///
+ /// The output.
+ /// The output value.
+ public bool GetOutput(Output output) {
+ DeviceStatus status = GetStatus();
+
+ if (output == Output.A)
+ return status.A;
+ else if (output == Output.B)
+ return status.B;
+ else if (output == Output.CS)
+ return status.ChipSelect;
+ else
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Gets the current output state by querying the device status asynchronously.
+ ///
+ /// The output.
+ /// The output value.
+ public async Task GetOutputAsync(Output output) {
+ DeviceStatus status = await GetStatusAsync().ConfigureAwait(false);
+
+ if (output == Output.A)
+ return status.A;
+ else if (output == Output.B)
+ return status.B;
+ else if (output == Output.CS)
+ return status.ChipSelect;
+ else
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Gets the status of the device.
+ ///
+ /// The status.
+ public DeviceStatus GetStatus() {
+ if (!_port.IsOpen)
+ throw new InvalidOperationException("The serial port is not open");
+
+ _portSemaphore.Wait();
+
+ try {
+ // send request
+ _port.Write(new byte[] { (byte)'?' }, 0, 1);
+
+ // get response
+ byte[] data = new byte[80];
+ ReadWire(data, 0, 80);
+
+ // convert to string
+ string dataStr = Encoding.ASCII.GetString(data)
+ .TrimStart('[')
+ .TrimEnd(']');
+
+ // split components
+ string[] dataComponents = dataStr.Split(' ');
+
+ // fill status object
+ DeviceStatus status = new DeviceStatus();
+ status.Model = dataComponents[0];
+ status.Serial = dataComponents[1];
+ status.Uptime = TimeSpan.FromSeconds((double)ulong.Parse(dataComponents[2]));
+ status.Voltage = float.Parse(dataComponents[3]);
+ status.Current = float.Parse(dataComponents[4]);
+ status.Temperature = float.Parse(dataComponents[5]);
+ status.A = int.Parse(dataComponents[6]) == 1;
+ status.B = int.Parse(dataComponents[7]) == 1;
+ status.ChipSelect = int.Parse(dataComponents[8]) == 1;
+ status.Crc = ushort.Parse(dataComponents[6], NumberStyles.HexNumber);
+
+ return status;
+ } finally {
+ _portSemaphore.Release();
+ }
+ }
+
+ ///
+ /// Gets the status of the device asynchronously.
+ ///
+ ///
+ public async Task GetStatusAsync() {
+ if (!_port.IsOpen)
+ throw new InvalidOperationException("The serial port is not open");
+
+ await _portSemaphore.WaitAsync().ConfigureAwait(false);
+
+ try {
+ return await Task.Run(() => {
+ // send request
+ _port.Write(new byte[] { (byte)'?' }, 0, 1);
+
+ // get response
+ byte[] data = new byte[80];
+ ReadWire(data, 0, 80);
+
+ // convert to string
+ string dataStr = Encoding.ASCII.GetString(data)
+ .TrimStart('[')
+ .TrimEnd(']');
+
+ // split components
+ string[] dataComponents = dataStr.Split(' ');
+
+ // fill status object
+ DeviceStatus status = new DeviceStatus();
+ status.Model = dataComponents[0];
+ status.Serial = dataComponents[1];
+ status.Uptime = TimeSpan.FromSeconds((double)ulong.Parse(dataComponents[2]));
+ status.Voltage = float.Parse(dataComponents[3]);
+ status.Current = float.Parse(dataComponents[4]);
+ status.Temperature = float.Parse(dataComponents[5]);
+ status.A = int.Parse(dataComponents[6]) == 1;
+ status.B = int.Parse(dataComponents[7]) == 1;
+ status.ChipSelect = int.Parse(dataComponents[8]) == 1;
+ status.Crc = ushort.Parse(dataComponents[6], NumberStyles.HexNumber);
+
+ return status;
+ }).ConfigureAwait(false);
+ } finally {
+ _portSemaphore.Release();
+ }
+ }
+
+ ///
+ /// Set the specified output pin on the device.
+ ///
+ /// The output pin.
+ /// The enable.
+ public void SetOutput(Output output, bool enable) {
+ if (!_port.IsOpen)
+ throw new InvalidOperationException("The serial port is not open");
+
+ _portSemaphore.Wait();
+
+ try {
+ if (output == Output.A) {
+ _port.Write(new byte[] { (byte)'a', enable ? (byte)1 : (byte)0 }, 0, 2);
+ } else if (output == Output.B) {
+ _port.Write(new byte[] { (byte)'b', enable ? (byte)1 : (byte)0 }, 0, 2);
+ } else if (output == Output.CS) {
+ _port.Write(new byte[] { enable ? (byte)'s' : (byte)'u'}, 0, 2);
+ } else {
+ throw new NotImplementedException();
+ }
+ } finally {
+ _portSemaphore.Release();
+ }
+ }
+
+ ///
+ /// Set the specified output pin on the device asynchronously.
+ ///
+ /// The output pin.
+ /// The enable.
+ public async Task SetOutputAsync(Output output, bool enable) {
+ if (!_port.IsOpen)
+ throw new InvalidOperationException("The serial port is not open");
+
+ await _portSemaphore.WaitAsync().ConfigureAwait(false);
+
+ try {
+ await Task.Run(() => {
+ if (output == Output.A) {
+ _port.Write(new byte[] { (byte)'a', enable ? (byte)1 : (byte)0 }, 0, 2);
+ } else if (output == Output.B) {
+ _port.Write(new byte[] { (byte)'b', enable ? (byte)1 : (byte)0 }, 0, 2);
+ } else if (output == Output.CS) {
+ _port.Write(new byte[] { enable ? (byte)'s' : (byte)'u' }, 0, 2);
+ } else {
+ throw new NotImplementedException();
+ }
+ }).ConfigureAwait(false);
+ } finally {
+ _portSemaphore.Release();
+ }
+ }
+
+ ///
+ /// Connects to the serial port and performs connection tests.
+ ///
+ public void Connect() {
+ _portSemaphore.Wait();
+
+ try {
+ if (!_port.IsOpen)
+ _port.Open();
+
+ // write init message
+ _port.Write(Encoding.ASCII.GetBytes(new string('@', 64)), 0, 64);
+
+ // write tests
+ byte[] tests = new byte[] { (byte)'A', (byte)'\r', (byte)'\n', 0xFF };
+ byte[] writeCmd = new byte[] { (byte)'e', 0 };
+ byte[] readCmd = new byte[] { 0 };
+
+ for (int i = 0; i < 4; i++) {
+ writeCmd[1] = tests[i];
+
+ // write test byte
+ _port.Write(writeCmd, 0, writeCmd.Length);
+
+ // read test byte
+ ReadWire(readCmd, 0, 1);
+
+ if (readCmd[0] != tests[i])
+ throw new Exception("Response invalid during connection test");
+ }
+ } finally {
+ _portSemaphore.Release();
+ }
+ }
+
+ ///
+ /// Connects to the serial port and performs connection tests asynchronously.
+ ///
+ public async Task ConnectAsync() {
+ await _portSemaphore.WaitAsync().ConfigureAwait(false);
+
+ try {
+ await Task.Run(() => {
+ if (!_port.IsOpen)
+ _port.Open();
+
+ // write init message
+ _port.Write(Encoding.ASCII.GetBytes(new string('@', 64)), 0, 64);
+
+ // write tests
+ byte[] tests = new byte[] { (byte)'A', (byte)'\r', (byte)'\n', 0xFF };
+ byte[] writeCmd = new byte[] { (byte)'e', 0 };
+ byte[] readCmd = new byte[] { 0 };
+
+ for (int i = 0; i < 4; i++) {
+ writeCmd[1] = tests[i];
+
+ // write test byte
+ _port.Write(writeCmd, 0, writeCmd.Length);
+
+ // read test byte
+ ReadWire(readCmd, 0, 1);
+
+ if (readCmd[0] != tests[i])
+ throw new Exception("Response invalid during connection test");
+ }
+ }).ConfigureAwait(false);
+ } finally {
+ _portSemaphore.Release();
+ }
+ }
+
+ ///
+ /// Writes data to the SPI device.
+ ///
+ /// The byte array.
+ /// The offset in the byte array to begin writing.
+ /// The number of bytes to write.
+ public void Write(byte[] bytes, int offset, int count) {
+ if (count == 0)
+ return;
+
+ _portSemaphore.Wait();
+
+ try {
+ byte[] cmd = new byte[65];
+
+ for (int i = 0; i < count; i += 64) {
+ int len = ((count - i) < 64) ? (count - i) : 64;
+
+ for (int j = 0; j < cmd.Length; j++) {
+ cmd[j] = (byte)(0xC0 + len - 1);
+ }
+
+ Buffer.BlockCopy(bytes, offset + i, cmd, 1, len);
+ _port.Write(cmd, 0, 1 + len);
+ }
+ } finally {
+ _portSemaphore.Release();
+ }
+ }
+
+ ///
+ /// Writes data to the SPI device asynchronously.
+ ///
+ /// The byte array.
+ /// The offset in the byte array to begin writing.
+ /// The number of bytes to write.
+ public async Task WriteAsync(byte[] bytes, int offset, int count) {
+ if (count == 0)
+ return;
+
+ await _portSemaphore.WaitAsync().ConfigureAwait(false);
+
+ try {
+ await Task.Run(() => {
+ byte[] cmd = new byte[65];
+
+ for (int i = 0; i < count; i += 64) {
+ int len = ((count - i) < 64) ? (count - i) : 64;
+
+ for (int j = 0; j < cmd.Length; j++) {
+ cmd[j] = (byte)(0xC0 + len - 1);
+ }
+
+ Buffer.BlockCopy(bytes, offset + i, cmd, 1, len);
+ _port.Write(cmd, 0, 1 + len);
+ }
+ }).ConfigureAwait(false);
+ } finally {
+ _portSemaphore.Release();
+ }
+ }
+
+ ///
+ /// Reads data from the SPI device.
+ ///
+ /// The byte array.
+ /// The offset in the byte array to begin reading.
+ /// The number of bytes to read.
+ /// The number of bytes read.
+ public int Read(byte[] bytes, int offset, int count) {
+ if (count == 0)
+ return 0;
+
+ _portSemaphore.Wait();
+
+ try {
+ byte[] cmd = new byte[65];
+
+ for (int i = 0; i < count; i += 64) {
+ int len = ((count - i) < 64) ? (count - i) : 64;
+ cmd[0] = (byte)(0x80 + len - 1);
+ _port.Write(cmd, 0, len + 1);
+ ReadWire(bytes, i + offset, len);
+ }
+ } finally {
+ _portSemaphore.Release();
+ }
+
+ return count;
+ }
+
+ ///
+ /// Reads data from the SPI device.
+ ///
+ /// The byte array.
+ /// The offset in the byte array to begin reading.
+ /// The number of bytes to read.
+ /// The number of bytes read.
+ public async Task ReadAsync(byte[] bytes, int offset, int count) {
+ if (count == 0)
+ return 0;
+
+ await _portSemaphore.WaitAsync().ConfigureAwait(false);
+
+ try {
+ await Task.Run(() => {
+ byte[] cmd = new byte[65];
+
+ for (int i = 0; i < count; i += 64) {
+ int len = ((count - i) < 64) ? (count - i) : 64;
+ cmd[0] = (byte)(0x80 + len - 1);
+ _port.Write(cmd, 0, len + 1);
+ ReadWire(bytes, i + offset, len);
+ }
+ }).ConfigureAwait(false);
+ } finally {
+ _portSemaphore.Release();
+ }
+
+ return count;
+ }
+
+ ///
+ /// Reads and writes data to and from the SPI device.
+ ///
+ /// The input byte array.
+ /// The offset in the byte array to begin reading.
+ /// The output byte array.
+ /// The offset in the byte array to begin writing.
+ /// The number of bytes to read.
+ /// The number of bytes written and read.
+ public int ReadWrite(byte[] inBytes, int inOffset, byte[] outBytes, int outOffset, int count) {
+ if (count == 0)
+ return 0;
+
+ _portSemaphore.Wait();
+
+ try {
+ byte[] cmd = new byte[65];
+
+ for (int i = 0; i < count; i += 64) {
+ int len = ((count - i) < 64) ? (count - i) : 64;
+ cmd[0] = (byte)(0x80 + len - 1);
+ Buffer.BlockCopy(outBytes, outOffset + i, cmd, 1, len);
+ _port.Write(cmd, 0, len + 1);
+ ReadWire(inBytes, i + inOffset, len);
+ }
+ } finally {
+ _portSemaphore.Release();
+ }
+
+ return count;
+ }
+
+ ///
+ /// Reads and writes data to and from the SPI device.
+ ///
+ /// The input byte array.
+ /// The offset in the byte array to begin reading.
+ /// The output byte array.
+ /// The offset in the byte array to begin writing.
+ /// The number of bytes to read.
+ /// The number of bytes written and read.
+ public async Task ReadWriteAsync(byte[] inBytes, int inOffset, byte[] outBytes, int outOffset, int count) {
+ if (count == 0)
+ return 0;
+
+ await _portSemaphore.WaitAsync().ConfigureAwait(false);
+
+ try {
+ await Task.Run(() => {
+ byte[] cmd = new byte[65];
+
+ for (int i = 0; i < count; i += 64) {
+ int len = ((count - i) < 64) ? (count - i) : 64;
+ cmd[0] = (byte)(0x80 + len - 1);
+ Buffer.BlockCopy(outBytes, outOffset + i, cmd, 1, len);
+ _port.Write(cmd, 0, len + 1);
+ ReadWire(inBytes, i + inOffset, len);
+ }
+ }).ConfigureAwait(false);
+ } finally {
+ _portSemaphore.Release();
+ }
+
+ return count;
+ }
+
+ ///
+ /// Creates a new device with the specified serial port.
+ ///
+ /// The port.
+ public Device(SerialPort port) {
+ _port = port;
+ }
+
+ ///
+ /// Creates a new device with the specified serial port.
+ ///
+ /// The port name.
+ public Device(string portName) {
+ _port = new SerialPort(portName, 460800, Parity.None, 8, StopBits.One);
+ }
+ }
+}
diff --git a/src/SpiDriver/DeviceStatus.cs b/src/SpiDriver/DeviceStatus.cs
new file mode 100644
index 0000000..0926ac5
--- /dev/null
+++ b/src/SpiDriver/DeviceStatus.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SpiDriver
+{
+ ///
+ /// Represents status information for the driver.
+ ///
+ public class DeviceStatus
+ {
+ ///
+ /// Gets the model.
+ ///
+ public string Model { get; internal set; }
+
+ ///
+ /// Gets the serial number.
+ ///
+ public string Serial { get; internal set; }
+
+ ///
+ /// Gets the uptime.
+ ///
+ public TimeSpan Uptime { get; internal set; }
+
+ ///
+ /// Gets the voltage of the USB line.
+ ///
+ public float Voltage { get; internal set; }
+
+ ///
+ /// Gets the current used by the target SPI device.
+ ///
+ public float Current { get; internal set; }
+
+ ///
+ /// Gets the device temperature.
+ ///
+ public float Temperature { get; internal set; }
+
+ ///
+ /// Gets the A state.
+ ///
+ public bool A { get; internal set; }
+
+ ///
+ /// Gets the B state.
+ ///
+ public bool B { get; internal set; }
+
+ ///
+ /// Gets the chip select state.
+ ///
+ public bool ChipSelect { get; internal set; }
+
+ ///
+ /// Gets thge current CRC value.
+ ///
+ public ushort Crc { get; internal set; }
+ }
+}
diff --git a/src/SpiDriver/Output.cs b/src/SpiDriver/Output.cs
new file mode 100644
index 0000000..d4032e9
--- /dev/null
+++ b/src/SpiDriver/Output.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SpiDriver
+{
+ ///
+ /// Defines the output pins on the device.
+ ///
+ public enum Output
+ {
+ ///
+ /// The A output.
+ ///
+ A,
+
+ ///
+ /// The B output.
+ ///
+ B,
+
+ ///
+ /// The chip select output.
+ ///
+ CS
+ }
+}
diff --git a/src/SpiDriver/SpiDriver.csproj b/src/SpiDriver/SpiDriver.csproj
new file mode 100644
index 0000000..25ac155
--- /dev/null
+++ b/src/SpiDriver/SpiDriver.csproj
@@ -0,0 +1,41 @@
+
+
+
+ netstandard2.0
+ LICENSE
+ 0.1.0
+ Alan Doherty
+ Alan Doherty
+ SPIDriver
+ SPIDriver
+ An unofficial library for SPIDriver allows for synchronous/asynchronous control of the device via the serial port
+ https://spidriver.com
+ https://github.com/alandoherty/spidriver-net
+ git
+ 0.1.0.0
+ 0.1.0.0
+ true
+ Alan Doherty (C) 2019
+ https://s3-eu-west-1.amazonaws.com/assets.alandoherty.co.uk/github/spidriver-net-nuget.png
+
+
+
+ bin\Debug\netstandard2.0\SPIDriver.xml
+
+
+
+ bin\Release\netstandard2.0\SPIDriver.xml
+
+
+
+
+
+
+
+
+ True
+
+
+
+
+