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;
    }
}

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

No comments

Post a Comment

Note: Only a member of this blog may post a comment.