Сборник рецептов как корректно обращаться с базовыми синхронными функциями 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;
}
}
Вариант правильный, но очень длинный. На каждое чтение/запись такое не напишешь. Поэтому для чтения данных мастерят "обертки", берущие на себя всю рутину обработки ошибок и пр.