PDA

Просмотр полной версии : MasterOPC и чтение файла с ПЛК



energvk
27.02.2015, 16:51
Добрый день. Пытаюсь разобраться как можно читать opc-сервером файл, созданный не модулем Archiver плк1хх, а программно. На выходе у меня получается файл с содержанием


2000.4.24 3:37:46 #avars=250\r\n2000.4.24 3:37:55 #avars=721\r\n2000.4.24 3:38:4 #avars=740\r\n


Конфигурацию сервера беру из примера "Конфигурация для чтения архивов контроллеров ОВЕН" с Вашего сайта, чтобы понять принцип работы. Сначала файл имел вид


2000.4.24 3:37:46 #avars=250
2000.4.24 3:37:55 #avars=721
2000.4.24 3:38:4 #avars=740

При этом сервер ругался на неправильный разделитель (использовал '$R$N'). После исправления на '\r\n' в Сообщениях скриптов стало чисто, но при этом тэг выдает значение ноль и COMM_FAILURE. Посмотрел скрипт, вроде как должно читать (только закомментил строки преобразования 16-ти ричного формата).
Направьте в нужном направлении :rolleyes:

P.S. Запросы и ответы вроде приходят, судя по логу


27-02-2015 15:55:42.452 Node2::Device1:(COM2) Rx: [0102] 01 14 61 60 06 32 30 30 30 2E 34 2E 32 34 20 33 3A 33 37 3A 34 36 20 23 61 76 61 72 73 3D 32 35 30 5C 72 5C 6E 32 30 30 30 2E 34 2E 32 34 20 33 3A 33 37 3A 35 35 20 23 61 76 61 72 73 3D 37 32 31 5C 72 5C 6E 32 30 30 30 2E 34 2E 32 34 20 33 3A 33 38 3A 34 20 23 61 76 61 72 73 3D 37 34 30 5C 72 5C 6E 3F BD
27-02-2015 15:55:42.420 Node2::Device1:(COM2) Tx: [0012] 01 14 07 06 00 00 00 00 00 0A 79 23
27-02-2015 15:55:32.358 Node2::Device1:(COM2) Rx: [0102] 01 14 61 60 06 32 30 30 30 2E 34 2E 32 34 20 33 3A 33 37 3A 34 36 20 23 61 76 61 72 73 3D 32 35 30 5C 72 5C 6E 32 30 30 30 2E 34 2E 32 34 20 33 3A 33 37 3A 35 35 20 23 61 76 61 72 73 3D 37 32 31 5C 72 5C 6E 32 30 30 30 2E 34 2E 32 34 20 33 3A 33 38 3A 34 20 23 61 76 61 72 73 3D 37 34 30 5C 72 5C 6E 3F BD
27-02-2015 15:55:32.311 Node2::Device1:(COM2) Tx: [0012] 01 14 07 06 00 00 00 00 00 0A 79 23

SCADAMaster
27.02.2015, 20:33
Исходная строка выглядит так:
2012.09.04 13:19:39 #000=0000
У вас так:
2000.4.24 3:37:46 #avars=250

Вот код анализирующий значения:
local k,z= string.find(str1, "=" ); --разбираем остальные элементы по символа "="
if k~=nil and z~=nil then
local str2=string.sub(str1,z+1); --символ найден - выделяем подстроку со значением
local str3=string.sub(str1,1,z-1); --символ найден - выделяем подстроку с номером
elem[str3+1]=str2; --вставляем в таблицу полученный элемент
else
То есть по номеру извлеченному из строки определяется куда затем писать значение (в какой тег). У вас же вместо номера значения - имя.
В принципе можно доработать, вставлять в таблицу не по номеру а просто функцией table.insert

energvk
27.02.2015, 22:11
Спасибо, понял в чем была проблема.
А можно в скрипте заменить символы конца строки с '\r\n' на '$R$N'?

SCADAMaster
28.02.2015, 09:43
В смысле другой разделитель использовать? Можно. Это убираем:
local n,l= string.find(str, "\r\n" ); --находим разделитель
if n~=nil and l~=nil then
server.Message("Некорректный разделитель, чтение невозможно");
return;
end;
Здесь исправляем:
n,l= string.find(str, "\n\r" ); --находим разделитель

energvk
28.02.2015, 13:08
Я, в принципе так и представлял. Меня смутило, что архивируя с конфигурации, получаю файл на выходе с текстом типа:


2000.04.23 21:40:36 #000=0000

2000.04.23 21:40:41 #000=0204

2000.04.23 21:44:31 #000=0200

2000.04.23 22:07:51 #000=0100

Т. е. с разделителем '$r$n'. При этом когда я пытался прочитать свой файл, скрипт ругался на неправильный разделитель. Поставив '\r\n', скрипт затих, но файл стал выглядеть


2000.4.24 3:37:46 #avars=250\r\n2000.4.24 3:37:55 #avars=721\r\n2000.4.24 3:38:4 #avars=740\r\n

