воскресенье, 1 сентября 2013 г.

ручная фильтрация праметров с учетом качества



собственно все очень просто и понятно ... но всеже :)

есть у нас параметр me.input, к примеру  и нужно при определенных условиях его дальше не передавать или передавать не его ... ну или [придумать что нужно  с ним сделать]

передаем мы его в me.output

пишем вот такой скрипт

имя
script : setOutput

условия
Trigger type: DataChange 
Expression: me.input
Quality changes: True

сам скрипт в котором я  копирую значение чеез SetAttributeVT( Attribute, Value, TimeStamp);
если разница между значениями меньше 100 ну а если больше то тоже копирую но уже без "извращений" и выставляю ему качество в "bad"

dim diff as integer;
diff = 100; ' предел для фильтрации шума, если нужно менять то задать как UDA
if isGood(me.input) and ABS(me.output - me.input) < diff then
SetAttributeVT( me.output , me.input, me.input.Time);
else
me.output = me.input; ' если не нужно передавать значение новое то закоментровать
setBad(me.output );
endif;

вторник, 13 августа 2013 г.

Порты фаервола для SP и Historian буду дополнять если не все сразу нашел :)




Хисториан
File and printer sharing445/tcp
SQL Server Browser1434/udp
SQL TCP1433/tcp/udp
Remote IDAS uses35 thru 139(TCP/UDP) and Port 45 (tCP and UDP)/udp
Платформа
DCOM135/tcp
File and printer sharing445/tcp
SQL TCP1433/tcp
SQL Server Browser1434/udp
  • и вот это  похоже тоже надо Ports 1024 to 65535  :)
    (вообще странно но в Tech Note 876 это есть)
не обязательно
DAS SI Direct 102
DAS MBTCP 502
DAS ABTCP 2221
DAS ABTCP 2222
DAS ABTCP 2223
S/L DA Servers 5413
DAS ABCIP 44818

желательно
ICMP

взял из "Checklist for System Platform implementations"

а если серьезно то рекомендую деалть отдельный VLAN для общения компонетов системы между собой и отдельный для клиентов

среда, 24 июля 2013 г.

вот тут SMC хранит информацию о соседних платформах

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\ArchestrA\Framework\Platform\PlatformNodes

именно SMC :) иногда .. ,при восстановления бекапа галактики на другой машине например, надо эту ветку пеенести со старой машины 

понедельник, 15 июля 2013 г.

ping скриптами System Platform

Собственно все очень просто :

периодический асинхронный скрипт

dim pingSender as System.Net.NetworkInformation.Ping;
pingSender = new System.Net.NetworkInformation.Ping();

dim reply as System.Net.NetworkInformation.PingReply;
reply = pingSender.Send( me.targIPorHostName, 2500 );

if reply.Status == System.Net.NetworkInformation.IPStatus.Success then
me.result = 0;
me.PingErr = False;
me.RoundtripTime = reply.RoundtripTime;
else 
me.result = me.result +1;
me.RoundtripTime = reply.RoundtripTime;  
endif;

и еще один скрипт который следит за количеством пропущенных пингов

WhileTrue по этому условию me.result &gt; me.pingErrLimit

me.PingErr = True;

ну а дальше на me.PingErr вешаем все что нам нужно

P.S.: 2500 - таймаут для пинга ... если не поставить то скрипт будет ну очень долго ждать результата, а это в System Platform неприемлемо.

среда, 22 мая 2013 г.

Один из множества вариантов выгрузки данных из Historian

      Часто возникает необходимость выгружать данные для каких либо внешних систем ... впринципе есть много разных способов это сделать ...
      Ниже я приведу SQL запрос кторый читает список тегов из одной таблицы и по этому списку читает среднее за пол часа из Historian. Чтение сделано курсором .. не самый эфективный вариант с точки зрения производительности но если нужно выгружать не много и не часто то пойдет. А если нет то можно оптимизировать, но это уже отдельный вопрос :)

если кому тема интересна но непонятна ... пишите, прокоментирую :)


SET NOCOUNT ON

DECLARE @StartDate DateTime;
DECLARE @EndDate DateTime;

SET @StartDate =  convert(nvarchar,  (SELECT ISNULL(Max([DateTime]),DateAdd(HH,-24,GetDate())) FROM testExportDB.dbo.data ), 21);


SET @EndDate = GetDate()
SET NOCOUNT OFF
select @StartDate,@EndDate
DECLARE @TagName nvarchar(256);


DECLARE tags_cursor CURSOR LOCAL FOR 
SELECT     TagName
FROM         testExportDB.dbo.Tags
WHERE     (ScheduleNumber = 1);

OPEN tags_cursor
FETCH NEXT FROM tags_cursor INTO @TagName; 
WHILE @@FETCH_STATUS = 0
BEGIN

--INSERT INTO testExportDB.dbo.data

SELECT temp.TagName ,Description ,DateTime  ,Value ,Unit = ISNULL(Cast(EngineeringUnit.Unit as VarChar(20)),'N/A')  ,QualityString  From (
SELECT  * 
 FROM Runtime.dbo.History
 WHERE History.TagName IN (@TagName)
 AND wwRetrievalMode = 'Average'
 AND wwResolution = 1800000
 AND wwVersion = 'Latest'
 AND DateTime >= @StartDate
 AND DateTime <= @EndDate) temp
