Разработка ADS.API двигается по пути упрощения интерфейса библиотеки: от работы с потоками через индексы и смещения (AdsStream), к работе с произвольными типами данных в виде именованных переменных (Read|WriteAny). Имена переменных ПЛК-задачи удобнее, чем абстрактные потоки байт, но по-прежнему необходимо преобразовывать имена в дескрипторы и только затем работать с ними. Напрашивается сделать следующий шаг на пути сокращения.
Symbol в TwinCAT-системе — это описание переменной и вся возможная информация о ней, т. е. что-то очень похожее на отражения или рефлексию в языках высокого уровня. Вообще, работа с переменными в библиотеке ADS.API построена не самым лучшим образом. Если есть желание взглянуть на то, каким оно должно быть или стать в будущем — лучше взгляните на независимый проект adsclient на гитхабе. Когда понравится — перечитайте про преимущество другой стороны ADS.
Как бы там ни было, а работать с этими функциями очень просто: обе принимают на вход имя переменной ПЛК-задачи в виде текстовой строки и, в зависимости от операции (чтение или запись), тип переменной (Type) или значение переменной. Последним параметром идет некое "обновление символьной информации": о нем и поговорим чуть позже, а пока как использовать эти функции:
using (TcAdsClient client = new TcAdsClient()) { client.Connect("192.168.1.17.1.1", 801); client.WriteSymbol("MAIN.uint16", (ushort)123, false); bool m_bool1 = (bool) client.ReadSymbol( "MAIN.bool1", typeof(bool), false); byte m_byte8 = (byte) client.ReadSymbol( "MAIN.byte8", typeof(byte), false); short m_int16 = (short) client.ReadSymbol( "MAIN.int16", typeof(short), false); ushort m_uint16 = (ushort) client.ReadSymbol( "MAIN.uint16", typeof(ushort), false); ushort m_word16 = (ushort) client.ReadSymbol( "MAIN.word16", typeof(ushort), false); float m_real4b = (float) client.ReadSymbol( "MAIN.real4b", typeof(float), false); double m_lreal8b = (double) client.ReadSymbol( "MAIN.lreal8b", typeof(double), false); }
Аналогично для "системных" типов данных:
client.WriteSymbol("MAIN.word16", (UInt16)456, false); Int16 s_int16 = (Int16) client.ReadSymbol( "MAIN.int16", typeof(Int16), false); UInt16 s_uint16 = (UInt16) client.ReadSymbol( "MAIN.uint16", typeof(UInt16), false); UInt16 s_word16 = (UInt16) client.ReadSymbol( "MAIN.word16", typeof(UInt16), false); Single s_real4b = (Single) client.ReadSymbol( "MAIN.real4b", typeof(Single), false); Double s_lreal8b = (Double) client.ReadSymbol( "MAIN.lreal8b", typeof(Double), false);
Обратная разработка
Упрощения не даются просто так и где-то что-то приходится приносить в жертву. Простые в использовании функции могут работать как-то не так или просто "тормозить". Заглянем во внутренности библиотеки, а конкретнее — в TcAdsClient. С небольшими сокращениями функции выглядят так:
public object ReadSymbol(string name, Type type, bool reloadSymbolInfo) { [...] if (this._symbolInfoTable == null) this._symbolInfoTable = new SymbolInfoTable(this); return this._symbolInfoTable.ReadSymbol(name, type, reloadSymbolInfo); } public void WriteSymbol(string name, object value, bool reloadSymbolInfo) { [...] if (this._symbolInfoTable == null) this._symbolInfoTable = new SymbolInfoTable(this); this._symbolInfoTable.WriteSymbol(name, value, reloadSymbolInfo); }
Видно, что это обычные обертки создающие объект некой таблицы символьной информации, а затем вызывающие одноименные методы этой таблицы. На ум сразу приходит идея о кешировании дескрипторов и пр. Смотрим дальше:
namespace TwinCAT.Ads.Internal { internal class SymbolInfoTable { private Dictionary<string, TcAdsSymbol> _symbolTable; private DatatypeInfoTable _datatypeTable; private TcAdsClient _adsClient; public SymbolInfoTable(TcAdsClient adsClient) { this._adsClient = adsClient; this._symbolTable = new Dictionary<string, TcAdsSymbol>((IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase); this._datatypeTable = new DatatypeInfoTable(adsClient); }
И да, действительно, это обертки для кеширования информации о переменных: словарь имен и таблиц символов, коллекция типов данных и обратная ссылка на владельца — класс AdsClient. Делаем вывод — данная таблица создается при первом вызове одной из функций ads-клиента и хранит информацию о переменных, что сокращает время и количество операций при работе с переменными ПЛК-задачи.
Третий параметр
Вернемся к "reloadSymbolInfo", третьему параметру функций Read|WriteSymbol. Он позволяет сбросить, стереть кешированную символьную информацию, накопленную в таблицах объекта класса SymbolInfoTable. Это хорошо видно в реализации методов класса (в начале каждого метода параметр проверяется и если требуется — таблица очищается):
public void WriteSymbol(string name, object value, bool bReloadInfo) { if (bReloadInfo) this.Cleanup(); [...] public object ReadSymbol(string name, Type type, bool bReloadInfo) { if (bReloadInfo) this.Cleanup(); [...]
Зачем нужно чистить таблицы, если они созданы для кеширования данных и ускорения работы? Для того, чтобы в случае устаревания этих данных, мы могли их сбросить. Устареть информация может при перезагрузке контроллера или при использовании одной и той же программы с различными контроллерами. Остальное додумайте самостоятельно.
Передача данных
Осталось выяснить как передаются данные. Продолжаем рассматривать методы Read|WriteSymbol класса SymbolInfoTable, пропускаем сброс таблиц (3):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public void WriteSymbol(string name, object value, bool bReloadInfo) { [...] TcAdsSymbol symbol = this.GetSymbol(name, true); if (symbol == null) TcAdsDllWrapper.ThrowAdsException(AdsErrorCode.DeviceSymbolNotFound); TcAdsDatatype dataype = this._datatypeTable.GetDataype(symbol.Type); if (dataype == null) throw new NotSupportedException("Type of symbol not supported"); AdsStream stream = new AdsStream((int) dataype.size); AdsBinaryWriter writer = new AdsBinaryWriter(stream); if (dataype.IsPrimitive) this.SetPrimitiveType(symbol.Name, value, dataype.managedType, symbol.Datatype, symbol.Size, writer); |
В строках 4-6 идет выборка кешированной информации, затем (строки 8-10) выборка типов переменных и наконец подготовка потоков (строки 12-13, AdsStream, AdsBinaryWriter). Никакой магии.
В строках 15-16 и далее, идет запись данных через подготовленные потоки, но это уже другая история.
Выводы:
- Символьная информация кешируется, а дескрипторы создаются и уничтожаются незаметно для разработчика.
- Если что-то случится, таблицы можно сбросить.
- Передача данных по-прежнему ведется через потоки, т. е. быстро.
- Функции удобны, просты и нужно использовать.
No comments
Post a Comment
Note: Only a member of this blog may post a comment.