初始化上传

This commit is contained in:
2025-08-26 08:37:44 +08:00
commit 31d81b91b6
448 changed files with 80981 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
using System;
using System.Diagnostics;
namespace SharpSerial
{
public static class Stdio
{
private static bool traceTimed = false;
private static bool traceEnabled = false;
private static readonly object locker = new object();
public static string ReadLine() => Console.ReadLine();
public static void WriteLine(string format, params object[] args)
{
lock (locker)
{
Console.Out.WriteLine(format, args);
Console.Out.Flush();
}
}
[Conditional("DEBUG")]
public static void Trace(string format, params object[] args)
{
if (traceEnabled)
{
lock (locker)
{
if (traceTimed)
{
Console.Error.Write(DateTime.Now.ToString("HH:mm:ss.fff"));
Console.Error.Write(" ");
}
Console.Error.WriteLine(format, args);
Console.Error.Flush();
}
}
}
public static void EnableTrace(bool enable, bool timed)
{
traceTimed = timed;
traceEnabled = enable;
}
}
}

View File

@@ -0,0 +1,99 @@
using System;
using System.Text;
namespace SharpSerial
{
public static class Tools
{
public static void SetupGlobalCatcher()
{
AppDomain.CurrentDomain.UnhandledException += ExceptionHandler;
}
public static void ExceptionHandler(object sender, UnhandledExceptionEventArgs args)
{
ExceptionHandler(args.ExceptionObject as Exception);
}
public static void ExceptionHandler(Exception ex)
{
Try(() => Stdio.Trace("!{0}", ex.ToString()));
Try(() => Stdio.WriteLine("!{0}", ex.ToString()));
Environment.Exit(1);
}
public static string StringHex(byte[] data)
{
var sb = new StringBuilder();
sb.Append("<");
foreach (var b in data) sb.Append(b.ToString("X2"));
return sb.ToString();
}
public static byte[] ParseHex(string text)
{
Assert(text.Length % 2 == 1, "Odd length expected for {0}:{1}", text.Length, text);
var bytes = new byte[text.Length / 2];
for (var i = 0; i < bytes.Length; i++)
{
var b2 = text.Substring(1 + i * 2, 2);
bytes[i] = Convert.ToByte(b2, 16);
}
return bytes;
}
public static void SetProperty(object target, string line)
{
var parts = line.Split(new char[] { '=' });
if (parts.Length != 2) throw Make("Expected 2 parts in {0}", Readable(line));
var propertyName = parts[0];
var propertyValue = parts[1];
var property = target.GetType().GetProperty(propertyName);
if (property == null) throw Make("Property not found {0}", Readable(propertyName));
var value = FromString(property.PropertyType, propertyValue);
property.SetValue(target, value, null);
}
public static object FromString(Type type, string text)
{
if (type.IsEnum) return Enum.Parse(type, text);
return Convert.ChangeType(text, type);
}
public static void Try(Action action)
{
try
{
action.Invoke();
}
catch (Exception)
{
//no clear use case for cleanup exception
}
}
public static Exception Make(string format, params object[] args)
{
var line = format;
if (args.Length > 0) line = string.Format(format, args);
return new Exception(line);
}
public static string Readable(string text)
{
var sb = new StringBuilder();
foreach (var c in text)
{
if (Char.IsControl(c)) sb.Append(((int)c).ToString("X2"));
else if (Char.IsWhiteSpace(c)) sb.Append(((int)c).ToString("X2"));
else sb.Append(c);
}
return sb.ToString();
}
public static void Assert(bool condition, string format, params object[] args)
{
if (!condition) throw Make(format, args);
}
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.IO;
using System.Threading;
using System.Collections.Generic;
using System.IO.Ports;
namespace SharpSerial
{
// com0com BytesToRead is UNRELIABLE
// Solution based on BaseStream and influenced by
// https://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport
// https://www.vgies.com/a-reliable-serial-port-in-c/
public class SerialDevice : ISerialStream, IDisposable
{
private readonly SerialPort serial;
private readonly List<byte> list;
private readonly Queue<byte> queue;
private readonly byte[] buffer;
public SerialDevice(object settings)
{
this.buffer = new byte[256];
this.list = new List<byte>(256);
this.queue = new Queue<byte>(256);
this.serial = new SerialPort();
//init serial port and launch async reader
SerialSettings.CopyProperties(settings, serial);
serial.Open();
//DiscardInBuffer not needed by FTDI and ignored by com0com
var stream = serial.BaseStream;
//unavailable after closed so pass it
stream.BeginRead(buffer, 0, buffer.Length, ReadCallback, stream);
}
public void Dispose()
{
Tools.Try(serial.Close);
Tools.Try(serial.Dispose);
}
public void Write(byte[] data)
{
var stream = serial.BaseStream;
stream.Write(data, 0, data.Length);
//always flush to allow sync by following read available
stream.Flush();
}
public byte[] Read(int size, int eop, int toms)
{
list.Clear();
var dl = DateTime.Now.AddMilliseconds(toms);
while (true)
{
int b = ReadByte();
if (b == -1)
{
//toms=0 should return immediately with available
if (DateTime.Now >= dl) break;
Thread.Sleep(1);
continue;
}
list.Add((byte)b);
if (eop >= 0 && b == eop) break;
if (size >= 0 && list.Count >= size) break;
dl = DateTime.Now.AddMilliseconds(toms);
}
return list.ToArray();
}
private int ReadByte()
{
lock (queue)
{
if (queue.Count == 0) return -1;
return queue.Dequeue();
}
}
private void ReadCallback(IAsyncResult ar)
{
Tools.Try(() =>
{
//try needed to avoid triggering the domain unhandled
//exception handler when used as standalone stream
var stream = ar.AsyncState as Stream;
int count = stream.EndRead(ar);
if (count > 0) //0 for closed stream
{
lock (queue) for (var i = 0; i < count; i++) queue.Enqueue(buffer[i]);
stream.BeginRead(buffer, 0, buffer.Length, ReadCallback, stream);
}
});
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace SharpSerial
{
public class SerialException : Exception
{
private readonly string trace;
public SerialException(string message, string trace) : base(message)
{
this.trace = trace;
}
public string Trace { get { return trace; } }
}
}

View File

@@ -0,0 +1,126 @@
using System;
using System.IO;
using System.Text;
using System.Diagnostics;
using System.Threading.Tasks;
namespace SharpSerial
{
//this class should not swallow exceptions outside dispose
public class SerialProcess : ISerialStream, IDisposable
{
private readonly Process process;
private readonly int pid;
public int Pid { get { return pid; } }
public SerialProcess(object settings)
{
var ss = new SerialSettings(settings);
var args = new StringBuilder();
foreach (var p in ss.GetType().GetProperties())
{
if (args.Length > 0) args.Append(" ");
args.AppendFormat("{0}={1}", p.Name, p.GetValue(ss, null).ToString());
}
process = new Process();
process.StartInfo = new ProcessStartInfo()
{
FileName = typeof(SerialProcess).Assembly.Location,
Arguments = args.ToString(),
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = false,
};
EnableStandardError(process.StartInfo);
process.Start();
pid = process.Id;
ForwardStandardError(process.StandardError);
}
public void Dispose()
{
Tools.Try(() =>
{
process.StandardInput.Close();
process.WaitForExit(200);
});
Tools.Try(process.Kill);
Tools.Try(process.Dispose);
}
public void Write(byte[] data)
{
WriteHex(data);
var line = ReadLine();
Tools.Assert(line == "<ok", "Unexpected write response {0}", line);
}
public byte[] Read(int size, int eop, int toms)
{
WriteLine("$r,{0},{1},{2}", size, eop, toms);
return ParseHex(ReadLine());
}
private string ReadLine()
{
var line = process.StandardOutput.ReadLine();
if (line == null) throw new EndOfStreamException("Serial process EOF");
if (line.StartsWith("!"))
{
var trace = process.StandardOutput.ReadToEnd();
throw new SerialException(line.Substring(1), trace);
}
return line;
}
private void WriteLine(string format, params object[] args)
{
process.StandardInput.WriteLine(format, args);
process.StandardInput.Flush();
}
private void WriteHex(byte[] data)
{
var sb = new StringBuilder();
sb.Append(">");
foreach (var b in data) sb.Append(b.ToString("X2"));
WriteLine(sb.ToString());
}
private byte[] ParseHex(string text)
{
Tools.Assert(text.StartsWith("<"), "First char < expected for {0}:{1}", text.Length, text);
Tools.Assert(text.Length % 2 == 1, "Odd length expected for {0}:{1}", text.Length, text);
var bytes = new byte[text.Length / 2];
for (var i = 0; i < bytes.Length; i++)
{
var b2 = text.Substring(1 + i * 2, 2);
bytes[i] = Convert.ToByte(b2, 16);
}
return bytes;
}
[Conditional("DEBUG")]
private void EnableStandardError(ProcessStartInfo psi)
{
psi.RedirectStandardError = true;
}
[Conditional("DEBUG")]
private void ForwardStandardError(StreamReader reader)
{
Task.Factory.StartNew(() =>
{
var line = reader.ReadLine();
while (line != null)
{
Stdio.Trace(line);
line = reader.ReadLine();
}
});
}
}
}

View File

@@ -0,0 +1,141 @@
using System;
using System.IO.Ports;
using System.Globalization;
using System.ComponentModel;
using System.Text.RegularExpressions;
namespace SharpSerial
{
public class SerialSettings
{
public SerialSettings() => CopyProperties(new SerialPort(), this);
public SerialSettings(string portName) => CopyProperties(new SerialPort(portName), this);
public SerialSettings(object source) => CopyProperties(source, this);
public void CopyFrom(object source) => CopyProperties(source, this);
public void CopyTo(object target) => CopyProperties(this, target);
[TypeConverter(typeof(PortNameConverter))]
public string PortName { get; set; }
[TypeConverter(typeof(BaudRateConverter))]
public int BaudRate { get; set; }
public int DataBits { get; set; }
public Parity Parity { get; set; }
public StopBits StopBits { get; set; }
public Handshake Handshake { get; set; }
public static void CopyProperties(Object source, Object target)
{
CopyProperty(source, target, "PortName");
CopyProperty(source, target, "BaudRate");
CopyProperty(source, target, "DataBits");
CopyProperty(source, target, "Parity");
CopyProperty(source, target, "StopBits");
CopyProperty(source, target, "Handshake");
}
static void CopyProperty(Object source, Object target, string name)
{
var propertySource = source.GetType().GetProperty(name);
var propertyTarget = target.GetType().GetProperty(name);
propertyTarget.SetValue(target, propertySource.GetValue(source, null), null);
}
}
public class PortNameConverter : TypeConverter
{
//windows only?
private readonly Regex re = new Regex(@"[^a-zA-Z0-9_]");
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(SerialPort.GetPortNames());
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return false;
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (sourceType == typeof(string));
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (re.IsMatch(value.ToString())) throw Tools.Make("Invalid chars");
return value;
}
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture, object value, Type destinationType)
{
return value;
}
}
public class BaudRateConverter : TypeConverter
{
public readonly static int[] BaudRates = new int[] {
110,
300,
600,
1200,
2400,
4800,
9600,
14400,
19200,
28800,
38400,
56000,
57600,
115200,
128000,
153600,
230400,
256000,
460800,
921600
};
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(BaudRates);
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return false;
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (sourceType == typeof(string));
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
return int.Parse(value.ToString());
}
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture, object value, Type destinationType)
{
return value.ToString();
}
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace SharpSerial
{
public interface ISerialStream : IDisposable
{
void Write(byte[] data);
byte[] Read(int size, int eop, int toms);
}
}