LEFT JOIN Runtime.dbo.Tag ON Tag.TagName =temp.TagName
LEFT JOIN Runtime.dbo.AnalogTag ON AnalogTag.TagName =temp.TagName
LEFT JOIN Runtime.dbo.EngineeringUnit ON AnalogTag.EUKey = EngineeringUnit.EUKey
LEFT JOIN Runtime.dbo.QualityMap ON QualityMap.QualityDetail = temp.QualityDetail
 WHERE temp.StartDateTime >= @StartDate


FETCH NEXT FROM tags_cursor INTO @TagName
END

CLOSE tags_cursor
DEALLOCATE tags_cursor

а вот этот запрос выгружает таблицу в CSV файл


DECLARE @FileCSV VARCHAR(100),
@Query VARCHAR(256),
@FileNameDT DateTime;
SET @FileNameDT = DateAdd(HH,-24,GetDate());

SET @FileCSV = 'c:\tmp\data' + convert(nvarchar, @FileNameDT, 112)  + '-0000.csv'
SET @Query = 'SQLCMD -S . -d testExportDB -Q "SELECT * FROM testExportDB.dbo.data" -s "," -o ' + @FileCSV

EXEC xp_cmdshell @Query


а этот (прямо из мсдн скопировал)разрешает работу предидущего


-- To allow advanced options to be changed.
EXEC sp_configure 'show advanced options', 1
GO
-- To update the currently configured value for advanced options.
RECONFIGURE
GO
-- To enable the feature.
EXEC sp_configure 'xp_cmdshell', 1
GO
-- To update the currently configured value for this feature.
RECONFIGURE
GO

среда, 17 апреля 2013 г.

Преобразование строки в ДатуВремя

Столкнулся тут с "забавной" особенностью ... Особенность:
Если создать галактику на локализованной версии (например русской) Windows и потом перенести бекап на не локализованную (английскую), то тип UDA DateTime в нашей галактике будет инициализироваться в русском формате (т.е. 16.04.2013 14:28:06.951) а например в BTL он используется для выполнения задержки перед построением модели и там есть вот такая строка:

deployTime = System.DateTime.Parse(Me.BTL.Deploy.LastTime);

System.DateTime.Parse ждет на входе строку в формате en-US (4/16/2013 2:28:06 PM) а получает  в формате ru-RU, и вполне предсказуемо скрипт у нас вылетает с критической ошибкой :(
вот такой:
chAppEngineBase_001.testParse: Script performed an illegal operation.
chAppEngineBase_001.testParse: mscorlib: String was not recognized as a valid DateTime.

в BTL 2012R2 эту строку заменили на более сложный вариант:


If NOT System.DateTime.TryParse(Me.BTL.Deploy.LastTime, deployTime) THEN 
deployTime = System.DateTime.ParseExact(Me.BTL.Deploy.LastTime, "M/d/yyyy h:mm:ss.fff tt", System.Globalization.CultureInfo.InvariantCulture); 
EndIf;

Но он в моем случае тоже не работает .... пока что я пришел к такому варианту :


Dim deployTime as System.DateTime;
deployTime = System.DateTime.Parse(Me.BTL.Deploy.LastTime,System.Globalization.CultureInfo("ru-RU",true));



Но он к сожалению требует прямого указания входной локали .... зато работает :) 

вторник, 26 марта 2013 г.

Работа с большими числами в SP побайтно

Появилась задача получить из контролера и обработать в платформе параметр типа Double, но в ПЛК такого типа нет и сам ПЛК читает этот параметр со счетчика ....  ПЛК получает байты в обратном порядке, и нам нужно их развернуть. Сделал разборку double на 2 integer и разворот и сборку обратно ... работает но вот правильно или нет это вопрос ....
 Дальше скрипт разборки на 2 части


'массивы байт
dim bytes8 [8] as System.Byte;
dim bytes1 [4] as System.Byte;
dim bytes2 [4] as System.Byte;
'системные переменные для работы
dim sysdouble as System.Double;
dim sysint64 as System.int64;
dim sysint32 as System.int32;
'берем число типа double из атрибута и конвертируем в масив байт
sysdouble = System.Convert.ToDouble(me.inputDouble);
bytes8[] = System.BitConverter.GetBytes(sysdouble);

'заполняем массивы типа integer на 8 и 2 по 4 элемента
'нужны нам для удобства отладки в Object Viewer
me.int8byte[1] =bytes8[1]; me.int1byte[1] =bytes8[8];
me.int8byte[2] =bytes8[2]; me.int1byte[2] =bytes8[7];
me.int8byte[3] =bytes8[3]; me.int1byte[3] =bytes8[6];
me.int8byte[4] =bytes8[4]; me.int1byte[4] =bytes8[5];