Т.е. строка не переносится.

Сможет ли скрипт понять символы '$r$n'? Проверить пока не могу.

SCADAMaster
28.02.2015, 15:30
Какой зададите разделитель, по такому и будет происходить разбор строки.

energvk
04.03.2015, 13:55
Так, всё равно, что-то не получается. Закомментировал строки:


local n,l= string.find(str, "\r\n" ); --находим разделитель
if n~=nil and l~=nil then
server.Message("Некорректный разделитель, чтение невозможно");
return;
end;

Исправил

n,l= string.find(str, "\n\r" ); --находим разделитель
на

n,l= string.find(str, "$R$N" ); --находим разделитель
но в ответ - тишина.
Запросы:


04-03-2015 12:52:12.563 Node1::Device1:(xxx.xxx.xx.xx:502) Rx: [0053] 03 00 00 00 00 2F 01 14 2C 2B 06 3A 33 32 20 23 30 30 30 3D 32 35 36 0D 0A 32 30 30 30 2E 34 2E 32 39 20 30 3A 34 38 3A 34 35 20 23 30 30 30 3D 35 31 32 0D 0A
04-03-2015 12:52:12.563 Node1::Device1:(xxx.xxx.xx.xx:502) Tx: [0016] 03 00 00 00 00 0A 01 14 07 06 00 00 00 14 00 0A
04-03-2015 12:52:12.563 Node1::Device1:(xxx.xxx.xx.xx:502) Rx: [0111] 02 00 00 00 00 69 01 14 66 65 06 0D 0A 32 30 30 30 2E 34 2E 32 39 20 30 3A 33 38 3A 31 20 23 30 30 30 3D 35 31 32 0D 0A 32 30 30 30 2E 34 2E 32 39 20 30 3A 33 38 3A 31 33 20 23 30 30 30 3D 34 30 30 0D 0A 32 30 30 30 2E 34 2E 32 39 20 30 3A 33 38 3A 32 36 20 23 30 30 30 3D 31 34 35 30 0D 0A 32 30 30 30 2E 34 2E 32 39 20 30 3A 34 38
04-03-2015 12:52:12.563 Node1::Device1:(xxx.xxx.xx.xx:502) Tx: [0016] 02 00 00 00 00 0A 01 14 07 06 00 00 00 0A 00 0A
04-03-2015 12:52:12.563 Node1::Device1:(xxx.xxx.xx.xx:502) Rx: [0111] 01 00 00 00 00 69 01 14 66 65 06 32 30 30 30 2E 34 2E 32 38 20 34 3A 35 31 3A 31 34 20 23 30 30 30 3D 34 0D 0A 32 30 30 30 2E 34 2E 32 38 20 34 3A 35 31 3A 35 37 20 23 30 30 30 3D 34 0D 0A 32 30 30 30 2E 34 2E 32 38 20 35 3A 31 3A 35 37 20 23 30 30 30 3D 34 0D 0A 32 30 30 30 2E 34 2E 32 38 20 35 3A 37 3A 31 35 20 23 30 30 30 3D 34
04-03-2015 12:52:12.547 Node1::Device1:(xxx.xxx.xx.xx:502) Tx: [0016] 01 00 00 00 00 0A 01 14 07 06 00 00 00 00 00 0A

В сообщениях скриптов пусто, в значениях пусто, качество - COMM_FAILURE

Сам файл выглядит таким образом:

2000.4.28 4:51:14 #000=4
2000.4.28 4:51:57 #000=4
2000.4.28 5:1:57 #000=4
2000.4.28 5:7:15 #000=4
2000.4.29 0:38:1 #000=512
2000.4.29 0:38:13 #000=400
2000.4.29 0:38:26 #000=1450
2000.4.29 0:48:32 #000=256
2000.4.29 0:48:45 #000=512

Что-то не так я делаю...Только вот,что?

SCADAMaster
04.03.2015, 14:29
Проверьте - находит ли скрипт данный разделитель.
Добавьте после строки поиска разделителя команду server.Message и выведите в него переменные позиции символа.

energvk
04.03.2015, 14:49
На строки

n,l= string.find(str, "$R$N" ); --находим разделитель
server.Message("$R$N", len); получаю, такой ответ

04-03-2015 13:54:57.636 Node1.Device1.Архив:$R$Ntable: 03ED44C0

SCADAMaster
04.03.2015, 15:34
Что за переменная len? Нужно вывести n и l.

energvk
04.03.2015, 15:53
То есть, если я правильно понял, то при

n,l= string.find(str, "$R$N" ); --находим разделитель
server.Message("$R$N", n);
server.Message("$R$N", l);

В ответ получаю


