API Serial Port

Introduzione

Le API descritte di seguito sono esposte dall’oggetto Porta seriale e servono a implementare le logiche del protocollo di comunicazione di uno o più PLC non supportati nativamente da UNIQO.

API disponibili

Di seguito i metodi disponibili:

public void Open();
public void Close();
public byte[] ReadBytes(uint count);
public char[] ReadChars(uint count);
public byte ReadByte();
public char ReadChar();
public string ReadLine();
public byte[] ReadBytesUntil(string delimiter);
public char[] ReadCharsUntil(string delimiter);
public void WriteBytes(byte[] buffer);
public void WriteChars(char[] buffer);
public void WriteLine(string text);
public void CancelRead();

Di seguito le proprietà C# per leggere/scrivere le variabili dell’oggetto e per modificare il funzionamento di alcuni metodi:

public string PortName { get; set; }
public uint BaudRate { get; set; }
public byte DataSize { get; set; }
public ParityType Parity { get; set; }
public StopBitsType StopBits { get; set; }
public FlowControlType FlowControl { get; set; }
public TimeSpan Timeout { get; set; }
public string NewLine { get; set; } = "\n";

Esempio di configurazione protocollo in modalità richiesta/risposta

Di seguito un esempio di un NetLogic, contenuto nell’oggetto Porta seriale, che utilizza la modalità richiesta/risposta per leggere ogni 300 millisecondi il valore di un registro di un PLC Modbus.

Con il metodo WriteBytes si invia il comando attraverso la seriale e con il metodo ReadBytes successivo si attende una risposta dal PLC. Se il PLC non risponde entro il tempo indicato dalla proprietà Timeout viene generata una eccezione e la lettura/scrittura fallisce.

Scarica il progetto di esempio da qui

public class RuntimeNetlogic1 : BaseNetLogic
{
    private SerialPort serialPort;
    private PeriodicTask periodicTask;
    public override void Start()
    {
        serialPort = (SerialPort)Owner;
        periodicTask = new PeriodicTask(Read, 300, Owner);
        periodicTask.Start();
    }
    private void Read()
    {
        try
        {
            ReadImpl();
        }
        catch (Exception ex)
        {
            Log.Error("Failed to read from Modbus: " + ex);
        }
    }
    private void ReadImpl()
    {
        serialPort.WriteBytes(Serialize());
        var result = serialPort.ReadBytes(3);
        if ((result[1] & 0x80) == 0)
        {
            result = serialPort.ReadBytes((uint)(result[2] + 2));
            Log.Info(Deserialize(result));
        }
        else
        {
            Log.Error("Failed to read from Modbus");
        }
    }
    private byte[] Serialize()
    {
        var buffer = new byte[]
        {
            0x01, // UnitId
            0x03, // Function code
            0x00, // Starting address
            0x00,
            0x00, // Quantity Of Registers
            0x01,
            0x84, // CRC
            0x0a
        };
        return buffer;
    }
    private ushort Deserialize(byte[] buffer)
    {
        var first = (ushort)buffer[1];
        var second = (ushort)(buffer[0] << 8);
        return (ushort)(first | second);
    }
}

Esempio di configurazione protocollo in modalità evento

Di seguito un esempio di un NetLogic, contenuto nell’oggetto Porta seriale, che utilizza la modalità a evento per leggere i dati inviati in maniera asincrona da un dispositivo. Il metodo OnClick, collegato all’evento di pressione di un pulsante, permette di interrompere la lettura corrente e di avviarne una nuova.

Per utilizzare questa modalità, la proprietà Timeout dev’essere impostata a 0, così facendo ReadBytes risulterà bloccante fintanto che non arrivano sulla seriale i dati richiesti. È possibile interrompere questa lettura con il metodo CancelRead oppure con il metodo Close. In entrambi i casi viene generata un’eccezione ReadCanceledException. Per effettuare una chiusura pulita del NetLogic è necessario chiamare il metodo Close nella Stop del NetLogic stesso per terminare eventuali operazioni di lettura pendenti.

Scarica il progetto di esempio da qui.

public class RuntimeNetlogic1 : BaseNetLogic
{
    private SerialPort serialPort;
    private LongRunningTask task;
    public override void Start()
    {
        serialPort = (SerialPort)Owner;
        serialPort.Timeout = TimeSpan.FromMilliseconds(0.0);
        task = new LongRunningTask(Run, Owner);
        task.Start();
    }
    [ExportMethod]
    public void OnClick()
    {
        // Cancel current read
        serialPort.CancelRead();
        task.Cancel();
        // Start new read
        task = new LongRunningTask(Run, Owner);
        task.Start();
    }
    private void Run()
    {
        while(!task.IsCancellationRequested)
        {
            try
            {
                // Block until 3 bytes arrive on serial
                var result = serialPort.ReadBytes(3);
                foreach (var b in result)
                    Log.Info(String.Format("0x{0:x2}", b));
            }
            catch (ReadCanceledException ex)
            {
                // In case of read canceled, exit from the loop
                Log.Info(ex.Message);
                return;
            }
            catch (Exception e)
            {
                Log.Error(e.Message);
            }
        }
    }
    public override void Stop()
    {
        // Explicit call to Close to cancel pending read (if any)
        serialPort.Close();
        task.Cancel();
    }
}