Showing posts with label CreateVariableHandle. Show all posts
Showing posts with label CreateVariableHandle. Show all posts

November 6, 2019

Производительность ReadAny и Reactive в 5.0.0-preview1

В продолжении темы измерения скорости чтения, посмотрим что там с превью версией под .NET Core 3. Заодно увидим как обстоят дела с реактивностью.


ReadAny


static void Main(string[] args)
{
    AdsSession session = new AdsSession(new AmsAddress("5.28.214.97.1.1", AmsPort.R0_RTS + 1));
    session.Connect();
    var connection = session.Connection;

    const int HOWMUCH = 1000;
    List results = new List();

    uint hMain_CycleCount = connection.CreateVariableHandle("MAIN.CycleCount");

    for (int i = 0; i < HOWMUCH; i++)
        results.Add(
            (uint)connection.ReadAny(hMain_CycleCount, typeof(uint)));

    foreach (var cycleNumber in results)
        Console.WriteLine(cycleNumber);
}


.NET Core 3, AdsRouterConsole



Для сравнения повтор картинки из предыдущего поста для .NET 4.7.1, TwinCAT.IO Router



Reactive


static void Main(string[] args)
{
    AdsSession session = new AdsSession(new AmsAddress("5.28.214.97.1.1", AmsPort.R0_RTS + 1));
    session.Connect();

    const int HOWMUCH = 1000;

    List<uint> results = new List<uint>();

    var valueObserver = Observer.Create(
        val => { // on next
            results.Add(val);
            //Console.WriteLine(val);
        },

        exc => { // on error
            Console.WriteLine(exc.Message);
        },

        () => {  //on completed
            Console.WriteLine($"Read {results.Count} cycles");

            foreach (var cycleNumber in results)
                Console.WriteLine(cycleNumber);
        });

    var subscription = session.Connection
        .WhenNotification("MAIN.CycleCount", NotificationSettings.ImmediatelyOnChange)
        .Take(HOWMUCH)
        .Subscribe(valueObserver);

    Console.WriteLine("ENTER to stop...");
    Console.ReadLine();
}


Все совсем плохо:



Отрицательные значения показывают, что пакеты прилетали не по порядку, а как-то иначе. Причем, если убрать условие в получении N-значений .Take(1000), и просто попытаться выбрать требуемое количество, то получится такая же точно каша с непоследовательными элементами. Что нарушает уверенность в целостности системы.

При времени цикла меньше одной секунды, перестает работать делегат отвечающий за onComplеte часть обозревателя. Именно поэтому пришлось добавить в onNext вывод промежуточных результатов.

Пока что очень сыро и сильно напрягает непоследовательность приходящих элементов.

May 7, 2015

Как готовить синхронные функции ADS.API

Сборник рецептов как корректно обращаться с базовыми синхронными функциями ADS.API.


Инициализация


PROGRAM MAIN
VAR
    PLCVar : INT;
END_VAR
...
private int hVar;
private TcAdsClient adsClient;
private short plcVar;
...

TcAdsClient adsClient = new TcAdsClient(); 


Подключаемся к ПЛК-рантайму №1.
Для TwinCAT2 порт = 801, для TwinCAT3 порт = 851.

adsClient.Connect("172.16.3.217.1.1", 801); // TwinCAT 2
adsClient.Connect("172.16.3.217.1.1", 851); // TwinCAT 3


Подключение


Как таковой связи еще нет, мы всего-лишь подключились к локальному роутеру. Поэтому, перед созданием дескрипторов переменных, необходимо как-то поймать момент, когда переменные ПЛК уже доступны. Произойдет это после запуска ПЛК-задачи контроллера:

StateInfo info = new StateInfo(AdsState.Invalid, 0);
while (info.AdsState != AdsState.Run)
{
    try
    {
        info = adsClient.ReadState();
    }
    catch { }

    Thread.Sleep(100); // ждем 100мс перед следующей проверкой
}
// OnConnected(); // отправляем в родительский поток сообщение о подключении

Такой кусок кода нужно запустить в отдельном потоке и дождаться сообщения о благополучном запуске задачи. В C#.NET 4.5 удобно использовать конструкции async/await.


Синхронное чтение


Создаем дескриптор для переменной из контроллера

try
{
    hVar = adsClient.CreateVariableHandle("MAIN.PLCVar");
}
catch(Exception ex)
{
    MessageBox.Show(ex.Message);
}


Немного исправим способ из справочной системы.

plcVar = (short) adsClient.ReadAny(hVar, typeof(short));          // Было
plcVar = (short) adsClient.ReadAny(hVar, plcVar.GetType());       // Стало *
plcVar = (short)(adsClient.ReadAny(hVar, plcVar.GetType()) ?? 0); // Избыточный вариант **

(*) typeof(short) лучше превратить в plcVar.GetType(), это заставит разработчика проинициализировать переменную до ее первого использования.
(**) ReadAny() возвращает object который всегда не-null, иначе выбрасывается исключение, т. е. null не бывает никогда, поэтому можно избежать использования nullable-типов (short?) с дальнейшей проверкой на null или более простой конструкции "??".


Обработка ошибок


Так как до- или во время чтения/записи может произойти обрыв связи или другие неприятности, необходима обработка исключений.

try
{
    plcVar = (short)adsClient.ReadAny(hVar, plcVar.GetType());
}
catch {}

Но, здесь нет анализа ошибки.


try
{
    plcVar = (short)adsClient.ReadAny(hVar, plcVar.GetType());
}
catch(Exception ex)
{
    MessageBox.Show(ex.Message);
}

Вместо анализа ошибки, закидает пользователя и рабочий стол сообщениями об ошибке.


AdsErrorCode errCode = AdsErrorCode.NoError;
try
{
    plcVar = (short)adsClient.ReadAny(hVar, plcVar.GetType());
}
catch(AdsErrorException ex)
{
    errCode = ex.ErrorCode;
}
catch(Exception genex)
{
    // Произошло что-то не связанное с ADS.API
}

if (errCode != AdsErrorCode.NoError)
{
    switch(ex.ErrorCode)
    {
        case AdsErrorCode.ClientSyncTimeOut:
            // Обрыв связи
            // OnDisconnected();
            break;
        case AdsErrorCode.DeviceSymbolNotFound:
            // Переменная пропала (например, была стерта программы)
            // OnInvalidData();
            break;
        default:
            // Все, что не требует обработки
            // OnGeneralError();
            break;
    }
}

Вариант правильный, но очень длинный. На каждое чтение/запись такое не напишешь. Поэтому для чтения данных мастерят "обертки", берущие на себя всю рутину обработки ошибок и пр.