04-03-2015 14:52:53.353 Node1.Device1.Архив:$R$Nnil
04-03-2015 14:52:53.353 Node1.Device1.Архив:$R$Nnil
04-03-2015 14:52:53.353 Node1.Device1.Архив:$R$Nnil
04-03-2015 14:52:53.353 Node1.Device1.Архив:$R$Nnil
04-03-2015 14:52:53.337 Node1.Device1.Архив:$R$Nnil
04-03-2015 14:52:53.337 Node1.Device1.Архив:$R$Nnil

energvk
04.03.2015, 16:10
Прописал

if n==nil and l==nil then
server.Message("Некорректный разделитель, чтение невозможно");
end;
Соответсвенно, получил в сообщениях сервера

"Некорректный разделитель, чтение невозможно"

Получается он не может прочитать разделитель "$R$N"?

Извечный вопрос, что делать?

SCADAMaster
04.03.2015, 16:27
Значит такой разделитель некорректен. Попробуйте "\r\n"

energvk
04.03.2015, 16:37
Да, с этим уже получилось разобраться.
Теперь другой вопрос, не могу получить значения в скаде. Переменная в системе подсвечена серым.

energvk
04.03.2015, 16:41
Разобрался, спасибо за помощь

SCADAMaster
04.03.2015, 16:48
Вы добавили в дерево системы OPC HDA сервер? В настройках OPC HDA в MasterSCADA задали глубину запроса?
В самом ОРС сервере, в режиме исполнения если выделить тег и посмотреть на закладку "HDA" - будут данные?

energvk
04.03.2015, 17:20
Вы добавили в дерево системы OPC HDA сервер? В настройках OPC HDA в MasterSCADA задали глубину запроса?
В самом ОРС сервере, в режиме исполнения если выделить тег и посмотреть на закладку "HDA" - будут данные?

Как я уже сказал чуть выше, всё получилось. Но...
При выводе на тренд, у меня есть только последнее значение, т.е. в opc такая картина:


14;4;GOOD;2015-03-04 16:00:11.000
13;4;GOOD;2015-03-04 15:48:55.000
12;16;GOOD;2015-03-04 15:46:57.000
11;4;GOOD;2015-03-04 15:46:16.000
10;256;GOOD;2015-03-04 15:44:34.000
9;512;GOOD;2015-03-04 15:40:49.000
8;512;GOOD;2015-03-04 15:40:48.000
7;512;GOOD;2015-03-04 15:40:46.000
6;512;GOOD;2015-03-04 15:40:45.000
5;512;GOOD;2000-04-29 02:47:29.000
4;512;GOOD;2000-04-29 02:47:28.000
3;512;GOOD;2000-04-29 02:47:26.000
2;512;GOOD;2000-04-29 02:47:25.000
1;512;GOOD;2000-04-29 02:47:23.000


А на графике только последнее значение

SCADAMaster
04.03.2015, 18:00
Значит что-то неправильно настроили в HDA - проверьте еще раз. Также проверьте что у ОРС HDA переменных включена архивация.
За 2000 год данные не считаются - это слишком большая глубина запроса, да и вряд ли они вам нужны.

energvk
05.03.2015, 12:27
Была выключена архивация. Спасибо, всё заработало

SCADAMaster
05.03.2015, 12:46
Вообще странно, по умолчанию у HDA тегов архивация включена (периодическая с шагом 00:00:00). Если только вы сами ее выключили.

energvk
05.03.2015, 12:57
По-моему сам выключал

energvk
05.03.2015, 13:15
Ещё небольшой вопрос: показания архива считываются и значения на тренде есть. Но задумка такая, в плк у меня биты упаковываются в WORD. Соответственно, я хочу полученный архив распаковать на биты, битам присвоить соответствующее событие,которое сохранялось бы в журнале. Например, АВАРИЯ1, АВАРИЯ2 и т.п. и чтобы время соответствовало времени в архивном файле.
Но пока не получается. Не подскажите направление?)

SCADAMaster
05.03.2015, 15:02
Задача не очень простая. Сначала вам нужно извлечь из архива данные и обработать их, затем на основе этих данных сформировать сообщения.
Эту задачу можно решить только через ФБ "Скрипт C#". Вот тут есть пример скрипта получающего значения:
http://www.owen.ru/forum/showthread.php?t=11806&p=137117&viewfull=1#post137117
Примеры генерации сообщений из скрипта есть в этой теме:
http://www.owen.ru/forum/showthread.php?t=17504

energvk
05.03.2015, 15:12
Понял, попробую разобраться, спасибо.

energvk
06.03.2015, 16:58
Н-да, тёмный лес для меня

energvk
06.03.2015, 17:16
Не пойму, как указать время

var startTime= //время начала считывания архива
var endTime= //время конца считывания архива

SCADAMaster
06.03.2015, 18:33
Ну в вашем случае StartTime - это последнее значение которое вы считывали (сохраните его между перезапусками в отдельной переменной), а endTime - время последней архивной записи переменной (LastItemTime).

energvk
26.04.2015, 14:47
Добрый день! Снова засел таки за решение желаемой задачи. Вроде дело сдвинулось с мертвой точки. Прописал скрипт:


using System;
using MasterSCADA.Script.FB;
using MasterSCADA.Hlp;
using MasterSCADA.Hlp.Archive;
using FB;
using System.Linq;

public partial class ФБ : ScriptBase
{
public override void Execute()
{
string name=HostFB.TreeItemHlp.FullName+".Входы."+"Вход";
var elem = (ITreePinHlp)HostFB.TreeItemHlp.Project.Item(name) ;
PinDataArchiveHlp k=elem.DataArchiveItem;
var startTime=k.FirstItemTime; //время начала считывания архива
var endTime=k.LastItemTime; //время конца считывания архива
var mas=k.Read(startTime, endTime, true); //читаем архив
//mas - коллекция значений
Значение=Convert.ToDouble(mas[0].Value); //получаем значение нулевого элемента коллекции
Время=mas[0].Time.ToLocalTime(); //получаем время нулевого элемента коллекции
}
}

Ошибок компиляции нет.
Запускаю проект в режим исполнения, на входе скрипта вижу архивные значения:
17906

При этом на выходах "Значение" и "Время" пусто.
17907

Так и должно быть или я как то неправильно применяю скрипт?

SCADAMaster
26.04.2015, 15:29
На входах неопределенное значение - возможно скрипт просто ни разу не выполнился. Попробуйте включить периодический опрос или прописать код в методе Start.

energvk
26.04.2015, 18:19
Удалил архив данных, перезапустил, заработало.
Массив данных формируется, на выходах "Значение" и "Время" получаю значения первого элемента массива. Теперь не знаю куда двигаться дальше. В задумке распаковка значений в биты и формирование на их основании сообщений.

P.S.

Ну в вашем случае StartTime - это последнее значение которое вы считывали (сохраните его между перезапусками в отдельной переменной)

Как лучше это сделать?

SCADAMaster
26.04.2015, 19:10
Удалил архив данных, перезапустил, заработало.
Массив данных формируется, на выходах "Значение" и "Время" получаю значения первого элемента массива. Теперь не знаю куда двигаться дальше. В задумке распаковка значений в биты и формирование на их основании сообщений.

Считываете массив значений, потом поочередно их перебираете, извлекаете биты и делаете что вам требуется с ними.



Как лучше это сделать?
Объявите переменную типа DateTime до метода Execute.

energvk
02.05.2015, 22:09
Продолжаю разбираться, принцип вроде понял но возникла проблема, в которой никак не могу разобраться. Упростил уже как только можно.
В итоге при запуске я получаю только одно сообщение, хотя avarsArr.Count() и journal.Count() показывает 20 значений. При этом, когда открываю журнал объекта - он пустой. Что я делаю не так?

Почему то код нормально в форум не вставляется - прикладываю отдельным файлом.
17990

energvk
02.05.2015, 23:46
Некоторые изменения:
Убрал Array.Reverse(avarsArr);
Сортировку журнала выполнил как в примере от Меркурия:


List<T1T2> journal = new List<T1T2>(); //формируем коллекцию со значениями (пока временно с Меркурия)
//сортировка по возрастанию. null - выше всех
journal.Sort(delegate(T1T2 us1, T1T2 us2)
{return us1.T1.HasValue ? (us2.T1.HasValue ? us1.T1.Value.CompareTo(us2.T1.Value):-1): 1; });

Получил 20 сообщений как и должны быть. Почему - непонятно. Но главное работает. Однако в журнале так ничего и не появляется.

SCADAMaster
03.05.2015, 12:12
У событий от которых вы генерируете сообщения включены каналы "Журнал" и "Архив"?

energvk
03.05.2015, 14:21
Да, включены, поэтому и не понимаю в чём дело

SCADAMaster
03.05.2015, 14:27
Приложите ваш проект.

energvk
03.05.2015, 15:08
Проект в приложении

17993

SCADAMaster
03.05.2015, 15:57
Мы проверили - у нас сообщения появляются (см. приложение).
Вы ведь хотели этот скрипт нам заказать - уже передумали?

energvk
03.05.2015, 19:48
Мы проверили - у нас сообщения появляются (см. приложение).

Блин, как так то?! Не понимаю, в чем проблема... У меня не появляются...


Вы ведь хотели этот скрипт нам заказать - уже передумали?

Не то чтобы передумал, просто самому то интересно, а тут появилось немного свободного времени, решил поразбираться.

energvk
04.05.2015, 00:17
Мы проверили - у нас сообщения появляются (см. приложение).

Ну уже совсем не понимаю, журналы уже и удалил и создал и пересоздал, а там всё равно пусто...:confused:

