Files
pamhagen-sysctrl/PamhagenSysCtrl/Helpers/T1.cs
T

132 lines
6.1 KiB
C#

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;
public T1(string portName, Parity parity = Parity.Odd) {
Serial = new SerialPort(portName, 9600, parity, 8, StopBits.One) {
Handshake = Handshake.None,
NewLine = "\r",
ReadTimeout = 1000,
WriteTimeout = 1000,
DtrEnable = true,
RtsEnable = true,
Encoding = Encoding.ASCII,
};
Serial.Open();
Serial.DiscardInBuffer();
Serial.DiscardOutBuffer();
}
public void Dispose() {
Serial.Dispose();
GC.SuppressFinalize(this);
}
protected async Task SendCommand(string cmd, string? data = null, bool checksum = true) {
// max 255 bytes
var line = new StringBuilder("(A", 256);
line.AppendFormat("{0:00}{1}", 1, cmd);
if (data != null) {
if (data.Length > 244) throw new ArgumentException("T1 playload too long");
line.Append(data);
}
if (checksum) {
line.Append('&');
var chk = Enumerable.Range(0, line.Length).Aggregate(0, (c, i) => c + line[i]) & 0xFF;
line.AppendFormat("{0:X2}", chk);
}
line.Append(')');
Serial.WriteLine(line.ToString());
}
protected async Task<(string, string)> ReceiveResponse(string? expectedCmd = null) {
var line = Serial.ReadLine();
if (line.Length < 8 || !line.StartsWith("(A") || !line.EndsWith(')') || line[^4] != '&') {
throw new FormatException("Invalid response from PLC (T1)");
}
var chk1 = Convert.ToInt32(line[^3..^1], 16);
var chk2 = Enumerable.Range(0, line.Length - 3).Aggregate(0, (c, i) => c + line[i]) & 0xFF;
if (chk1 != chk2) {
throw new IOException($"Ungültige Prüfsumme von SPS (T1)");
}
var (cmd, data) = (line[4..6], line[6..^4]);
if (cmd == "ST" && data[3] == '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 = "_Ping_Pong_0123456789_abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_") {
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:X}")))));
var (_, res) = await ReceiveResponse("ST");
return new Status((Mode)(res[3] - '0'), ((res[0] - '0') << 4) | (res[1] - '0'));
}
}
}