Автоматически воссоздавать PollValues <T> после потери соединения с ПЛК или загрузки программы ПЛК
Я пытаюсь написать приложение C#, в котором клиент ADS будет автоматически повторно подключать / обновлять значения чтения / записи и подписки, когда приложение теряет соединение с PLC или будет загружена новая версия программы PLC.
Я использую библиотеку TwinCAT.Ads.Reactive v4.4.0 от NuGet.
Моя программа:
Подключиться к серверу ADS (подключение успешно)=>
- Создайте реактивное уведомление с помощью ValueSymbolExtensions.WhenValueChanged
Создать значение реактивной циклической записи ValueSymbolExtensions.WriteValues
- 3[a] Создание значений реактивного циклического опроса AnyTypeExtensions.PollValues.T
- 3[b] Я также пробовал ValueSymbolExtensions.PollValues, который еще не задокументирован на веб-сайте Beckhoff.
На данный момент я обнаружил, что 1. и 2. работают, даже если я отключу кабель Ethernet или загружу новую программу в ПЛК -> WriteValues () и WhenValueChanged() обновятся внутри
КОД:
// WhenValueChanged()
TreeViewSymbols = SymbolLoaderFactory.Create(_client, SymbolLoaderSettings.Default).Symbols; // Load symbol tree from plc
IValueSymbol boolVal = (IValueSymbol)TreeViewSymbols["SomeBoolValue"];
boolVal.WhenValueChanged().Subscribe(Observer.Create<object>(val => ArchiveData((bool)val)));
// WriteValues()
IValueSymbol toggleBit = (IValueSymbol)TreeViewSymbols["toggle_bit"];
toggleBit.WriteValues(
Observable.Interval(TimeSpan.FromSeconds(1.0)).Select(x => x % 2 == 0 ? false : (object)true),
e => Debug.WriteLine($"Error writing toggle bit")
);
Я читаю в PollValues () каждую вторую настраиваемую структуру данных. Во время нормальной работы это работает нормально, но после того, как я меняю программу ПЛК и загружаю изменения в ПЛК, или соединение потеряно (отключенный кабель Ethernet), эта подписка не работает внутренне и не восстанавливается, как описано выше.
IValueSymbol state = (IValueSymbol)TreeViewSymbols[Cfg.ModuleStateTag];
state.PollValues(TimeSpan.FromSeconds(1.0))
.Subscribe(Observer.Create<object>(
val => // val comes as byte[] array
{
var a = new ModuleStateData((byte[])val);
Debug.WriteLine($"Status values Machine Mode:{a.MachineMode}");
},
e => Debug.WriteLine($"Error reading status"),
() => Debug.WriteLine($"OnComplete???? reading status"))
).AddDisposableTo(_disposables);
_client.PollValues<ModuleStateData>(
Cfg.ModuleStateTag,
TimeSpan.FromSeconds(1.0)
).Subscribe(Observer.Create<object>(
val =>
{
Debug.WriteLine($"Status values Machine Mode:{val.MachineMode});
},
e => Debug.WriteLine($"Error reading status - {e.Message}"),
() => Debug.WriteLine($"OnComplete???? reading status"))
).AddDisposableTo(_disposables);
ConnectionStateChanged
Также событие изменения состояния подключения запускается, только если я вызываю Connect()/Disconnect() в рекламном клиенте, а не при проблемах с подключением. Есть идеи, как я могу узнать о проблеме с подключением?
2 ответа
Я сообщил об этой проблеме создателям пакета nuget.
Ответ 6.3.2020:
Спасибо, что сообщили об этой проблеме, это важный аспект. Я кратко рассмотрел ваше дело. К сожалению, автоматическое воскрешение наблюдаемого на самом деле не поддерживается. Причина в том, что PollValues внутренне использует дескрипторы символов, которые станут недействительными при обновлении текущей программы ПЛК. Итак, на самом деле ваше единственное решение - зарегистрироваться для события SymbolVersionChanged в TcAdsClient/AdsConnection (оно отправляется после загрузки / перезапуска) и воссоздать Observable.
https://infosys.beckhoff.de/content/1031/tc3_adsnetref/7313543307.html?id=2192955395989567903
Если вам уместно дождаться следующей версии пакета AdsClient / Ads.Reactive - у вас должна быть возможность справиться с этой ситуацией внутри кода PollValues (как вы ожидаете). Теперь он у меня в моем списке TODO.
Прежде чем я получил этот ответ, я отредактировал их реализацию и создал свой собственный метод расширения.
Реализация TwinCAT.Ads.Reactive v4.4.0
public static IObservable<T> PollValues<T>(
this IAdsConnection connection,
string instancePath,
int[] args,
IObservable<Unit> trigger,
Func<Exception, T> errorHandler)
{
DisposableHandleBag bag = new DisposableHandleBag(connection, (IList<string>) new string[1]
{
instancePath
});
Func<Unit, T> selector = (Func<Unit, T>) (o =>
{
try
{
return (T) connection.ReadAny(61445U, bag.GetHandle(instancePath), typeof (T), args);
}
catch (Exception ex)
{
if (errorHandler != null)
return errorHandler(ex);
throw;
}
});
Action finallyAction = (Action) (() =>
{
bag.Dispose();
bag = (DisposableHandleBag) null;
});
return trigger.Select<Unit, T>(selector).Finally<T>(finallyAction);
}
Мое редактирование - где я создаю дескриптор для каждого чтения и удаляю дескриптор после чтения
public static IObservable<T> MyPollValues<T>(
this IAdsConnection connection,
string instancePath,
int[] args,
IObservable<Unit> trigger,
Func<Exception, T> errorHandler)
{
Func<Unit, T> selector = (Func<Unit, T>)(o =>
{
try
{
var handle = connection.CreateVariableHandle(instancePath);
var data = (T)connection.ReadAny(handle, typeof(T), args);
connection.DeleteVariableHandle(handle);
return data;
}
catch (Exception ex)
{
if (errorHandler != null)
return errorHandler(ex);
throw;
}
});
return trigger.Select<Unit, T>(selector);
}
Я нашел решение для половины моей проблемы, когда наблюдаемое работает даже при ошибке подключения (отключенный кабель Ethernet), но после загрузки новой программы PLC (версия символа рекламы изменена) она не воссоздает переменную с новой версией, поэтому она просто выдает ошибку.
Решение заключается в использовании другой перегрузки PollValues, где я указываюFunc<Exception, T> errorHandler
. Этот обработчик работает как резервное значение в случае ошибки.
_client.PollValues<ModuleStateData>(
Cfg.ModuleStateTag,
TimeSpan.FromSeconds(1.0),
e =>
{
Debug.WriteLine($"Error reading status {Cfg.Name} - {e.Message}");
return new ModuleStateData()
{
// Set data in case of error
};
}
);