energvk
06.05.2015, 00:19
Как выяснилось, при запуске в режим исполнения, генерируются только сообщения из архива, при этом запись в журнал не выполняется (почему не ясно). При дальнейшей работе в режиме исполнения, если приходят новые данные, то генерируются сообщения уже с записью в журнале. В ходе экспериментов удалось один раз получить при запуске скады и сообщения и записи в журнале, хотя я уже в этом не уверен, так как повторить сие чудо не удалось. Также заметил любопытный момент. Получил несколько записей в журнале (около 10) и через некоторое время обнаружил, что журнал пуст, также при мне пропадали первые записи в журнале через какой то период времени. Это с чем может быть связано? Немного в ступоре.

SCADAMaster
06.05.2015, 08:18
Возможно это связано с тем что вы генерируете несколько одинаковых сообщений с одинаковыми метками времени за одну сессию.

energvk
06.05.2015, 11:24
Возможно это связано с тем что вы генерируете несколько одинаковых сообщений с одинаковыми метками времени за одну сессию.

Да вроде нет, прикладываю скрин, на котором видно разные метки времени. Изначально было 13 сообщений, на скрине их осталось уже 4. Сейчас по факту 2.
18039

P.S.Теперь журнал опять пуст...

SCADAMaster
06.05.2015, 11:39
У нас проблема не проявляется - возможно вы смотрите не за тот интервал времени.
Также просмотре ваш код и сверяйте с кодом из примера.

energvk
06.05.2015, 12:04
Не понимаю, что значит смотрю не за тот интервал времени?

SCADAMaster
06.05.2015, 13:12
На скриншоте не видно за какое время журнал. Возможно вы сгенерировали сообщения за интервал который в журнале не отображается.

energvk
06.05.2015, 14:02
Спасибо, я просто идиот :)

energvk
07.05.2015, 11:32
Получился более-менее рабочий вариант. Но не могу понять каким образом заставить присваиваться значениям "LastTime".
Код такой:


var project = HostFB.TreeItemHlp.Project;
var filter = new EventFilterData();
//получение последнего сообщения в архиве
var eventItem=HostFB.TreeItemHlp.Parent.GetChild(name) ;
var eventItem1=HostFB.TreeItemHlp.Parent.GetChild(name 1);
var eventItem2=HostFB.TreeItemHlp.Parent.GetChild(name 2);
filter.Sources=new List<ITreeObjectHlp> {eventItem};
//filter.Sources=new List<ITreeObjectHlp> {eventItem1};
//filter.Sources=new List<ITreeObjectHlp> {eventItem2};
filter.ChildObjects=false;
var server=project.GetService<EventServer>();
var enumerator = server.CreateEnumerator(HostFB.TreeItemHlp.Parent, filter, true);
var archEvents = enumerator.Next(1);
var LastActiv=false;
DateTime? LastTime=null;

if (archEvents.Count>0)
{
//определяем активность последнего сообщения
LastActiv=archEvents[0].IsActive;
//и метку времени начала
LastTime=archEvents[0].ActiveTime; //запоминает метку последнего сообщения даже при сбросе архива
}

var alarmId = ((IFBEvents)eventItem.FBObject).AlarmID;
var alarmId1 = ((IFBEvents)eventItem1.FBObject).AlarmID;
var alarmId2 = ((IFBEvents)eventItem2.FBObject).AlarmID;


//записываем сообщения в архив
//System.Diagnostics.Debug.Assert(false);
for (int i=0;i<journal.Count;i++)
{
//если метка времени равна null - выходим из цикла
if (journal[i].T1.HasValue==false) break;
//если метка времени сообщения прибора меньше последнего сообщения архива - пропускаем
if (LastTime.HasValue==true && journal[i].T1.Value<LastTime.Value.ToLocalTime().AddSeconds(-1))
continue;
//если метка времени равна последнему сообщению и сообщение неактивно пропускаем
if (LastTime.HasValue==true && journal[i].T1.Value==LastTime.Value.ToLocalTime() &&
//если сообщение активно, но не будет завершено - также пропускаем
(LastActiv==false || (LastActiv==true && journal[i].T2.HasValue==false))) continue;
//получаем значение времени начала
var TimeStart=journal[i].T1.Value.ToUniversalTime();
DateTime? TimeEnd=null;
//если значение есть - получаем значение времени конца
if (journal[i].T2.HasValue==true) TimeEnd=journal[i].T2.Value.ToUniversalTime();
//если это первая запись после старта и архивирование в базу данных - не создаем стартовое сообщение
if ((ActivStart==true && ToSUBD==true)==false &&
//если последнее сообщение - то не создаем стартовое сообщение
(ActivStart==false && LastActiv==true)==false)

{
if (((journal[i].I1.Value >> 0) & 1) == 1)
{
//создаем сообщение
project.AlarmManager.OnFBEventTimed(alarmId1, text1, -1, (short)EventStatus.EventOn, (uint)OpcQuality.Good,TimeStart);
project.AlarmManager.OnFBEventTimed(alarmId1, text1, -1, (short)EventStatus.EventOff, (uint)OpcQuality.Good,TimeEnd);
}
if (((journal[i].I1.Value >> 1) & 1) == 1)
{
project.AlarmManager.OnFBEventTimed(alarmId2, text2, -1, (short)EventStatus.EventOn, (uint)OpcQuality.Good,TimeStart);
project.AlarmManager.OnFBEventTimed(alarmId2, text2, -1, (short)EventStatus.EventOff, (uint)OpcQuality.Good,TimeEnd);
}
ActivStart=false;
LastActiv=false;
}
}

