Add PamhagenSysCtrl
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
using System.IO;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
|
||||
namespace PamhagenSysCtrl.Helpers {
|
||||
public class T1 : IDisposable {
|
||||
|
||||
public enum Mode { HALT = 1, RUN = 2, RUN_F = 3, HOLD = 4, ERROR = 6 }
|
||||
public readonly record struct Status(Mode Mode, int Flags);
|
||||
|
||||
protected SerialPort Serial;
|
||||
protected Stream Stream;
|
||||
protected StreamReader Reader;
|
||||
|
||||
public T1(string portName, Parity parity = Parity.Odd) {
|
||||
Serial = new SerialPort(portName, 9600, parity, 8, StopBits.One);
|
||||
Serial.Open();
|
||||
Stream = Serial.BaseStream ?? Stream.Null;
|
||||
Stream.ReadTimeout = 250;
|
||||
Stream.WriteTimeout = 250;
|
||||
Reader = new(Stream, Encoding.ASCII, false, 256);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Reader.Dispose();
|
||||
Stream.Dispose();
|
||||
Serial.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected async Task SendCommand(string cmd, string? data = null, bool checksum = true) {
|
||||
// max 255 bytes
|
||||
byte[] d = new byte[255];
|
||||
int n = Encoding.ASCII.GetBytes($"(A{1:02}{cmd}", d);
|
||||
if (data != null) {
|
||||
if (data.Length > 244) throw new ArgumentException("T1 playload too long");
|
||||
n += Encoding.ASCII.GetBytes(data, 0, data.Length, d, n);
|
||||
}
|
||||
if (checksum) {
|
||||
d[n++] = (byte)'&';
|
||||
byte chk = d.Take(n).Aggregate((s, c) => s = (byte)((s + c) & 0xFF));
|
||||
n += Encoding.ASCII.GetBytes($"{chk:02X}", 0, 2, d, n);
|
||||
}
|
||||
d[n++] = (byte)')';
|
||||
d[n++] = (byte)'\r';
|
||||
await Stream.WriteAsync(d.AsMemory(0, n));
|
||||
}
|
||||
|
||||
protected async Task<(string, string)> ReceiveResponse(string? expectedCmd = null) {
|
||||
var line = await Reader.ReadUntilAsync('\r');
|
||||
if (line == null) {
|
||||
throw new IOException("Verbindung zu SPS (T1) verloren");
|
||||
} else if (line.Length < 9 || !line.StartsWith("(A") || !line.EndsWith(")\r") || line[^5] != '&') {
|
||||
throw new FormatException("Invalid response from PLC (T1)");
|
||||
}
|
||||
|
||||
var chk1 = (byte)Convert.ToInt32(line[^4..^2], 16);
|
||||
var chk2 = Encoding.ASCII.GetBytes(line).SkipLast(5).Aggregate((s, c) => (byte)((s + c) & 0xFF));
|
||||
if (chk1 != chk2) {
|
||||
throw new IOException("Ungültige Prüfsumme von SPS (T1)");
|
||||
}
|
||||
|
||||
var (cmd, data) = (line[3..5], line[5..^5]);
|
||||
if (cmd == "ST" && data[4] == '6') {
|
||||
// TODO check for other error bits?
|
||||
await SendCommand("ER");
|
||||
var (_, res) = await ReceiveResponse("ER");
|
||||
int code = Convert.ToInt32(res);
|
||||
var error = Enum.IsDefined(typeof(T1Error), code) ? (T1Error)code : T1Error.UNKNOWN_ERROR;
|
||||
throw new T1Exception($"PLC Error ({data}): {error.GetName()}" + (error.GetDescription() != null ? $" ({error.GetDescription()})" : ""));
|
||||
} else if (cmd == expectedCmd) {
|
||||
return (cmd, data);
|
||||
} else if (cmd == "CE") {
|
||||
int code = Convert.ToInt32(data);
|
||||
throw code switch {
|
||||
1 => new T1Exception($"Computer Link Error ({data}): Command error (Received command is illegal)"),
|
||||
2 => new T1Exception($"Computer Link Error ({data}): Format error (Received message format is invalid)"),
|
||||
3 => new T1Exception($"Computer Link Error ({data}): Checksum error (Checksum mismatch is detected)"),
|
||||
_ => new T1Exception($"Computer Link Error ({data}): Unknown error"),
|
||||
};
|
||||
} else if (cmd == "EE") {
|
||||
int code = Convert.ToInt32(data);
|
||||
var error = Enum.IsDefined(typeof(T1Error), code) ? (T1Error)code : T1Error.UNKNOWN_ERROR;
|
||||
throw new T1Exception($"PLC Error ({data}): {error.GetName()}" + (error.GetDescription() != null ? $" ({error.GetDescription()})" : ""));
|
||||
} else if (expectedCmd != null) {
|
||||
throw new T1Exception($"Unexpected response from PLC (T1): {cmd} {data}");
|
||||
}
|
||||
|
||||
return (cmd, data);
|
||||
}
|
||||
|
||||
public async Task Test(string data = "PINGPONG") {
|
||||
await SendCommand("TS", data);
|
||||
var (_, res) = await ReceiveResponse("TS");
|
||||
if (data.Replace(" ", "") != res) throw new T1Exception("Wrong test response from PLC (T1)");
|
||||
}
|
||||
|
||||
public async Task<Status> StatusRead() {
|
||||
await SendCommand("ST", checksum: false);
|
||||
var (_, res) = await ReceiveResponse("ST");
|
||||
return new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0'));
|
||||
}
|
||||
|
||||
public async Task<T1Error> ErrorStatusRead() {
|
||||
await SendCommand("ER", checksum: false);
|
||||
var (_, res) = await ReceiveResponse("ER");
|
||||
var code = Convert.ToInt32(res, 10);
|
||||
return Enum.IsDefined(typeof(T1Error), code) ? (T1Error)code : T1Error.UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
public Task<ushort[]> DataRead(string register, int length) {
|
||||
return DataRead((register, length));
|
||||
}
|
||||
|
||||
public async Task<ushort[]> DataRead(params (string Register, int Length)[] registers) {
|
||||
await SendCommand("DR", string.Join(',', registers.Select(r => $"{r.Register},{r.Length}")));
|
||||
var (_, res) = await ReceiveResponse("DR");
|
||||
if (res.Length != registers.Sum(r => r.Length) * 4) throw new T1Exception("Invalid response length from PLC (T1)");
|
||||
return [.. Enumerable.Range(0, res.Length / 4).Select(i => Convert.ToUInt16(res.Substring(i * 4, 4), 16))];
|
||||
}
|
||||
|
||||
public Task<Status> DataWrite(string register, params ushort[] values) {
|
||||
return DataWrite((register, values));
|
||||
}
|
||||
|
||||
public async Task<Status> DataWrite(params (string Register, ushort[] Values)[] registers) {
|
||||
await SendCommand("DW", string.Join(',', registers.Select(r => $"{r.Register},{r.Values.Length}," + string.Join(',', r.Values.Select(v => $"{v:04X}")))));
|
||||
var (_, res) = await ReceiveResponse("ST");
|
||||
return new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0'));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user