Автоматически воссоздавать PollValues ​​<T> после потери соединения с ПЛК или загрузки программы ПЛК

Я пытаюсь написать приложение C#, в котором клиент ADS будет автоматически повторно подключать / обновлять значения чтения / записи и подписки, когда приложение теряет соединение с PLC или будет загружена новая версия программы PLC.

Я использую библиотеку TwinCAT.Ads.Reactive v4.4.0 от NuGet.

Моя программа:

Подключиться к серверу ADS (подключение успешно)=>

  1. Создайте реактивное уведомление с помощью ValueSymbolExtensions.WhenValueChanged
  2. Создать значение реактивной циклической записи 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
                    };     
                }
            );
Другие вопросы по тегам