Как я понимаю, что-то не правильно в строках 1-15. Или что неправильно?

SCADAMaster
07.05.2015, 16:17
В коде у вас все правильно. LastTime это время последнего сообщения (если таковое имеется).

energvk
08.05.2015, 10:51
В коде у вас все правильно. LastTime это время последнего сообщения (если таковое имеется).

Да. Это понятно. LastTime обновлялась, когда я генерировал сообщения


//создаем стартовое сообщение
{project.AlarmManager.OnFBEventTimed(alarmId, text, -1, (short)EventStatus.EventOn, (uint)OpcQuality.Good,TimeStart);
//project.AlarmManager.OnFBEventTimed(alarmId, text, -1, (short)EventStatus.EventOff, (uint)OpcQuality.Good,TimeEnd);}

Соответственно, когда я стал генерировать только сообщения по разбору битов (alarmId1, alarmId2 и т.д.), то LastTime уже не обновляется, что логично. А хотелось бы, чтобы разбор битов и генерация сообщений и записей в журнал начиналась именно с последнего активного события (как в примере), но при этом само событие не вызывало сообщения и запись в журнале, а начинался его разбор битов и т.п. Примерно представляю как это можно сделать, но это сильно раздует код, уверен, что это можно реализовать аккуратно и красиво, но пока не пойму как (или нельзя?).

И ещё меня смущает один момент по считыванию массива данных. Объявил переменную времени до метода Execute:


public partial class ФБ : ScriptBase
{
DateTime startArchT = new DateTime(); // переменная начала считывания архива
}

В самом Excute прописал так:

if (startArchT==null) //если время не определено, начинаем считывание сначала архива
{
startArchT=k.FirstItemTime;
}
var startArch=startArchT; //начало считывания архива
var endArch=k.LastItemTime; //конец считывания архива
var avarsArr=k.Read(startArch, endArch, true); //записываем считанные данные из архива в массив
startArchT=k.LastItemTime; //присваиваем время начала архива
Всё работает, но непонятно почему Архив=avarsArr.Count(); стал показывать всё время значение 1, хотя если в архив приходит сразу несколько сообщений, обрабатываются все. Хотя Журнал=journal.Count() тоже стал всегда равен "1" как и Архив, при коде:



List<T1T2I1> journal = new List<T1T2I1>(); //формируем коллекцию со значениями

for (int i=0;i<avarsArr.Count();i++)
{
T1T2I1 rec; //структура времени начала и окончания события (для журнала и сообщений)
DateTime? TStart=null; //время начала
DateTime? TStop=null; //время окончания
Int16? avarsVal=null; //код аварии

TStart=avarsArr[i].Time.ToLocalTime(); //получаем метку времени события
TStop=TStart.Value.AddSeconds(1); //прибавляем 1 сек. (так как данные в архиве только о начале)
avarsVal=Convert.ToInt16(avarsArr[i].Value); //получаем код аварии

rec.T1=TStart; //формируем структуру данных
rec.T2=TStop;
rec.I1=avarsVal;
journal.Add(rec); //добавляем запись
}
Журнал_Каунт=journal.Count();

До этого было

startArchT=k.FirstItemTime;
var startArch=startArchT; //начало считывания архива
var endArch=k.LastItemTime; //конец считывания архива
var avarsArr=k.Read(startArch, endArch, true); //записываем считанные данные из архива в массив

и Архив=avarsArr.Count() показывал суммарное значение элементов массива.

В общем пытаюсь оптимизировать код и буду благодарен любым подсказкам.

Заранее спасибо.

P.S. Какие то проблемы со вставкой кода в форум. Часто часть кода просто не отображается...

energvk
16.05.2017, 15:10
Добрый день! В продолжение темы. Есть архив с данными:
31172

Обрабатываю его скриптом:

using System;
using InSAT.OPC;
using MasterSCADA.Script.FB;
using MasterSCADA.Hlp;
using FB;
using System.Linq;
using MasterSCADALib;
using System.Collections.Generic;
using MasterSCADA.Common.Events;
using MasterSCADA.Hlp.Pins;
using MasterSCADA.Hlp.Events;
using MasterSCADA.Hlp.Archive;
using MasterSCADA.Archive.Events;

public partial class ФБ : ScriptBase
{
DateTime startArchT = new DateTime(); // переменная начала считывания архива
//string name="Событие";


public override void Execute() //алгоритм выполнения записывается в теле процедуры Execute()
{
//Получаем архивные данные
string events=HostFB.TreeItemHlp.FullName+".Входы."+"Вход"; //путь до входа скрипта
var elem = (ITreePinHlp)HostFB.TreeItemHlp.Project.Item(event s);
PinDataArchiveHlp k=elem.DataArchiveItem; //переменная из архива

//if (startArchT==null) //если время не определено, начинаем считывание сначала архива
//{
//startArchT=k.FirstItemTime;
//}
var startArch=k.FirstItemTime; //начало считывания архива
var endArch=k.LastItemTime; //конец считывания архива
var eventsArr=k.Read(startArch, endArch, true); //записываем считанные данные из архива в массив
//startArchT=k.LastItemTime; //присваиваем время начала архива по последней считанной записи
Параметр1=eventsArr.Count();

//Int16? eventsVal=null; //код аварии
//НомерСобытия=Convert.ToInt16(eventsArr[Convert.ToByte(eventsArr.Count())].Value); //получаем код аварии
//ВремяСобытия=Convert.ToString(eventsArr[Convert.ToByte(eventsArr.Count())].Time.ToLocalTime());
for (int i=0;i<eventsArr.Count();i++)
{
DateTime? TStart=null; //время начала
Int16? eventsVal=null; //код аварии

eventsVal=Convert.ToInt16(eventsArr[i].Value); //получаем код аварии
//Параметр1=Convert.ToInt16(eventsArr[i].Value);
НомерСобытия=eventsVal;
ВремяСобытия=Convert.ToString(eventsArr[i].Time.ToLocalTime());
}

}

}

31174

На выходе получаю некорректный массив:
31173

Не могу понять в чём дело. Перепробовал массу вариантов обработки, опросов (периодический/по изменению/входов-выходов), но корректный массив с данными не могу получить. Что-то не так в коде скрипта?

SCADAMaster
16.05.2017, 16:14
Смотрите под отладчиком:
http://blog.insat.ru/2016/10/script-csharp8.html
Навскидку:
Параметр1=eventsArr.Count();
Надо:
Параметр1=eventsArr.Length;

energvk
16.05.2017, 23:21
Параметр1=eventsArr.Length;

Такой вариант я тоже пробовал, разницы никакой. Значения eventsArr.Count() и eventsArr.Length были равны при этом.


Смотрите под отладчиком:
http://blog.insat.ru/2016/10/script-csharp8.html

Н-да, очень не хотелось ставить такой громоздкий Visual Studio из-за такой мелкой задачи, код казалось бы малюсенький. Но видимо без этого никак. Похоже оставлю задачку на потом

Даже элементарно:

string events=HostFB.TreeItemHlp.FullName+".Входы."+"Вход"; //путь до входа скрипта
var elem = (ITreePinHlp)HostFB.TreeItemHlp.Project.Item(event s);
PinDataArchiveHlp k=elem.DataArchiveItem; //переменная из архива

var startArch=k.FirstItemTime; //начало считывания архива
var endArch=k.LastItemTime; //конец считывания архива
var eventsArr=k.Read(startArch, endArch, true); //записываем считанные данные из архива в массив
//startArchT=k.LastItemTime; //присваиваем время начала архива по последней считанной записи
MassiveLength=eventsArr.Length;
MassiveCounts=eventsArr.Count();

на входящие 58 значений eventsArr.Length=eventsArr.Count()=55 меня ставят в ступор

energvk
23.05.2017, 18:07
В общем поставил VisualStudio, включил отладку, но мне это не помогло. Не знаю, что в данном случае контролировать в VS. Последний вариант кода такой:


using System;
using MasterSCADA.Script.FB;
using MasterSCADA.Hlp;
using MasterSCADA.Hlp.Archive;
using FB;
using System.Linq;
using MasterSCADA.Hlp.Pins;

public partial class ФБ : ScriptBase
{

public override void Start()
{
System.Diagnostics.Debug.Assert(false);
}

public override void Execute() //алгоритм выполнения записывается в теле процедуры Execute()
{
//Получаем архивные данные
var elem = HostFB.InputGroup.GetPin("Вход").TreePinHlp;
var k=elem.DataArchiveItem;

DateTime startArch=k.FirstItemTime.ToUniversalTime();
DateTime endArch=k.LastItemTime.ToUniversalTime(); //конец считывания архива

var eventsArr=k.Read(startArch, endArch, false); //записываем считанные данные из архива в массив

Параметр1=eventsArr.Length;

foreach (var element in eventsArr)
{
НомерСобытия=Convert.ToInt16(element.Value);
ВремяСобытия=Convert.ToString(element.Time.ToLocal Time());
}
}
}

Разницы в результате никакой. В архиве 68 событий, после запуска в массиве получаю 66, после останова/последующего запуска 68. Но при этом первых двух значений нет, и последующие не всегда совпадают с архивными.

SCADAMaster
23.05.2017, 18:54
Попробуйте из startArch вычесть секунду, а к endArch прибавить.

energvk
24.05.2017, 12:07
К сожалению не помогло. Получил 67 элементов (при исходных 68)

При var eventsArr=k.Read(startArch, endArch, true);
Исходный архив:
31329

Полученный массив:
31330

При var eventsArr=k.Read(startArch, endArch, false);
массив также не соответствует архиву, но значения немного другие

SCADAMaster
24.05.2017, 13:02
Приложите проект целиком (вместе с данными - заархивируйте папку проекта и выложите).

energvk
24.05.2017, 13:39
Прикладываю тестовый проект

31337

SCADAMaster
24.05.2017, 14:24
У нас все корректно. Скрипт проверялся в режиме разработки через DoAction.
Проект с исправленным скриптом и файл сравнения значения в приложении

energvk
25.05.2017, 10:29
Спасибо, протестировал. Что осталось непонятным. В таком варианте строка формируется правильно. Только при первом запуске и накоплении архива получаю всё равно -1 запись, первая обрабатывается только при повторном запуске симуляции. Но при том, что

str=str+ Convert.ToString(eventsArr[i].Time.ToLocalTime())+" - "+eventsArr[i].Value.ToString()+"\n";
формирует строку данных правильно, но

eventsVal=Convert.ToInt16(eventsArr[i].Value); //получаем код аварии
даёт на выход некорректные данные (смотрю во вкладке данные), про что я и писал ранее. Никак не могу разобраться почему так

SCADAMaster
25.05.2017, 12:08
Где именно некорректные данные? Мы вам приложили скрипт и сравнение - все совпадает.

energvk
25.05.2017, 22:47
Прилагаю скриншот для понимания что я имею ввиду

31372

SCADAMaster
26.05.2017, 08:59
И что? Где отличия то? С чем сравнивать?

energvk
26.05.2017, 09:07
Элементарно по факту нет дублирующихся значений 4-4, 8-8, 256-256, 16-16

SCADAMaster
26.05.2017, 09:33
В скрипте?
Приложите последнюю версию проекта.

energvk
26.05.2017, 17:24
выкладываю проект
31398

SCADAMaster
26.05.2017, 18:59
Во первых вот здесь не нужно ToUniversal:
DateTime startArch=k.FirstItemTime.AddSeconds(-1);
DateTime endArch=k.LastItemTime.AddSeconds(1); //конец считывания архива
Во вторых для записи нужно использовать метод SetValue и формировать также метку времени. При такой скорости записи скада не успевает получить значения на выход и записать в архив.

energvk
27.05.2017, 12:35
При такой скорости записи скада не успевает получить значения на выход и записать в архив.
У меня были подобные предположения.


Во вторых для записи нужно использовать метод SetValue и формировать также метку времени

Попробую (если пойму как этот метод использовать...)

SCADAMaster
27.05.2017, 17:56
SetValue("ИмяВыхода", new PinValue(Значение,МеткаВремени));

energvk
28.05.2017, 23:44
SetValue("ИмяВыхода", new PinValue(Значение,МеткаВремени));

Спасибо большое, я вообще не в ту сторону пошел искать.

Но всё равно не помогло. "ИмяВыхода" не соответствовало тому что нужно. При опросе по изменению я получил на выходе (с галочкой выхода "Архивировать") 17 значений, при периодическом - 27 (при фактическом 67). Добавил в цикл задержку System.Threading.Thread.Sleep(1000); (не уверен можно ли так делать в цикле). в итоге видел в поле "Текущее значение" как все значения проходили, но на выходе - не то. Не успевает чтоли сохраняться в архив...

SCADAMaster
29.05.2017, 10:33
1000 мс много - достаточно 5-10.
Но вообще ситуация странная - у нас все записывается корректно. Вы метку времени формируете?

energvk
29.05.2017, 15:43
согласен, что странная. У вас наверное нормально записывается, когда данные уже есть. У меня, если не ошибаюсь в таком варианте тоже отрабатывалось нормально. Но когда данных нет и они начинают приходить с опс сервера, вот тут и начинаются эти казусы непонятные. Метку времени формирую. Думаю проще мне будет обработку архива осуществлять в одном скрипте, не заморачиваясь с выходами. Хотелось упростить...

SCADAMaster
29.05.2017, 16:27
Что значит данных нет?
Нет данных, то и писать нечего - контролировать изменение метки времени, и только если поменялось выполнять скрипт. Не должно быть проблем - в таком режиме мы тоже работали.

energvk
29.05.2017, 20:33
Что значит данных нет?

Я имею ввиду, что экспортирую проект для очистки архива, затем запускаю уже экспортированный проект. Т.е. накопление архивных данных начинается с нуля.


Нет данных, то и писать нечего - контролировать изменение метки времени, и только если поменялось выполнять скрипт. Не должно быть проблем - в таком режиме мы тоже работали.

Логично. Только непонятно в чём проблема, с учетом того что внутри скрипта всё нормально формируется. только с выходными данными какая то проблема