me.int8byte[5] =bytes8[5]; me.int2byte[1] =bytes8[4];
me.int8byte[6] =bytes8[6]; me.int2byte[2] =bytes8[3];
me.int8byte[7] =bytes8[7]; me.int2byte[3] =bytes8[2];
me.int8byte[8] =bytes8[8]; me.int2byte[4] =bytes8[1];

'делим наш массив из 8 байт на 2 4х байтных
bytes1[1] =bytes8[8];
bytes1[2] =bytes8[7];
bytes1[3] =bytes8[6];
bytes1[4] =bytes8[5]; 

bytes2[1] =bytes8[4];
bytes2[2] =bytes8[3];
bytes2[3] =bytes8[2];
bytes2[4] =bytes8[1];

'конвертируем 1й массив в переменную типа int32 
sysint32 = System.BitConverter.Toint32(bytes1[], 0);
'и копируем ее в атрибут 
me.int1= sysint32 ;
'конвертируем результат в 16чную и 2ичную строки для удобства отладки
me.hexStrByte1 = System.Convert.ToString( sysint32 , 16 );
me.binStrByte1 = System.Convert.ToString( sysint32 , 2 );

'конвертируем 2й массив в переменную типа int32 
sysint32 = System.BitConverter.Toint32(bytes2[], 0);
'и копируем ее в атрибут 
me.int2= sysint32 ;
'конвертируем результат в 16чную и 2ичную строки для удобства отладки
me.hexStrByte2 = System.Convert.ToString( sysint32 , 16 );
me.binStrByte2 = System.Convert.ToString( sysint32 , 2 );



me.Int1 и me.Int2 - два входных параметра которые мы принимаем в тип Double т.к. Integer для развернутых по байтно чисел может не хватить.

И скрипт сборки обратно


'массивы байт
dim bytes1 [4] as System.Byte;
dim bytes2 [4] as System.Byte;
dim bytes3 [8] as System.Byte;
'системные переменные для работы
dim sysint64 as System.int64;
dim sysdouble as System.Double;
'при конвертировании в масив байт число разворачивается
sysint64= System.Convert.ToInt64(me.Int1);
bytes1[] = System.BitConverter.GetBytes(sysint64);

sysint64= System.Convert.ToInt64(me.Int2);
bytes2[] = System.BitConverter.GetBytes(sysint64);
'заполнение переменных для отладки
me.intFromByte1[1] =bytes2[4]; me.int1FromByte[1] =bytes1[1];
me.intFromByte1[2] =bytes2[3]; me.int1FromByte[2] =bytes1[2];
me.intFromByte1[3] =bytes2[2]; me.int1FromByte[3] =bytes1[3];
me.intFromByte1[4] =bytes2[1]; me.int1FromByte[4] =bytes1[4];

me.intFromByte1[5] =bytes1[4]; me.int2FromByte[1] =bytes2[1];
me.intFromByte1[6] =bytes1[3]; me.int2FromByte[2] =bytes2[2];
me.intFromByte1[7] =bytes1[2]; me.int2FromByte[3] =bytes2[3];
me.intFromByte1[8] =bytes1[1]; me.int2FromByte[4] =bytes2[4];
'копируем масивы байт в 1 и разворачиваем
bytes3[1] =bytes2[4];
bytes3[2] =bytes2[3];
bytes3[3] =bytes2[2];
bytes3[4] =bytes2[1]; 

bytes3[5] =bytes1[4];
bytes3[6] =bytes1[3];
bytes3[7] =bytes1[2];
bytes3[8] =bytes1[1];

sysdouble = System.BitConverter.ToDouble(bytes3[], 0);
'sysint64 = System.BitConverter.Toint64(bytes3[], 0);
me.strResult = sysdouble ;
me.doubleResult = sysdouble ;


И рисунок по которому возможно лучше понятно что происходит


вот только вопрос "Правильно ли это все работает?!" пока открыт :)
велика вероятность что у меня просто четное количество ошибок :( -  НОРМ. РАБОТАЕТ :)

пятница, 15 февраля 2013 г.

Комплексные данные в custom property ? можно даже так !

Вот очень интересный и полезный материал по работе с комплексными данными в системной платформе от "everdyn". Очень рекомендую. :)

  http://www.everdyn.com/storing-complex-datas-in-a-single-custom-property/

И в догонку скрипт как функция. Тоже может быть очень полезно.
http://www.everdyn.com/use-scripts-as-functions-in-archestra-graphics/

вторник, 5 февраля 2013 г.

case в quick script ? а если вот так ? :)


Скрипт в символе по DataChange переменной State

if isGood(State) then

dim TxtStTable as System.Collections.Hashtable;
TxtStTable = new System.Collections.Hashtable;

TxtStTable.Add(0,"Отключена");
TxtStTable.Add(10,"Открыта");
TxtStTable.Add(11,"Закрыта");
TxtStTable.Add(12,"Открытие");
TxtStTable.Add(13,"Закрытие");
TxtStTable.Add(20,"Заклинивание");
TxtStTable.Add(21,"Тепловая защита");

if TxtStTable.ContainsKey( state ) then
Text1.Text = TxtStTable[state];
else
Text1.Text = "Состояние не определено";
endif;

else
Text1.Text = "Quality = Bad";
endif;