初始化上传
This commit is contained in:
47
常用工具集/Utility/Network/Modbus/SharpSerial/Program.Stdio.cs
Normal file
47
常用工具集/Utility/Network/Modbus/SharpSerial/Program.Stdio.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
99
常用工具集/Utility/Network/Modbus/SharpSerial/Program.Tools.cs
Normal file
99
常用工具集/Utility/Network/Modbus/SharpSerial/Program.Tools.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
97
常用工具集/Utility/Network/Modbus/SharpSerial/SerialDevice.cs
Normal file
97
常用工具集/Utility/Network/Modbus/SharpSerial/SerialDevice.cs
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
16
常用工具集/Utility/Network/Modbus/SharpSerial/SerialException.cs
Normal file
16
常用工具集/Utility/Network/Modbus/SharpSerial/SerialException.cs
Normal 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; } }
|
||||
}
|
||||
}
|
||||
126
常用工具集/Utility/Network/Modbus/SharpSerial/SerialProcess.cs
Normal file
126
常用工具集/Utility/Network/Modbus/SharpSerial/SerialProcess.cs
Normal 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
141
常用工具集/Utility/Network/Modbus/SharpSerial/SerialSettings.cs
Normal file
141
常用工具集/Utility/Network/Modbus/SharpSerial/SerialSettings.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
10
常用工具集/Utility/Network/Modbus/SharpSerial/SerialStream.cs
Normal file
10
常用工具集/Utility/Network/Modbus/SharpSerial/SerialStream.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user