March 22, 2016

Автоматизация TwinCAT.Ads API

Удобные библиотеки Бекхофф для работы с протоколом ADS, к сожалению, работают на достаточно низком уровне. С каждым новым обновлением уровень немного повышается, но по прежнему приходится работать с функциями обмена данными, а хочется работать непосредственно с данными. Поэтому был разработан очередной бесплатный костыль на C# .NET 4.5 — библиотека AdsRemote. С открытым исходным кодом.

Внезапно, текущая версия библиотеки 0.5, т. е. родилась она не вчера и уже работает в некоторых проектах. Основные фишки AdsRemote — автоматический контроль канала связи на обрыв соединения и переподключения, а также простой способ работы с переменными ПЛК-программы и шины данных вообще. Кроме этого, библиотека умеет находить контроллеры в Ethernet-подсети и удаленно добавлять запись в AMS-роутер контроллера.

Библиотека пока не умеет работать со структурами и дает чуть большую нагрузку на контроллер, так как активно использует событийный механизм роутера, но универсальные механизмы всегда чуть менее производительны, чем узко заточенные. Удобство и скорость разработки просто так не даются.

Для работы понадобится установленный TwinCAT 2.11 или 3.1, иначе говоря — необходим работающий AMS-роутер. Еще проще говоря — Твинкат должен быть установлен и работать. Инженерной версии вполне достаточно. Не забудьте добавить записи в таблицы роутеров.


Подключение и отключение


Работа с библиотекой начинается с экземпляра контроллера. Для этого передаем AmsNetId контроллера в конструктор класса PLC. Напоминаю, связь не начнет работать до тех пор, пока вы не добавите нужные записи в таблицы роутеров.

PLC plc = new PLC("5.2.100.109.1.1");


С момента инициализации экземпляра объекта, в фоне начинает работать поток  автоматически контролирующий состояние связи с контроллером. Чтобы оперативно узнавать о проблемах со связью, необходимо подписаться на уведомления:

plc.DeviceReady += Plc_DeviceReady; // когда связь установлена или восстановлена
plc.DeviceLost += Plc_DeviceLost;   // когда связь потеряна

[...]

private void Plc_DeviceReady(object sender, AdsDevice e)
{
    Log("READY [" + e.Address.Port.ToString() + "]");
}

AdsDevice в данном случае — это ADS-устройство в составе контроллера. Именно они идентифицируются на шине по номеру порта. Если связь с контроллером прервется, мы получим столько уведомлений, сколько устройств зарегистрировано в объекте ПЛК. Соответственно, при отключении только одного устройства — придет только одно уведомление.


Работа с переменными 


Создавать устройства AdsDevice мы не можем: при добавлении новой переменной, автоматически анализируется номер порта устройства и, при необходимости, добавляется новое устройство. В принципе, разработчику можно об устройствах и портах не задумываться, а работать исключительно с переменными:

Var<short> main_count = plc.Var<short>("MAIN.count");
Var<ushort> main_state = plc.Var<ushort>("MAIN.state");
Var<ushort> frm0 = plc.Var<ushort>("Inputs.Frm0InputToggle", 27907);
Var<ushort> devState = plc.Var<ushort>(0xF030, 0x5FE, 27907);
Var<short> g_Version = plc.Var<ushort>(".VERSION");


С момента создания переменной, ее значение будет автоматически обновляться в соответствии с переменной на стороне контроллера. Переменные типа Var<T> могут спокойно участвовать в обычных арифметических операциях. За редким исключением их тип будет автоматически приведен к значимому или строковому:

long countTotal += main_count;
MessageBox.Show(main_count);


Можно подписаться на уведомления об изменении значения переменной:

main_count.ValueChanged +=
    delegate
    {
        counterStatusLabel.Text = main_count;
    };


Можно использовать единый обработчик для нескольких переменных:

main_count.ValueChanged += OnValueChanged;
main_state.ValueChanged += OnValueChanged;

protected void OnValueChanged (object src, Var v)
{
    ushort val = (ushort)v.GetValue();
    framesTotal += val / 2;
    label1.Text = val.ToString();
};


Для изменения значения переменной на стороне контроллера, необходимо обратиться к внутреннему свойству переменной — RemoteValue:

main_count.RemoteValue = 123;


Связывание группы переменных


Неудобно связывать переменные по одной за раз. Проще дать описание всех сразу, а затем активировать одной строкой:

public class PRG_Main
{
    [LinkedTo("MAIN.count")]
    public Var<short> count;

    [LinkedTo("MAIN.state")]
    public Var<ushort> state;
}


Более подробный вариант:

public class PRG_Main
{
    [LinkedTo("MAIN.count", As: typeof(short), Port: (int)AmsPort3.PlcRuntime1)]
    public Var count;

    [LinkedTo("MAIN.state", Port: (int)AmsPort3.PlcRuntime1)]
    public Var<ushort> state;

    [LinkedTo("Inputs.Frm0InputToggle", Port: 27907)]
    public Var<ushort> frm0_1;

    [LinkedTo(IGrp: 0xF030, IOffs: 0x5F4, Port: 27907)]
    public Var<ushort> frm0;
}

В любом случае, необходимо одним из двух способов указать тип переменной. В противном случае получится NULL, а не экземпляр переменной.

Для дальнейшего использования переменных в связке с WPF-контролами — необходимо объявить их как параметры:

public class PRG_Main
{
    [LinkedTo("MAIN.count")]
    public Var<short> count { get; set; }

    [LinkedTo("MAIN.state")]
    public Var<ushort> state { get; set; }
}


После задания описания, объект с данными создается и активируется одной строкой:

PRG_Main Main = plc.Class<PRG_Main>();


Если требуется вызов сложного конструктора — можно воспользоваться следующим вариантом:

Main = new PRG_Main(param1, param2, ...);
plc.Class(Main);

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

No comments

Post a Comment

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